Auto-discovered strategy
Symbol: SOL | Exchange: Binance | Role: vol_control
Click a period to view chart
| Period | Return | Win Rate | Trades | Max DD | Sharpe |
|---|---|---|---|---|---|
| 2020 | -1.3% | 53.6% | 140 | 2.8% | -0.64 |
| 2021 | +4.0% | 60.1% | 358 | 2.6% | 1.23 |
| 2022 | +0.1% | 56.4% | 358 | 2.6% | 0.04 |
| 2023 | -0.5% | 53.5% | 357 | 2.6% | -0.17 |
| 2024 | +4.1% | 59.2% | 358 | 1.8% | 1.33 |
| 2025 | -1.3% | 54.5% | 358 | 3.2% | -0.44 |
| Window | Train Period | Val Period | Val Return | Val | Test Period | Test Return | Status |
|---|---|---|---|---|---|---|---|
| WF-1 | 2025-07→2025-11 | 2025-12→2025-12 | +0.3% | OK | 2026-01→ongoing | +0.0% | PASS |
"""
Vol Control Strategy: ATR Regime Filter for SOLUSDT
=====================================================
Role: VOL_CONTROL - "Only trade when volatility is favorable"
Concept:
- Compute ATR z-score to classify volatility regime
- Trade VWAP reversion ONLY when volatility is in "calm" zone
- Hard disable on volatility spikes (ATR z > 2)
- Very conservative position management
WHY THIS WORKS on 1-minute:
- SOL is more volatile than BTC - needs stricter vol control
- VWAP is reliable anchor for mean reversion
- Calm vol = reliable fills, predictable moves
- Avoid the chaos where slippage eats the edge
Entry (strict conditions):
1. ATR z-score between -0.5 and 0.5 (CALM volatility only)
2. Price deviates from VWAP by at least 0.15% (SOL needs wider threshold)
3. RSI confirmation (not overbought/oversold in wrong direction)
4. Minimum cooldown between trades
Exit:
- Price returns toward VWAP (within 0.05%)
- OR max hold time (15 bars / 15 minutes)
- OR volatility spikes (z > 1.5) - early exit
- OR stop loss (0.20%)
Vol Control requirements: sharpe >= 0.2, DD < 12%, trades >= 10, loss < -3%
"""
import sys
sys.path.insert(0, '/root/trade_1m')
from lib import atr, vwap, rsi
def init_strategy():
return {
'name': 'vol_control_sol_atr_regime',
'role': 'vol_control', # CRITICAL: vol_control role
'warmup': 240, # 4 hours warmup for stats
'subscriptions': [
{'symbol': 'SOLUSDT', 'exchange': 'binance', 'timeframe': '1m'},
],
'parameters': {
# ATR parameters for vol regime detection
'atr_period': 30, # 30 min ATR
'atr_lookback': 120, # 2 hour lookback for z-score
# Volatility gates - trade in LOW vol only
'vol_entry_max': -0.5, # Only trade when vol is below average
'vol_spike_exit': 1.0, # Exit on vol rise
'vol_hard_disable': 2.0, # NO trades above this
# Price momentum filter
'price_change_period': 5, # 5-bar price change
'price_entry_min': 0.10, # Need 0.10% move to trigger
'price_entry_max': 0.40, # Not more than 0.40%
# Trade management
'max_hold_bars': 10, # Max 10 minutes - quick exit
'take_profit_pct': 0.15, # 0.15% take profit
'stop_loss_pct': 0.20, # 0.20% stop
'cooldown_bars': 1440, # 24 hours cooldown (working config)
}
}
def process_time_step(ctx):
"""
Vol control strategy: Mean reversion in low volatility conditions.
Key principle: In low vol, prices tend to revert. Fade small moves.
CRITICAL: No trades when ATR z-score > 2 (hard disable)
"""
key = ('SOLUSDT', 'binance')
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_exit']
vol_hard = params['vol_hard_disable']
price_change_period = params['price_change_period']
price_entry_min = params['price_entry_min']
price_entry_max = params['price_entry_max']
max_hold = params['max_hold_bars']
cooldown = params['cooldown_bars']
# Initialize state
if 'last_trade_bar' not in state:
state['last_trade_bar'] = -cooldown
# Check minimum data
if i < atr_lookback + atr_period:
return []
# Lazy-compute ATR values
if 'atr_values' not in state:
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)
atr_values = state['atr_values']
current_atr = atr_values[i] if i < len(atr_values) and atr_values[i] is not None else None
if current_atr is None:
return []
# Compute 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
# === HARD VOL DISABLE ===
if atr_zscore > vol_hard:
if key in positions:
pos = positions[key]
state['last_trade_bar'] = i
action_type = 'close_long' if pos.side == 'long' else 'close_short'
return [{
'action': action_type,
'symbol': 'SOLUSDT',
'exchange': 'binance',
}]
return []
# Compute recent price change
if i < price_change_period:
return []
price_now = bars[i].close
price_before = bars[i - price_change_period].close
price_change_pct = (price_now - price_before) / price_before * 100
actions = []
# === VOL SPIKE EXIT ===
if atr_zscore > vol_spike:
if key in positions:
pos = positions[key]
state['last_trade_bar'] = i
action_type = 'close_long' if pos.side == 'long' else 'close_short'
return [{
'action': action_type,
'symbol': 'SOLUSDT',
'exchange': 'binance',
}]
return []
# === 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 spikes - exit immediately
if atr_zscore > vol_spike:
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': 'SOLUSDT',
'exchange': 'binance',
})
return actions
# === ENTRY LOGIC ===
# Check cooldown
if i - state['last_trade_bar'] < cooldown:
return []
# Check volatility is LOW (below average)
if atr_zscore > vol_entry_max:
return []
# Fade recent move if it's in the "sweet spot" (not too small, not too big)
# Long: price dropped moderately, fade the drop
if price_change_pct < -price_entry_min and price_change_pct > -price_entry_max:
state['last_trade_bar'] = i
actions.append({
'action': 'open_long',
'symbol': 'SOLUSDT',
'exchange': 'binance',
'size': 1.0,
'take_profit_pct': params['take_profit_pct'],
'stop_loss_pct': params['stop_loss_pct'],
})
# Short: price rose moderately, fade the rise
elif price_change_pct > price_entry_min and price_change_pct < price_entry_max:
state['last_trade_bar'] = i
actions.append({
'action': 'open_short',
'symbol': 'SOLUSDT',
'exchange': 'binance',
'size': 1.0,
'take_profit_pct': params['take_profit_pct'],
'stop_loss_pct': params['stop_loss_pct'],
})
return actions
if __name__ == '__main__':
from strategy import backtest_strategy, run_strategy
from lib import calc_metrics, Trade
print("\n" + "="*60)
print("Testing: vol_control_sol_atr_regime")
print("Role: vol_control")
print("Symbol: SOLUSDT @ binance")
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" Monthly returns: {[f'{r:.1f}' for r in monthly_returns]}")
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'}")