Auto-discovered strategy
Symbol: ETH | Exchange: Bitfinex | Role: breakout_fade
Click a period to view chart
| Period | Return | Win Rate | Trades | Max DD | Sharpe |
|---|---|---|---|---|---|
| 2020 | +22.8% | 59.4% | 1083 | 7.8% | 2.01 |
| 2021 | +19.8% | 58.2% | 1374 | 8.1% | 1.47 |
| 2022 | +10.9% | 58.1% | 1204 | 11.4% | 0.96 |
| 2023 | -11.5% | 52.7% | 628 | 13.3% | -1.67 |
| 2024 | -7.4% | 53.5% | 564 | 11.3% | -0.93 |
| 2025 | +2.6% | 55.7% | 673 | 11.3% | 0.30 |
| Window | Train Period | Val Period | Val Return | Val | Test Period | Test Return | Status |
|---|---|---|---|---|---|---|---|
| WF-1 | 2025-07→2025-11 | 2025-12→2025-12 | -2.1% | FAIL | 2026-01→ongoing | -0.4% | FAIL |
#!/usr/bin/env python3
"""
Breakout Fade Strategy: VWAP Extension + False Breakout
========================================================
Role: BREAKOUT_FADE - "Fade failed breakouts when price is extended from VWAP"
Concept:
- Identify micro range (30-bar high/low)
- Require price to be significantly extended from VWAP (2.5+ ATR)
- Detect false breakout (price breaks range but closes back inside)
- Fade the breakout by trading opposite direction
- Exit on VWAP reversion or time limit
WHY THIS WORKS on 1-minute:
- Most breakouts at 1m scale are noise, not real moves
- When price is already extended from VWAP, mean reversion is likely
- False breakouts + VWAP extension = double confirmation of reversal
- Volume spike confirms institutional interest in the rejection
Entry (Long fade - after false breakdown):
1. Price is 2.5+ ATR below 2h VWAP (oversold)
2. Price broke below 30-bar low (made new low)
3. Bar closed back above the low (rejection wick)
4. Volume spike (2x average)
5. ATR z-score < 1.5 (no crazy vol)
Entry (Short fade - after false breakup):
1. Price is 2.5+ ATR above 2h VWAP (overbought)
2. Price broke above 30-bar high (made new high)
3. Bar closed back below the high (rejection wick)
4. Volume spike (2x average)
5. ATR z-score < 1.5 (no crazy vol)
Exit:
- Price returns to VWAP
- Time-based (30 bars max)
- Take profit (0.35%)
- Stop loss (0.50%)
- Vol spike emergency (ATR z > 2)
Training Results (2025-07 to 2025-11):
- Total Return: +12.64%
- Trades: 334
- Sharpe: 2.23
- Max DD: 2.3%
- Win Rate: 60%
- Positive months: 4/5
IMPORTANT: This strategy has avg_trade ~0.04%, which is below the 0.05% cost floor.
After slippage costs, net return may be negative. Use at your own risk.
Breakout_fade requirements: sharpe >= 0.4, DD < 18%, trades >= 15, loss < -8%
"""
import sys
sys.path.insert(0, '/root/trade_1m')
from lib import atr, vwap
from math import sqrt
def init_strategy():
return {
'name': 'breakout_fade_eth_vwap_extreme',
'role': 'breakout_fade',
'warmup': 200, # Warmup for 2h VWAP
'subscriptions': [
{'symbol': 'tETHUSD', 'exchange': 'bitfinex', 'timeframe': '1m'},
],
'parameters': {
# Range detection
'range_period': 30, # 30-bar range
# VWAP extension
'vwap_period': 120, # 2h VWAP
'vwap_threshold': 2.5, # Must be 2.5+ ATR from VWAP
# Volume confirmation
'volume_spike': 2.0, # 2x average volume
'volume_lookback': 60, # 60-bar volume average
# ATR vol filter
'atr_period': 20,
'atr_lookback': 60,
'max_atr_zscore': 1.5, # Don't trade in vol spikes
# Trade management
'max_hold_bars': 30, # Max 30 min hold
'stop_loss_pct': 0.50, # 0.5% stop
'take_profit_pct': 0.35, # 0.35% target
# Cooldown
'cooldown_bars': 180, # 3-hour minimum between trades
}
}
def process_time_step(ctx):
"""
Breakout fade with VWAP extension filter.
Only fade breakouts when price is already significantly extended from VWAP.
This creates a double confirmation: VWAP mean reversion + breakout rejection.
"""
key = ('tETHUSD', 'bitfinex')
bars = ctx['bars'][key]
i = ctx['i']
positions = ctx['positions']
params = ctx['parameters']
state = ctx['state']
range_period = params['range_period']
vwap_period = params['vwap_period']
vwap_threshold = params['vwap_threshold']
volume_spike = params['volume_spike']
volume_lookback = params['volume_lookback']
atr_period = params['atr_period']
atr_lookback = params['atr_lookback']
max_zscore = params['max_atr_zscore']
max_hold = params['max_hold_bars']
cooldown = params['cooldown_bars']
# Initialize state
if 'last_trade_bar' not in state:
state['last_trade_bar'] = -cooldown
if 'atr_vals' not in state:
# Precompute ATR once at startup
all_highs = [b.high for b in bars]
all_lows = [b.low for b in bars]
all_closes = [b.close for b in bars]
state['atr_vals'] = atr(all_highs, all_lows, all_closes, atr_period)
# Need enough data
min_data = max(range_period + 1, atr_lookback + atr_period, volume_lookback, vwap_period)
if i < min_data:
return []
bar = bars[i]
# Compute range (prior bars, excluding current)
range_high = max(bars[j].high for j in range(i - range_period, i))
range_low = min(bars[j].low for j in range(i - range_period, i))
# Get precomputed ATR
atr_values = state['atr_vals']
current_atr = atr_values[i] if i < len(atr_values) and atr_values[i] is not None else None
if current_atr is None or current_atr < 0.01:
return []
# ATR z-score calculation
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) < 20:
return []
atr_mean = sum(recent_atrs) / len(recent_atrs)
atr_var = sum((x - atr_mean) ** 2 for x in recent_atrs) / len(recent_atrs)
atr_std = sqrt(atr_var) if atr_var > 0 else 0.0001
atr_zscore = (current_atr - atr_mean) / atr_std
# Compute volume ratio
volumes = [bars[j].volume for j in range(max(0, i - volume_lookback), i)]
avg_volume = sum(volumes) / len(volumes) if volumes else 1
vol_ratio = bar.volume / avg_volume if avg_volume > 0 else 0
# Compute VWAP
vwap_val = vwap(bars, vwap_period, i)
if vwap_val is None:
return []
# VWAP deviation in ATR units
vwap_dev = (bar.close - vwap_val) / current_atr
actions = []
# === 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
# VWAP reversion exit (target reached)
if pos.side == 'long' and bar.close >= vwap_val:
should_exit = True
elif pos.side == 'short' and bar.close <= vwap_val:
should_exit = True
# Vol spike emergency exit
if atr_zscore > 2.0:
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': 'tETHUSD',
'exchange': 'bitfinex',
})
return actions
# === ENTRY LOGIC ===
# Check cooldown
if i - state['last_trade_bar'] < cooldown:
return []
# No trades in vol spikes
if atr_zscore > max_zscore:
return []
# Detect false breakout patterns
false_breakup = (bar.high > range_high and bar.close < range_high)
false_breakdown = (bar.low < range_low and bar.close > range_low)
# Volume confirmation
has_volume = vol_ratio >= volume_spike
# VWAP extension filters
extended_below_vwap = vwap_dev < -vwap_threshold
extended_above_vwap = vwap_dev > vwap_threshold
# Long: false breakdown + extended below VWAP (strong mean reversion setup)
if false_breakdown and has_volume and extended_below_vwap:
state['last_trade_bar'] = i
actions.append({
'action': 'open_long',
'symbol': 'tETHUSD',
'exchange': 'bitfinex',
'size': 1.0,
'stop_loss_pct': params['stop_loss_pct'],
'take_profit_pct': params['take_profit_pct'],
})
# Short: false breakup + extended above VWAP
elif false_breakup and has_volume and extended_above_vwap:
state['last_trade_bar'] = i
actions.append({
'action': 'open_short',
'symbol': 'tETHUSD',
'exchange': 'bitfinex',
'size': 1.0,
'stop_loss_pct': params['stop_loss_pct'],
'take_profit_pct': params['take_profit_pct'],
})
return actions
if __name__ == '__main__':
from strategy import backtest_strategy, run_strategy
from lib import calc_metrics, Trade
print("\n" + "="*60)
print("Testing: breakout_fade_eth_vwap_extreme")
print("Role: breakout_fade")
print("Symbol: tETHUSD @ bitfinex")
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
print(f"\n MONTHLY RETURNS:")
months = ['Jul', 'Aug', 'Sep', 'Oct', 'Nov']
for idx, ret in enumerate(monthly_returns):
print(f" {months[idx]}: {ret:+.2f}%")
positive_months = sum(1 for r in monthly_returns if r > 0)
total_ret = overall.get('return', 0)
print(f"\n MONTHLY CONSISTENCY:")
print(f" Positive months: {positive_months}/5")
# Cost analysis
trades_count = overall.get('trades', 0)
avg_trade = overall.get('avg_trade', 0)
SLIPPAGE = 0.05
total_cost = trades_count * 2 * SLIPPAGE
after_slippage = total_ret - total_cost
print(f"\n COST ANALYSIS:")
print(f" Gross return: {total_ret:+.2f}%")
print(f" Slippage cost ({trades_count} trades x 0.10%): {total_cost:.2f}%")
print(f" After slippage: {after_slippage:+.2f}%")
print(f" Avg trade: {avg_trade:.3f}%")
print(f" Avg trade vs slippage: {'PASS' if avg_trade > 0.05 else 'FAIL'}")
# Validation requirements
print(f"\n BREAKOUT_FADE VALIDATION:")
sharpe_pass = overall.get('sharpe', 0) >= 0.4
dd_pass = overall.get('max_dd', 0) < 18
trades_pass = overall.get('trades', 0) >= 15
loss_pass = overall.get('return', 0) > -8
print(f" - Sharpe >= 0.4: {'PASS' if sharpe_pass else 'FAIL'} ({overall.get('sharpe', 0):.2f})")
print(f" - DD < 18%: {'PASS' if dd_pass else 'FAIL'} ({overall.get('max_dd', 0):.1f}%)")
print(f" - Trades >= 15: {'PASS' if trades_pass else 'FAIL'} ({overall.get('trades', 0)})")
print(f" - Return > -8%: {'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'}")