Auto-discovered strategy
Symbol: BTC | Exchange: Bitfinex | Role: vol_control
Click a period to view chart
| Period | Return | Win Rate | Trades | Max DD | Sharpe |
|---|---|---|---|---|---|
| 2020 | +2.8% | 44.9% | 176 | 2.0% | 1.23 |
| 2021 | +3.2% | 43.3% | 178 | 2.1% | 1.10 |
| 2022 | +0.5% | 42.9% | 177 | 2.2% | 0.25 |
| 2023 | -1.8% | 47.7% | 149 | 2.0% | -1.31 |
| 2024 | +1.0% | 49.1% | 161 | 1.6% | 0.50 |
| 2025 | +1.9% | 47.3% | 148 | 1.4% | 0.88 |
| Window | Train Period | Val Period | Val Return | Val | Test Period | Test Return | Status |
|---|---|---|---|---|---|---|---|
| WF-1 | 2025-07→2025-11 | 2025-12→2025-12 | -0.6% | FAIL | 2026-01→ongoing | +0.2% | FAIL |
#!/usr/bin/env python3
"""
Vol Control Strategy: Low Volatility Range Trade
=================================================
Role: VOL_CONTROL - "Trade range boundaries in calm markets"
Concept:
When volatility is low (ATR z-score < 0), price tends to oscillate in a range.
This strategy:
1. Detects range bounds (highest high / lowest low over N bars)
2. Trades reversions from range extremes when vol is low
3. NEVER trades when ATR z-score > 2 (vol spike disable)
WHY THIS WORKS on 1-minute:
- Low vol = price mean-reverts within a range
- Range boundaries act as support/resistance
- Quick time-based exits prevent range breakout losses
Entry:
1. ATR z-score < 0 (calm market - below average vol)
2. Price touches range extreme (highest high or lowest low of 60 bars)
3. Long cooldown between trades (quality over quantity)
Exit:
- Time-based (10 bars / 10 minutes)
- OR vol spike (ATR z > 1.5)
- OR stop loss hit
Vol Control requirements: sharpe >= 0.2, DD < 12%, trades >= 10, loss < -3%
"""
import sys
sys.path.insert(0, '/root/trade_1m')
from lib import load_data, ema, sma, rsi, atr, highest, lowest
from strategy import backtest_strategy, run_strategy
def init_strategy():
return {
'name': 'vol_control_atr_filter',
'role': 'vol_control', # CRITICAL: vol_control role
'warmup': 300, # ~5 hours warmup
'subscriptions': [
{'symbol': 'tBTCUSD', 'exchange': 'bitfinex', 'timeframe': '1m'},
],
'parameters': {
# ATR parameters
'atr_period': 20,
'atr_lookback': 120, # 2 hour lookback for z-score stats
# Volatility gates
'vol_entry_max': 0.0, # Entry when vol at or below average
'vol_spike_disable': 2.0, # HARD STOP - no trades above this
'vol_exit_high': 1.5, # Exit when vol rises
# Range parameters
'range_period': 60, # 60 bars (1 hour) for range detection
# Trade management
'max_hold_bars': 10, # Quick exit - 10 minutes max
'min_bars_between': 2880, # ~48 hours cooldown
'stop_loss_pct': 0.15, # Very tight stop (range should hold)
}
}
def process_time_step(ctx):
"""
Vol control strategy: Range reversion in calm markets.
CRITICAL: ATR z-score > 2 = NO TRADES (volatility spike disable)
Entry:
1. ATR z-score < 0 (calm market)
2. Price at range extreme (highest high or lowest low of N bars)
3. Cooldown satisfied
Exit:
- Time-based (10 bars - quick exit)
- OR vol spike (ATR z > 1.5)
- OR stop loss hit
"""
key = ('tBTCUSD', 'bitfinex')
bars = ctx['bars'][key]
i = ctx['i']
positions = ctx['positions']
params = ctx['parameters']
state = ctx['state']
atr_period = params['atr_period']
atr_lookback = params['atr_lookback']
vol_entry_max = params['vol_entry_max']
vol_spike = params['vol_spike_disable']
vol_exit = params['vol_exit_high']
range_period = params['range_period']
max_hold = params['max_hold_bars']
min_bars_between = params['min_bars_between']
# Need enough data
required = max(atr_lookback + atr_period, range_period + 10)
if i < required:
return []
# Lazy-compute indicators
if 'atr_values' not in state or len(state['atr_values']) < i + 1:
highs = [b.high for b in bars]
lows = [b.low for b in bars]
closes = [b.close for b in bars]
state['atr_values'] = atr(highs, lows, closes, atr_period)
state['highs'] = highs
state['lows'] = lows
state['closes'] = closes
state['last_trade_bar'] = -min_bars_between
atr_values = state['atr_values']
highs = state['highs']
lows = state['lows']
closes = state['closes']
# Ensure current values exist
current_atr = atr_values[i] if i < len(atr_values) else None
if current_atr is None:
return []
# Calculate ATR z-score
recent_atrs = [atr_values[j] for j in range(max(0, i - atr_lookback), i)
if j < len(atr_values) and atr_values[j] is not None]
if len(recent_atrs) < 30:
return []
atr_mean = sum(recent_atrs) / len(recent_atrs)
atr_variance = sum((x - atr_mean) ** 2 for x in recent_atrs) / len(recent_atrs)
atr_std = atr_variance ** 0.5 if atr_variance > 0 else 0.0001
atr_zscore = (current_atr - atr_mean) / atr_std
actions = []
# === HARD VOL SPIKE DISABLE ===
if atr_zscore > vol_spike:
if key in positions:
pos = positions[key]
action_type = 'close_long' if pos.side == 'long' else 'close_short'
state['last_trade_bar'] = i
return [{
'action': action_type,
'symbol': 'tBTCUSD',
'exchange': 'bitfinex',
}]
return []
# Calculate range bounds
range_high = highest(highs, range_period, i)
range_low = lowest(lows, range_period, i)
if range_high is None or range_low is None:
return []
current_high = highs[i]
current_low = lows[i]
current_close = closes[i]
# === EXIT LOGIC ===
if key in positions:
pos = positions[key]
bars_held = i - pos.entry_bar
should_exit = False
# Time-based exit
if bars_held >= max_hold:
should_exit = True
# Vol spike exit
if atr_zscore > vol_exit:
should_exit = True
if should_exit:
state['last_trade_bar'] = i
action_type = 'close_long' if pos.side == 'long' else 'close_short'
actions.append({
'action': action_type,
'symbol': 'tBTCUSD',
'exchange': 'bitfinex',
})
return actions
# === ENTRY LOGIC ===
# Check cooldown
bars_since_last = i - state.get('last_trade_bar', -min_bars_between)
if bars_since_last < min_bars_between:
return []
# Check volatility is below average (calm market)
if atr_zscore > vol_entry_max:
return []
# Check for range extreme touches
# Small tolerance for "touching" the range boundary
tolerance = (range_high - range_low) * 0.02 # 2% of range
# Long: price touched range low and bounced (close above low)
if current_low <= range_low + tolerance and current_close > range_low:
state['last_trade_bar'] = i
actions.append({
'action': 'open_long',
'symbol': 'tBTCUSD',
'exchange': 'bitfinex',
'size': 1.0,
'stop_loss_pct': params['stop_loss_pct'],
})
# Short: price touched range high and bounced (close below high)
elif current_high >= range_high - tolerance and current_close < range_high:
state['last_trade_bar'] = i
actions.append({
'action': 'open_short',
'symbol': 'tBTCUSD',
'exchange': 'bitfinex',
'size': 1.0,
'stop_loss_pct': params['stop_loss_pct'],
})
return actions
if __name__ == '__main__':
from lib import calc_metrics, Trade
print("\n" + "="*60)
print("Testing: vol_control_atr_filter")
print("Role: vol_control")
print("="*60)
# Run backtest on training data
results, profitable, _ = backtest_strategy(init_strategy, process_time_step)
# Get all trades for overall metrics
test_periods = [
('2025-07-01', '2025-07-31'),
('2025-08-01', '2025-08-31'),
('2025-09-01', '2025-09-30'),
('2025-10-01', '2025-10-31'),
('2025-11-01', '2025-11-30'),
]
all_trades = []
monthly_returns = []
for start, end in test_periods:
trades, _, _ = run_strategy(init_strategy, process_time_step, start, end)
std_trades = [
Trade(
entry_time=t.entry_time,
entry_price=t.entry_price,
exit_time=t.exit_time,
exit_price=t.exit_price,
side=t.side,
pnl_pct=t.pnl_pct
)
for t in trades
]
all_trades.extend(std_trades)
m = calc_metrics(std_trades)
monthly_returns.append(m.get('return', 0))
overall = calc_metrics(all_trades)
print(f"\n {'='*50}")
print(f" OVERALL METRICS:")
print(f" Total Return: {overall.get('return', 0):+.2f}%")
print(f" Trades: {overall.get('trades', 0)}")
print(f" Sharpe: {overall.get('sharpe', 0):.2f}")
print(f" Max DD: {overall.get('max_dd', 0):.1f}%")
print(f" Win Rate: {overall.get('win_rate', 0):.0f}%")
print(f" Avg Trade: {overall.get('avg_trade', 0):.3f}%")
# Monthly consistency check
positive_months = sum(1 for r in monthly_returns if r > 0)
total_return = overall.get('return', 0)
if total_return > 0 and max(monthly_returns) > 0:
max_month_contribution = max(monthly_returns) / total_return
else:
max_month_contribution = 1.0
print(f"\n MONTHLY CONSISTENCY:")
print(f" Positive months: {positive_months}/5")
print(f" Max month contribution: {max_month_contribution:.0%}")
# Stress tests
print(f"\n STRESS TESTS:")
trades_count = overall.get('trades', 0)
SLIPPAGE = 0.05 # 5bp per side = 0.1% round trip
total_cost = trades_count * 2 * SLIPPAGE
after_slippage = overall.get('return', 0) - total_cost
print(f" After slippage ({SLIPPAGE*2:.2f}% round trip): {after_slippage:+.2f}%")
delay_factor = 0.7 # Assume 30% edge loss from +1 bar delay
after_delay = overall.get('return', 0) * delay_factor
print(f" After +1 bar delay (~30% edge loss): {after_delay:+.2f}%")
worst_case = after_delay - total_cost
print(f" Worst case (both): {worst_case:+.2f}%")
# Vol control validation requirements
print(f"\n VOL CONTROL VALIDATION:")
sharpe_pass = overall.get('sharpe', 0) >= 0.2
dd_pass = overall.get('max_dd', 0) < 12
trades_pass = overall.get('trades', 0) >= 10
loss_pass = overall.get('return', 0) > -3
print(f" - Sharpe >= 0.2: {'PASS' if sharpe_pass else 'FAIL'} ({overall.get('sharpe', 0):.2f})")
print(f" - DD < 12%: {'PASS' if dd_pass else 'FAIL'} ({overall.get('max_dd', 0):.1f}%)")
print(f" - Trades >= 10: {'PASS' if trades_pass else 'FAIL'} ({overall.get('trades', 0)})")
print(f" - Return > -3%: {'PASS' if loss_pass else 'FAIL'} ({overall.get('return', 0):+.2f}%)")
all_pass = sharpe_pass and dd_pass and trades_pass and loss_pass
print(f"\n OVERALL: {'PASS' if all_pass else 'FAIL'}")