Defensive Strategy: Ultra-Low Activity Filter ============================================== Role: DEFENSIVE - "Capital preservation first" Concept - TRULY DEFENSIVE: This strategy is designed to be a "do nothing most of the time" filter. It only trades under extremely rare conditions where: 1. Volatility is at multi-day lows (ATR z-score < -2) 2. Very strong volume surge (3x average) 3. Clear directional bias from prior bars 4. Time-based exit to limit exposure The goal is NOT to make money
Symbol: BTC | Exchange: Bitfinex | Role: defensive
Click a period to view chart
| Period | Return | Win Rate | Trades | Max DD | Sharpe |
|---|---|---|---|---|---|
| 2025-07 | -0.2% | 50.0% | 2 | 0.3% | -0.47 |
| 2025-08 | +0.4% | 75.0% | 4 | 0.0% | 2.04 |
| 2025-09 | +0.1% | 33.3% | 3 | 0.1% | 0.58 |
| 2025-10 | -0.5% | 50.0% | 4 | 0.8% | -1.07 |
| 2025-11 | +2.3% | 60.0% | 10 | 0.4% | 1.59 |
| 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% | OK | 2026-01→ongoing | +0.0% | PASS |
#!/usr/bin/env python3
"""
Defensive Strategy: Ultra-Low Activity Filter
==============================================
Role: DEFENSIVE - "Capital preservation first"
Concept - TRULY DEFENSIVE:
This strategy is designed to be a "do nothing most of the time" filter.
It only trades under extremely rare conditions where:
1. Volatility is at multi-day lows (ATR z-score < -2)
2. Very strong volume surge (3x average)
3. Clear directional bias from prior bars
4. Time-based exit to limit exposure
The goal is NOT to make money - it's to NOT LOSE money.
A flat equity curve is success for this strategy.
Design philosophy:
- Trade count target: ~20-30 over 5 months (very few)
- Focus on avoiding bad trades
- Preserving capital is the primary objective
Defensive requirements: sharpe >= 0, DD < 10%, must not lose
"""
import sys
sys.path.insert(0, '/root/trade_1m')
from lib import load_data, ema, sma, rsi, atr
from strategy import backtest_strategy, run_strategy
def init_strategy():
return {
'name': 'defensive_vol_calm',
'role': 'defensive', # CRITICAL: defensive role
'warmup': 300, # 5 hours warmup
'subscriptions': [
{'symbol': 'tBTCUSD', 'exchange': 'bitfinex', 'timeframe': '1m'},
],
'parameters': {
'atr_period': 20, # ATR period
'atr_lookback': 120, # 2 hours lookback for stats
'extreme_calm_threshold': -2.0, # EXTREME calm only (2 std below mean)
'volume_surge': 3.0, # Volume must be 3x average (stricter)
'min_bars_between': 360, # 6 hours between trades
'hold_bars': 15, # 15 min hold
'stop_loss_pct': 0.4, # Tight stop
'consecutive_bars': 3, # Need 3 bars in same direction
}
}
def process_time_step(ctx):
"""
Ultra-defensive trading: mostly flat, very rare entries.
Entry requires ALL of:
1. ATR z-score < -2.0 (extreme calm - 2 std below mean)
2. Volume surge 3x average
3. 3 consecutive bars in same direction
4. 6+ hours since last trade
Exit:
- Time-based (15 bars)
- OR stop loss hit (0.4%)
"""
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']
extreme_calm_threshold = params['extreme_calm_threshold']
volume_surge = params['volume_surge']
min_bars_between = params['min_bars_between']
hold_bars = params['hold_bars']
consecutive_bars = params['consecutive_bars']
if i < atr_lookback + atr_period + consecutive_bars:
return []
# Lazy-compute indicators
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]
volumes = [b.volume for b in bars]
state['atr_values'] = atr(highs, lows, closes, atr_period)
state['volumes'] = volumes
state['closes'] = closes
state['last_trade_bar'] = -min_bars_between
atr_values = state['atr_values']
volumes = state['volumes']
closes = state['closes']
current_atr = atr_values[i]
if current_atr is None:
return []
# ATR z-score with longer lookback
recent_atrs = [atr_values[j] for j in range(max(0, i - atr_lookback), i) if 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
# Volume ratio
recent_volumes = volumes[max(0, i-60):i]
avg_volume = sum(recent_volumes) / len(recent_volumes) if recent_volumes else 1
current_volume = volumes[i]
vol_ratio = current_volume / avg_volume if avg_volume > 0 else 0
actions = []
if key in positions:
pos = positions[key]
bars_held = i - pos.entry_bar
# Time-based exit only
if bars_held >= hold_bars:
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',
})
else:
# Check cooldown (6 hours)
bars_since_last = i - state.get('last_trade_bar', -min_bars_between)
if bars_since_last < min_bars_between:
return []
# ULTRA-STRICT entry conditions
extreme_calm = atr_zscore <= extreme_calm_threshold
high_volume = vol_ratio >= volume_surge
if extreme_calm and high_volume:
# Check for consecutive bars in same direction
bullish_count = 0
bearish_count = 0
for j in range(i - consecutive_bars + 1, i + 1):
if closes[j] > closes[j-1]:
bullish_count += 1
elif closes[j] < closes[j-1]:
bearish_count += 1
# Need ALL consecutive bars in same direction
if bullish_count == consecutive_bars:
state['last_trade_bar'] = i
actions.append({
'action': 'open_long',
'symbol': 'tBTCUSD',
'exchange': 'bitfinex',
'size': 1.0,
'stop_loss_pct': params['stop_loss_pct'],
})
elif bearish_count == consecutive_bars:
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: defensive_vol_calm (Ultra-Low Activity)")
print("Role: defensive")
print("="*60)
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 = []
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)
metrics = calc_metrics(std_trades)
month = start[5:7]
ret = metrics.get('return', 0)
tr = metrics.get('trades', 0)
wr = metrics.get('win_rate', 0)
print(f" 2025-{month}: {ret:+.2f}% | {tr} trades | WR: {wr:.0f}%")
overall = calc_metrics(all_trades)
print(f"\n {'='*50}")
print(f" TOTAL: {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}%")
# Stress tests
print(f"\n STRESS TESTS:")
trades_count = overall.get('trades', 0)
SLIPPAGE = 0.05 # 5bp per side
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 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}%")
print(f"\n Defensive validation:")
print(f" - Sharpe >= 0: {'PASS' if overall.get('sharpe', 0) >= 0 else 'FAIL'}")
print(f" - DD < 10%: {'PASS' if overall.get('max_dd', 0) < 10 else 'FAIL'}")
print(f" - Minimal loss after costs: {'PASS' if after_slippage > -1 else 'FAIL'}")
print(f" (Defensive goal: near-zero, small loss acceptable)")