test: add comprehensive Hurst strategy simulations and backtest engine enhancements (v1.7.11)
- Add Hurst_simulations.md with detailed performance data from Jan 2025 to Mar 2026 across 1m-1h TFs.
- Enhance backtest_engine.py:
- Add --starting_equity for memory-efficient chunked testing.
- Add Max Drawdown tracking.
- Add --interval override flag.
- Add --brake_period/--brake_interval for safety filter testing.
This commit is contained in:
180
Hurst_simulations.md
Normal file
180
Hurst_simulations.md
Normal file
@ -0,0 +1,180 @@
|
||||
# Comprehensive Hurst Strategy Simulations (since 2025-01-01)
|
||||
Comparison of different Hurst Timeframes and Entry Filters.
|
||||
|
||||
# Timeframe: 1m
|
||||
|
||||
## Scenario: Without Filter
|
||||
| Period | Start Bal | End Equity | PnL | Trades | Max DD |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| 2025-01-01 to 2025-03-31 | $1000.00 | $3192.86 | $2192.86 | 13239 | 7.40% |
|
||||
| 2025-04-01 to 2025-06-30 | $3192.86 | $5665.00 | $2472.14 | 13242 | 3.39% |
|
||||
| 2025-07-01 to 2025-09-30 | $5665.00 | $7520.61 | $1855.61 | 13720 | 1.02% |
|
||||
| 2025-10-01 to 2025-12-31 | $7520.61 | $8891.62 | $1371.01 | 13584 | 0.56% |
|
||||
| 2026-01-01 to 2026-03-10 | $8891.62 | $10437.95 | $1546.33 | 10120 | 0.52% |
|
||||
|
||||
**Final Results for 1m (Without Filter):**
|
||||
- Final Equity: **$10437.95**
|
||||
- Total ROI: **943.80%**
|
||||
- Total Trades: **63905**
|
||||
- Max Overall Drawdown: **7.40%**
|
||||
|
||||
## Scenario: With 1H SMA 200 Filter
|
||||
| Period | Start Bal | End Equity | PnL | Trades | Max DD |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| 2025-01-01 to 2025-03-31 | $1000.00 | $2124.83 | $1124.83 | 10469 | 7.57% |
|
||||
| 2025-04-01 to 2025-06-30 | $2124.83 | $3729.53 | $1604.70 | 11016 | 5.09% |
|
||||
| 2025-07-01 to 2025-09-30 | $3729.53 | $4673.10 | $943.57 | 10923 | 2.25% |
|
||||
| 2025-10-01 to 2025-12-31 | $4673.10 | $5428.01 | $754.91 | 11319 | 1.49% |
|
||||
| 2026-01-01 to 2026-03-10 | $5428.01 | $6307.05 | $879.04 | 8485 | 1.24% |
|
||||
|
||||
**Final Results for 1m (With 1H SMA 200 Filter):**
|
||||
- Final Equity: **$6307.05**
|
||||
- Total ROI: **530.71%**
|
||||
- Total Trades: **52212**
|
||||
- Max Overall Drawdown: **7.57%**
|
||||
|
||||
# Timeframe: 3m
|
||||
|
||||
## Scenario: Without Filter
|
||||
| Period | Start Bal | End Equity | PnL | Trades | Max DD |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| 2025-01-01 to 2025-03-31 | $1000.00 | $2309.37 | $1309.37 | 4233 | 8.10% |
|
||||
| 2025-04-01 to 2025-06-30 | $2309.37 | $3801.67 | $1492.30 | 4331 | 2.07% |
|
||||
| 2025-07-01 to 2025-09-30 | $3801.67 | $4988.92 | $1187.25 | 4513 | 1.60% |
|
||||
| 2025-10-01 to 2025-12-31 | $4988.92 | $5912.62 | $923.70 | 4370 | 1.05% |
|
||||
| 2026-01-01 to 2026-03-10 | $5912.62 | $6740.90 | $828.28 | 3306 | 1.01% |
|
||||
|
||||
**Final Results for 3m (Without Filter):**
|
||||
- Final Equity: **$6740.90**
|
||||
- Total ROI: **574.09%**
|
||||
- Total Trades: **20753**
|
||||
- Max Overall Drawdown: **8.10%**
|
||||
|
||||
## Scenario: With 1H SMA 200 Filter
|
||||
| Period | Start Bal | End Equity | PnL | Trades | Max DD |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| 2025-01-01 to 2025-03-31 | $1000.00 | $1470.40 | $470.40 | 3288 | 10.19% |
|
||||
| 2025-04-01 to 2025-06-30 | $1470.40 | $2366.47 | $896.07 | 3597 | 4.96% |
|
||||
| 2025-07-01 to 2025-09-30 | $2366.47 | $2844.79 | $478.32 | 3478 | 3.23% |
|
||||
| 2025-10-01 to 2025-12-31 | $2844.79 | $3250.87 | $406.08 | 3597 | 3.43% |
|
||||
| 2026-01-01 to 2026-03-10 | $3250.87 | $3643.24 | $392.37 | 2739 | 1.90% |
|
||||
|
||||
**Final Results for 3m (With 1H SMA 200 Filter):**
|
||||
- Final Equity: **$3643.24**
|
||||
- Total ROI: **264.32%**
|
||||
- Total Trades: **16699**
|
||||
- Max Overall Drawdown: **10.19%**
|
||||
|
||||
# Timeframe: 5m
|
||||
|
||||
## Scenario: Without Filter
|
||||
| Period | Start Bal | End Equity | PnL | Trades | Max DD |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| 2025-01-01 to 2025-03-31 | $1000.00 | $1878.15 | $878.15 | 2482 | 14.06% |
|
||||
| 2025-04-01 to 2025-06-30 | $1878.15 | $3053.49 | $1175.34 | 2601 | 3.26% |
|
||||
| 2025-07-01 to 2025-09-30 | $3053.49 | $3944.73 | $891.24 | 2689 | 2.08% |
|
||||
| 2025-10-01 to 2025-12-31 | $3944.73 | $4578.13 | $633.40 | 2491 | 1.33% |
|
||||
| 2026-01-01 to 2026-03-10 | $4578.13 | $5122.25 | $544.12 | 1966 | 1.39% |
|
||||
|
||||
**Final Results for 5m (Without Filter):**
|
||||
- Final Equity: **$5122.25**
|
||||
- Total ROI: **412.23%**
|
||||
- Total Trades: **12229**
|
||||
- Max Overall Drawdown: **14.06%**
|
||||
|
||||
## Scenario: With 1H SMA 200 Filter
|
||||
| Period | Start Bal | End Equity | PnL | Trades | Max DD |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| 2025-01-01 to 2025-03-31 | $1000.00 | $1304.55 | $304.55 | 1915 | 14.36% |
|
||||
| 2025-04-01 to 2025-06-30 | $1304.55 | $1926.70 | $622.15 | 2134 | 7.03% |
|
||||
| 2025-07-01 to 2025-09-30 | $1926.70 | $2253.45 | $326.75 | 2052 | 4.25% |
|
||||
| 2025-10-01 to 2025-12-31 | $2253.45 | $2539.78 | $286.33 | 2038 | 2.83% |
|
||||
| 2026-01-01 to 2026-03-10 | $2539.78 | $2774.98 | $235.20 | 1583 | 2.75% |
|
||||
|
||||
**Final Results for 5m (With 1H SMA 200 Filter):**
|
||||
- Final Equity: **$2774.98**
|
||||
- Total ROI: **177.50%**
|
||||
- Total Trades: **9722**
|
||||
- Max Overall Drawdown: **14.36%**
|
||||
|
||||
# Timeframe: 15m
|
||||
|
||||
## Scenario: Without Filter
|
||||
| Period | Start Bal | End Equity | PnL | Trades | Max DD |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| 2025-01-01 to 2025-03-31 | $1000.00 | $1373.61 | $373.61 | 785 | 12.40% |
|
||||
| 2025-04-01 to 2025-06-30 | $1373.61 | $1824.22 | $450.61 | 725 | 6.62% |
|
||||
| 2025-07-01 to 2025-09-30 | $1824.22 | $2212.35 | $388.13 | 807 | 2.60% |
|
||||
| 2025-10-01 to 2025-12-31 | $2212.35 | $2535.13 | $322.78 | 765 | 3.14% |
|
||||
| 2026-01-01 to 2026-03-10 | $2535.13 | $2821.05 | $285.92 | 607 | 1.92% |
|
||||
|
||||
**Final Results for 15m (Without Filter):**
|
||||
- Final Equity: **$2821.05**
|
||||
- Total ROI: **182.11%**
|
||||
- Total Trades: **3689**
|
||||
- Max Overall Drawdown: **12.40%**
|
||||
|
||||
## Scenario: With 1H SMA 200 Filter
|
||||
| Period | Start Bal | End Equity | PnL | Trades | Max DD |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| 2025-01-01 to 2025-03-31 | $1000.00 | $993.20 | $-6.80 | 578 | 20.65% |
|
||||
| 2025-04-01 to 2025-06-30 | $993.20 | $1127.20 | $134.00 | 559 | 10.21% |
|
||||
| 2025-07-01 to 2025-09-30 | $1127.20 | $1328.46 | $201.26 | 595 | 2.96% |
|
||||
| 2025-10-01 to 2025-12-31 | $1328.46 | $1394.87 | $66.41 | 606 | 5.36% |
|
||||
| 2026-01-01 to 2026-03-10 | $1394.87 | $1379.42 | $-15.45 | 455 | 7.15% |
|
||||
|
||||
**Final Results for 15m (With 1H SMA 200 Filter):**
|
||||
- Final Equity: **$1379.42**
|
||||
- Total ROI: **37.94%**
|
||||
- Total Trades: **2793**
|
||||
- Max Overall Drawdown: **20.65%**
|
||||
|
||||
# Timeframe: 37m
|
||||
|
||||
## Scenario: Without Filter
|
||||
| Period | Start Bal | End Equity | PnL | Trades | Max DD |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| 2025-01-01 to 2025-03-31 | $1000.00 | $1120.15 | $120.15 | 293 | 10.05% |
|
||||
| 2025-04-01 to 2025-06-30 | $1120.15 | $1487.65 | $367.50 | 282 | 6.98% |
|
||||
| 2025-07-01 to 2025-09-30 | $1487.65 | $1520.11 | $32.46 | 289 | 4.01% |
|
||||
| 2025-10-01 to 2025-12-31 | $1520.11 | $1575.24 | $55.13 | 292 | 9.73% |
|
||||
| 2026-01-01 to 2026-03-10 | $1575.24 | $1748.85 | $173.61 | 246 | 3.27% |
|
||||
|
||||
**Final Results for 37m (Without Filter):**
|
||||
- Final Equity: **$1748.85**
|
||||
- Total ROI: **74.88%**
|
||||
- Total Trades: **1402**
|
||||
- Max Overall Drawdown: **10.05%**
|
||||
|
||||
## Scenario: With 1H SMA 200 Filter
|
||||
| Period | Start Bal | End Equity | PnL | Trades | Max DD |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| 2025-01-01 to 2025-03-31 | $1000.00 | $1003.56 | $3.56 | 206 | 10.69% |
|
||||
| 2025-04-01 to 2025-06-30 | $1003.56 | $1130.86 | $127.30 | 208 | 8.98% |
|
||||
| 2025-07-01 to 2025-09-30 | $1130.86 | $1154.90 | $24.04 | 202 | 2.81% |
|
||||
| 2025-10-01 to 2025-12-31 | $1154.90 | $1108.85 | $-46.05 | 224 | 9.50% |
|
||||
| 2026-01-01 to 2026-03-10 | $1108.85 | $1117.52 | $8.67 | 179 | 4.01% |
|
||||
|
||||
**Final Results for 37m (With 1H SMA 200 Filter):**
|
||||
- Final Equity: **$1117.52**
|
||||
- Total ROI: **11.75%**
|
||||
- Total Trades: **1019**
|
||||
- Max Overall Drawdown: **10.69%**
|
||||
|
||||
# Timeframe: 1h
|
||||
|
||||
## Scenario: Without Filter
|
||||
| Period | Start Bal | End Equity | PnL | Trades | Max DD |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| 2025-01-01 to 2025-03-31 | $1000.00 | $1109.99 | $109.99 | 162 | 7.36% |
|
||||
| 2025-04-01 to 2025-06-30 | $1109.99 | $1305.05 | $195.06 | 145 | 7.26% |
|
||||
| 2025-07-01 to 2025-09-30 | $1305.05 | $1342.48 | $37.43 | 180 | 2.83% |
|
||||
| 2025-10-01 to 2025-12-31 | $1342.48 | $1448.42 | $105.94 | 166 | 6.25% |
|
||||
| 2026-01-01 to 2026-03-10 | $1448.42 | $1552.57 | $104.15 | 135 | 4.56% |
|
||||
|
||||
**Final Results for 1h (Without Filter):**
|
||||
- Final Equity: **$1552.57**
|
||||
- Total ROI: **55.26%**
|
||||
- Total Trades: **788**
|
||||
- Max Overall Drawdown: **7.36%**
|
||||
|
||||
@ -13,8 +13,8 @@ from ping_pong_bot import PingPongStrategy
|
||||
load_dotenv()
|
||||
|
||||
class BacktestEngine:
|
||||
def __init__(self, config_path="config/ping_pong_config.yaml"):
|
||||
self.version = "1.7.9"
|
||||
def __init__(self, config_path="config/ping_pong_config.yaml", starting_equity=1000.0):
|
||||
self.version = "1.7.11"
|
||||
with open(config_path, 'r') as f:
|
||||
self.config = yaml.safe_load(f)
|
||||
|
||||
@ -23,12 +23,17 @@ class BacktestEngine:
|
||||
self.strategy.direction = self.direction
|
||||
|
||||
# Virtual Exchange State
|
||||
self.balance = 1000.0 # Starting USD
|
||||
self.equity = 1000.0
|
||||
self.start_equity = starting_equity
|
||||
self.balance = starting_equity
|
||||
self.equity = starting_equity
|
||||
self.position_size = 0.0 # BTC
|
||||
self.position_value = 0.0 # USD
|
||||
self.entry_price = 0.0
|
||||
|
||||
# Performance Tracking
|
||||
self.max_equity = starting_equity
|
||||
self.max_drawdown = 0.0
|
||||
|
||||
# Settings
|
||||
self.fee_rate = 0.0005 # 0.05% Taker
|
||||
self.leverage = 5.0 # Will be updated based on mode
|
||||
@ -39,6 +44,9 @@ class BacktestEngine:
|
||||
self.stop_loss_pct = 0.0 # 0.0 = Disabled
|
||||
self.stop_on_hurst_break = False
|
||||
|
||||
# Safety Brake Settings
|
||||
self.use_brake = False
|
||||
|
||||
self.trades = []
|
||||
self.equity_curve = []
|
||||
|
||||
@ -75,7 +83,7 @@ class BacktestEngine:
|
||||
df[['open', 'high', 'low', 'close', 'volume']] = df[['open', 'high', 'low', 'close', 'volume']].astype(float)
|
||||
return df
|
||||
|
||||
def run(self, df, ma_df=None, ma_period=None):
|
||||
def run(self, df, ma_df=None, ma_period=None, brake_df=None, brake_period=None):
|
||||
if df.empty:
|
||||
print("No data to run backtest.")
|
||||
return
|
||||
@ -95,6 +103,17 @@ class BacktestEngine:
|
||||
ma_values = df['ma'].values
|
||||
print(f"Regime Switching enabled (MA {ma_period})")
|
||||
|
||||
# Prepare Brake MA
|
||||
brake_values = None
|
||||
if brake_df is not None and brake_period:
|
||||
brake_df['brake_ma'] = brake_df['close'].rolling(window=brake_period).mean()
|
||||
brake_subset = brake_df[['time', 'brake_ma']].rename(columns={'time': 'brake_time'})
|
||||
df = pd.merge_asof(df.sort_values('time'), brake_subset.sort_values('brake_time'),
|
||||
left_on='time', right_on='brake_time', direction='backward')
|
||||
brake_values = df['brake_ma'].values
|
||||
self.use_brake = True
|
||||
print(f"Safety Brake enabled (MA {brake_period})")
|
||||
|
||||
start_idx = max(self.config['rsi']['period'], self.config['hurst']['period'], 100)
|
||||
if start_idx >= len(df):
|
||||
print(f"Error: Not enough candles. Need {start_idx}, got {len(df)}")
|
||||
@ -125,10 +144,19 @@ class BacktestEngine:
|
||||
signal = None
|
||||
|
||||
if signal == "open":
|
||||
self.open_position(price, time)
|
||||
# Apply Safety Brake
|
||||
if self.use_brake and brake_values is not None and not np.isnan(brake_values[i]):
|
||||
if self.direction == "short" and price > brake_values[i]:
|
||||
signal = None # Brake: Don't short in uptrend
|
||||
elif self.direction == "long" and price < brake_values[i]:
|
||||
signal = None # Brake: Don't long in downtrend
|
||||
|
||||
if signal == "open":
|
||||
self.open_position(price, time)
|
||||
elif signal == "close" and abs(self.position_size) > 0:
|
||||
self.close_partial_position(price, time)
|
||||
|
||||
# Mark to Market Equity
|
||||
unrealized = 0
|
||||
if self.direction == "long":
|
||||
unrealized = self.position_size * (price - self.entry_price) if self.position_size > 0 else 0
|
||||
@ -136,6 +164,15 @@ class BacktestEngine:
|
||||
unrealized = abs(self.position_size) * (self.entry_price - price) if self.position_size < 0 else 0
|
||||
|
||||
self.equity = self.balance + unrealized
|
||||
|
||||
# Max Drawdown Tracking
|
||||
if self.equity > self.max_equity:
|
||||
self.max_equity = self.equity
|
||||
|
||||
dd = (self.max_equity - self.equity) / self.max_equity
|
||||
if dd > self.max_drawdown:
|
||||
self.max_drawdown = dd
|
||||
|
||||
self.equity_curve.append({"time": time, "equity": self.equity})
|
||||
|
||||
self.print_results()
|
||||
@ -186,8 +223,8 @@ class BacktestEngine:
|
||||
self.trades.append({"time": time, "type": reason, "price": price, "pnl": pnl, "fee": fee})
|
||||
|
||||
def print_results(self):
|
||||
total_pnl = self.equity - 1000.0
|
||||
roi = (total_pnl / 1000.0) * 100
|
||||
total_pnl = self.equity - self.start_equity
|
||||
roi = (total_pnl / self.start_equity) * 100
|
||||
fees = sum(t['fee'] for t in self.trades)
|
||||
sl_hits = len([t for t in self.trades if "Stop Loss" in t['type']])
|
||||
print("\n" + "="*30)
|
||||
@ -198,6 +235,7 @@ class BacktestEngine:
|
||||
print(f"Final Equity: ${self.equity:.2f}")
|
||||
print(f"Total PnL: ${total_pnl:.2f}")
|
||||
print(f"ROI: {roi:.2f}%")
|
||||
print(f"Max Drawdown: {self.max_drawdown*100:.2f}%")
|
||||
print(f"Total Fees: ${fees:.2f}")
|
||||
print("="*30)
|
||||
|
||||
@ -205,17 +243,21 @@ async def main():
|
||||
parser = argparse.ArgumentParser(description='Ping-Pong Strategy Backtester')
|
||||
parser.add_argument('--config', type=str, default='config/ping_pong_config.yaml')
|
||||
parser.add_argument('--limit', type=int, default=10000)
|
||||
parser.add_argument('--interval', type=str, help='Strategy Interval (e.g. 5m, 15m)')
|
||||
parser.add_argument('--start_date', type=str)
|
||||
parser.add_argument('--end_date', type=str)
|
||||
parser.add_argument('--ma_period', type=int)
|
||||
parser.add_argument('--ma_interval', type=str, default='1h')
|
||||
parser.add_argument('--brake_period', type=int, help='Safety Brake MA Period')
|
||||
parser.add_argument('--brake_interval', type=str, default='1h', help='Safety Brake MA Interval')
|
||||
parser.add_argument('--direction', type=str, choices=['long', 'short'])
|
||||
parser.add_argument('--stop_loss', type=float, default=0.0, help='Stop Loss % (e.g. 0.02 for 2%)')
|
||||
parser.add_argument('--hurst_stop', action='store_true', help='Enable Stop Loss on Hurst break')
|
||||
parser.add_argument('--maker_fee', type=float, help='Override fee rate for Maker simulation (e.g. 0.0002)')
|
||||
parser.add_argument('--starting_equity', type=float, default=1000.0, help='Initial balance')
|
||||
|
||||
args = parser.parse_args()
|
||||
engine = BacktestEngine(config_path=args.config)
|
||||
engine = BacktestEngine(config_path=args.config, starting_equity=args.starting_equity)
|
||||
|
||||
if args.maker_fee:
|
||||
engine.fee_rate = args.maker_fee
|
||||
@ -224,19 +266,27 @@ async def main():
|
||||
engine.stop_loss_pct = args.stop_loss
|
||||
engine.stop_on_hurst_break = args.hurst_stop
|
||||
|
||||
# Base Data
|
||||
symbol = engine.config['symbol'].replace("USDT", "").replace("USD", "")
|
||||
df = await engine.load_data(symbol, "1m", limit=args.limit, start_date=args.start_date, end_date=args.end_date)
|
||||
data_interval = args.interval if args.interval else engine.config['interval']
|
||||
if data_interval.isdigit(): data_interval += "m"
|
||||
|
||||
df = await engine.load_data(symbol, data_interval, limit=args.limit, start_date=args.start_date, end_date=args.end_date)
|
||||
if df.empty: return
|
||||
|
||||
ma_df = None
|
||||
if args.ma_period:
|
||||
ma_df = await engine.load_data(symbol, args.ma_interval, limit=5000, start_date=None, end_date=args.end_date)
|
||||
|
||||
brake_df = None
|
||||
if args.brake_period:
|
||||
brake_df = await engine.load_data(symbol, args.brake_interval, limit=5000, start_date=None, end_date=args.end_date)
|
||||
|
||||
if args.direction:
|
||||
engine.direction = args.direction
|
||||
engine.strategy.direction = args.direction
|
||||
|
||||
engine.run(df, ma_df=ma_df, ma_period=args.ma_period)
|
||||
engine.run(df, ma_df=ma_df, ma_period=args.ma_period, brake_df=brake_df, brake_period=args.brake_period)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
Reference in New Issue
Block a user