diegobeyl/backtesting
2
1
2from backtesting import Backtest, Strategy
3import pandas as pd
4import numpy as np
5
6class DonchianStructuralStrategy(Strategy):
7 donchian_period = 1
8 risk_percent = 1.0
9 leverage = 30
10 trailing_mode = "FAST"
11
12 def init(self):
13 # Donchian High and Low
14 self.donchian_high = self.I(lambda h: pd.Series(h).shift(1).rolling(self.donchian_period).max().values, self.data.High)
15 self.donchian_low = self.I(lambda l: pd.Series(l).shift(1).rolling(self.donchian_period).min().values, self.data.Low)
16 self.tracking_highs = []
17 self.structure_level = None
18
19 def _calculate_position_size(self, entry_price: float, stop_loss: float) -> float:
20 equity = self.equity
21 risk_amount = equity * (self.risk_percent / 100.0)
22 sl_distance = abs(entry_price - stop_loss)
23 if sl_distance <= 0: return 0.0
24 quantity = risk_amount / sl_distance
25 buying_power = equity * self.leverage
26 return max(0.0, min((quantity * entry_price) / buying_power, 0.999))
27
28 def next(self):
29 if not self.position:
30 # Simple entry for testing
31 if self.data.Close[-1] > self.donchian_high[-1]:
32 sl = self.donchian_low[-1]
33 size = self._calculate_position_size(self.data.Close[-1], sl)
34 self.buy(size=size, sl=sl)
35 elif self.data.Close[-1] < self.donchian_low[-1]:
36 sl = self.donchian_high[-1]
37 size = self._calculate_position_size(self.data.Close[-1], sl)
38 self.sell(size=size, sl=sl)
39 elif self.position.is_short and self.data.Close[-1] > self.donchian_high[-1]:
40 self.position.close()
41 elif self.position.is_long and self.data.Close[-1] < self.donchian_low[-1]:
42 self.position.close()
43
44# 1. Create real BTC data
45price_scaling_factor = 100000.0
46data = pd.DataFrame({
47 'Open': [70000, 71000, 69000, 68000, 67000, 66000, 65000, 64000]*2,
48 'High': [72000]*16,
49 'Low': [64000]*16,
50 'Close': [71000, 69000, 68000, 67000, 66000, 65000, 64000, 63000]*2
51})
52data.index = pd.date_range('2024-01-01', periods=16, freq='W')
53
54scaled_data = data / price_scaling_factor
55
56# 2. Run backtest
57bt = Backtest(scaled_data, DonchianStructuralStrategy, cash=10000, commission=0.001, margin=1/30)
58stats = bt.run()
59
60print(f"--- Full Trade Inspection ---")
61print(f"Final Equity: {stats['Equity Final [$]']:.2f}")
62
63if '_trades' in stats is not None:
64 trades = stats['_trades']
65 for i, t in trades.iterrows():
66 real_entry = t['EntryPrice'] * price_scaling_factor
67 real_exit = t['ExitPrice'] * price_scaling_factor
68 real_size = t['Size'] / price_scaling_factor
69 pnl = t['PnL']
70 ret = t['ReturnPct'] * 100
71
72 print(f"\nTrade {i} ({'LONG' if t['Size'] > 0 else 'SHORT'}):")
73 print(f" Entry: {real_entry:.2f}, Exit: {real_exit:.2f}")
74 print(f" Size: {real_size:.5f} BTC (Units: {t['Size']})")
75 print(f" PnL Result: {pnl:.2f}")
76 print(f" ReturnPct: {ret:.2f}%")
77
78 expected_raw_pnl = (real_exit - real_entry) * real_size
79 print(f" Expected Raw PnL (No Comm): {expected_raw_pnl:.2f}")
80
81 # Estimate Commission
82 comm_entry = 0.001 * t['EntryPrice'] * abs(t['Size'])
83 comm_exit = 0.001 * t['ExitPrice'] * abs(t['Size'])
84 print(f" Est. Commission: {comm_entry + comm_exit:.2f}")
85 print(f" Expected Net PnL: {expected_raw_pnl - (comm_entry + comm_exit):.2f}")
86 