P
PipsGrowth

bt لاختبار المحافظ

إطار اختبار خلفي قائم على شجرة القرارات. مثالي لبناء واختبار استراتيجيات إدارة المحافظ.

الصعوبة: متوسط
الفئة: اختبار خلفي
🌳 قائم على الأشجار

التثبيت

Install bt with pip. It depends on ffn (financial functions) which will be installed automatically.

# Install bt
$ pip install bt
# Install with data sources
$ pip install bt yfinance pandas

الميزات الرئيسية

شجرة القرارات

بناء استراتيجيات معقدة بتركيب عقد قرارية بسيطة.

إعادة التوازن

دعم مدمج لإعادة توازن المحفظة بطرق متعددة.

مقارنة المعايير

مقارنة أداء استراتيجيات متعددة مقابل مؤشرات مرجعية.

رسوم بيانية مدمجة

تصور الأداء وتحليل العوائد بسهولة.

How bt Works

bt uses an algorithm tree where each node is an algorithm that processes in sequence:

1.RunMonthly()→ When to run
2.SelectAll()→ What to consider
3.WeighEqually()→ How to weight
4.Rebalance()→ Execute trades

أمثلة الكود

Simple SMA Crossover Strategy

Create a basic moving average crossover strategy

Python
import bt
import yfinance as yf
# Download data
data = yf.download(['EURUSD=X', 'GBPUSD=X'], start='2020-01-01', end='2024-01-01')['Close']
# Create a simple SMA crossover strategy
def sma_crossover(data, short_period=20, long_period=50):
"""SMA crossover signal generator"""
sma_short = data.rolling(short_period).mean()
sma_long = data.rolling(long_period).mean()
return sma_short > sma_long
# Generate signals
signal = sma_crossover(data, 20, 50)
# Create strategy
strategy = bt.Strategy('SMA Crossover', [
bt.algos.SelectWhere(signal),
bt.algos.WeighEqually(),
bt.algos.Rebalance()
])
# Create backtest
backtest = bt.Backtest(strategy, data)
# Run backtest
result = bt.run(backtest)
# Display results
result.display()
result.plot()

Compare Multiple Strategies

Run and compare different strategies simultaneously

Python
import bt
import yfinance as yf
data = yf.download('EURUSD=X', start='2020-01-01', end='2024-01-01')['Close'].to_frame('EURUSD')
# Strategy 1: Buy and Hold
buy_hold = bt.Strategy('Buy and Hold', [
bt.algos.RunOnce(),
bt.algos.SelectAll(),
bt.algos.WeighEqually(),
bt.algos.Rebalance()
])
# Strategy 2: SMA 50
sma_signal = data > data.rolling(50).mean()
sma_strategy = bt.Strategy('SMA 50 Filter', [
bt.algos.SelectWhere(sma_signal),
bt.algos.WeighEqually(),
bt.algos.Rebalance()
])
# Strategy 3: Monthly Rebalance
monthly = bt.Strategy('Monthly Rebalance', [
bt.algos.RunMonthly(),
bt.algos.SelectAll(),
bt.algos.WeighEqually(),
bt.algos.Rebalance()
])
# Create backtests
backtests = [
bt.Backtest(buy_hold, data),
bt.Backtest(sma_strategy, data),
bt.Backtest(monthly, data)
]
# Run all backtests
results = bt.run(*backtests)
# Compare results
results.display()
results.plot()

Custom Portfolio Weighting

Implement various weighting schemes

Python
import bt
import numpy as np
# Download multi-asset data
symbols = ['EURUSD=X', 'GBPUSD=X', 'USDJPY=X', 'AUDUSD=X']
data = yf.download(symbols, start='2020-01-01', end='2024-01-01')['Close']
data.columns = ['EURUSD', 'GBPUSD', 'USDJPY', 'AUDUSD']
# Equal Weight Strategy
equal_weight = bt.Strategy('Equal Weight', [
bt.algos.RunMonthly(),
bt.algos.SelectAll(),
bt.algos.WeighEqually(),
bt.algos.Rebalance()
])
# Inverse Volatility Weighting
invvol = bt.Strategy('Inverse Volatility', [
bt.algos.RunMonthly(),
bt.algos.SelectAll(),
bt.algos.WeighInvVol(lookback=pd.DateOffset(months=3)),
bt.algos.Rebalance()
])
# Mean-Variance Optimization
mvo = bt.Strategy('Mean-Variance', [
bt.algos.RunMonthly(),
bt.algos.SelectAll(),
bt.algos.WeighMeanVar(lookback=pd.DateOffset(months=6)),
bt.algos.Rebalance()
])
# Custom Fixed Weights
def fixed_weights():
"""Return fixed weights"""
return {'EURUSD': 0.4, 'GBPUSD': 0.3, 'USDJPY': 0.2, 'AUDUSD': 0.1}
fixed = bt.Strategy('Fixed Weights', [
bt.algos.RunMonthly(),
bt.algos.SelectAll(),
bt.algos.WeighSpecified(**fixed_weights()),
bt.algos.Rebalance()
])
# Run comparison
results = bt.run(
bt.Backtest(equal_weight, data),
bt.Backtest(invvol, data),
bt.Backtest(mvo, data),
bt.Backtest(fixed, data)
)
results.plot_weights()

Momentum Strategy

Select assets based on momentum

Python
import bt
import pandas as pd
# Download data
symbols = ['EURUSD=X', 'GBPUSD=X', 'USDJPY=X', 'AUDUSD=X', 'USDCHF=X']
data = yf.download(symbols, start='2018-01-01', end='2024-01-01')['Close']
data.columns = [s.replace('=X', '') for s in symbols]
# Calculate momentum (12-month return)
momentum = data.pct_change(252)
# Momentum strategy - Select top 2 performers
momentum_strategy = bt.Strategy('Momentum Top 2', [
bt.algos.RunMonthly(),
bt.algos.SelectAll(),
bt.algos.SelectMomentum(n=2, lookback=pd.DateOffset(months=12)),
bt.algos.WeighEqually(),
bt.algos.Rebalance()
])
# Dual Momentum - Only buy if positive momentum
class SelectPositiveMomentum(bt.Algo):
def __init__(self, lookback=252):
self.lookback = lookback
def __call__(self, target):
selected = target.temp.get('selected', target.universe.columns)
returns = target.universe[selected].pct_change(self.lookback).iloc[-1]
target.temp['selected'] = returns[returns > 0].index.tolist()
return len(target.temp['selected']) > 0
dual_momentum = bt.Strategy('Dual Momentum', [
bt.algos.RunMonthly(),
bt.algos.SelectAll(),
SelectPositiveMomentum(lookback=252),
bt.algos.SelectMomentum(n=2, lookback=pd.DateOffset(months=12)),
bt.algos.WeighEqually(),
bt.algos.Rebalance()
])
results = bt.run(
bt.Backtest(momentum_strategy, data),
bt.Backtest(dual_momentum, data)
)
results.display()

Risk Parity Strategy

Allocate based on risk contribution

Python
import bt
import pandas as pd
# Risk Parity - Equal risk contribution from each asset
risk_parity = bt.Strategy('Risk Parity', [
bt.algos.RunMonthly(),
bt.algos.SelectAll(),
bt.algos.WeighERC(lookback=pd.DateOffset(months=6)), # Equal Risk Contribution
bt.algos.Rebalance()
])
# Target Volatility - Scale exposure to target vol
class TargetVolatility(bt.Algo):
def __init__(self, target_vol=0.1, lookback=60):
self.target_vol = target_vol
self.lookback = lookback
def __call__(self, target):
if target.positions.shape[0] < self.lookback:
return True
# Calculate realized volatility
returns = target.universe.pct_change().iloc[-self.lookback:]
weights = target.temp.get('weights', {})
if not weights:
return True
# Scale weights to target volatility
portfolio_vol = returns.std().mean() * np.sqrt(252)
scale = self.target_vol / portfolio_vol if portfolio_vol > 0 else 1
scale = min(scale, 2) # Cap leverage at 2x
target.temp['weights'] = {k: v * scale for k, v in weights.items()}
return True
target_vol = bt.Strategy('Target Volatility 10%', [
bt.algos.RunMonthly(),
bt.algos.SelectAll(),
bt.algos.WeighEqually(),
TargetVolatility(target_vol=0.10),
bt.algos.Rebalance()
])
results = bt.run(
bt.Backtest(risk_parity, data),
bt.Backtest(target_vol, data)
)
results.plot_security_weights()

Performance Analysis

Analyze detailed backtest metrics

Python
import bt
# After running backtest
result = bt.run(backtest)
# Get performance statistics
stats = result.stats
print(stats)
# Key metrics
print(f"Total Return: {result.stats.loc['total_return'].values[0]:.2%}")
print(f"CAGR: {result.stats.loc['cagr'].values[0]:.2%}")
print(f"Max Drawdown: {result.stats.loc['max_drawdown'].values[0]:.2%}")
print(f"Sharpe Ratio: {result.stats.loc['daily_sharpe'].values[0]:.2f}")
print(f"Calmar Ratio: {result.stats.loc['calmar'].values[0]:.2f}")
# Monthly returns heatmap
result.plot_histogram()
# Drawdown plot
result.plot_drawdown()
# Get transaction log
transactions = result.get_transactions()
print(f"\nTotal trades: {len(transactions)}")
print(transactions.tail())
# Get weights over time
weights = result.get_security_weights()
weights.plot(title='Portfolio Weights Over Time')

Create Custom Algorithm

Build your own selection/weighting algorithm

Python
import bt
import pandas as pd
import numpy as np
class SelectByRSI(bt.Algo):
"""Select assets where RSI is oversold"""
def __init__(self, period=14, oversold=30):
self.period = period
self.oversold = oversold
def __call__(self, target):
# Calculate RSI for all assets
prices = target.universe
delta = prices.diff()
gain = delta.where(delta > 0, 0).rolling(self.period).mean()
loss = (-delta.where(delta < 0, 0)).rolling(self.period).mean()
rs = gain / loss
rsi = 100 - (100 / (1 + rs))
# Select oversold assets
latest_rsi = rsi.iloc[-1]
selected = latest_rsi[latest_rsi < self.oversold].index.tolist()
target.temp['selected'] = selected if selected else target.universe.columns.tolist()
return True
class LimitPositionSize(bt.Algo):
"""Limit maximum position size"""
def __init__(self, max_weight=0.25):
self.max_weight = max_weight
def __call__(self, target):
weights = target.temp.get('weights', {})
if not weights:
return True
# Cap weights
capped = {k: min(v, self.max_weight) for k, v in weights.items()}
# Renormalize
total = sum(capped.values())
if total > 0:
target.temp['weights'] = {k: v/total for k, v in capped.items()}
return True
# Use custom algorithms
custom_strategy = bt.Strategy('RSI Oversold', [
bt.algos.RunWeekly(),
SelectByRSI(period=14, oversold=30),
bt.algos.WeighEqually(),
LimitPositionSize(max_weight=0.30),
bt.algos.Rebalance()
])
result = bt.run(bt.Backtest(custom_strategy, data))
result.display()

حالات الاستخدام الشائعة

إعادة توازن المحفظة
تخصيص الأصول
اختبار المعايير
استراتيجيات المحفظة
تحسين الأوزان
تحليل العوائد
مقارنة الاستراتيجيات
تتبع المؤشرات
إدارة المخاطر
محاكاة المحفظة

Best Practices & Common Pitfalls

أهداف التخصيص

حدد أوزان الأصول بناءً على تحليل دقيق وليس عشوائياً.

فترة إعادة التوازن

اختبر فترات مختلفة لإعادة التوازن (شهري، ربع سنوي).

تكاليف المعاملات

أدخل تكاليف المعاملات الحقيقية في اختباراتك.

التقييم الأولي

لا تُفرط في تحسين المعلمات — استخدم بيانات خارج العينة.

الحالات الحدية

اختبر استراتيجيتك في أسواق صاعدة وهابطة وجانبية.

التوثيق

وثّق منطق كل عقدة قرارية لسهولة الصيانة.

موارد إضافية

bt vs Other Frameworks

  • bt: Portfolio-focused, tree structure
  • Backtrader: Event-driven, feature-rich
  • vectorbt: Fastest, vectorized

الخطوات التالية

Explore other backtesting frameworks or add live trading capabilities:

PipsGrowth - مراجعات خبراء الوسطاء، استراتيجيات التداول والأدوات