Auto-discovered strategy
Symbol: SOL | Exchange: Binance | Role: momentum
Click a year to view chart
| Year | Return | Win Rate | Trades | Max DD | Sharpe |
|---|---|---|---|---|---|
| 2020 | +35.0% | 50.0% | 14 | 14.3% | 1.25 |
| 2021 | +14.2% | 34.8% | 46 | 48.7% | 0.29 |
| 2022 | +14.2% | 34.8% | 46 | 33.7% | 0.29 |
| 2023 | +24.9% | 39.4% | 33 | 25.3% | 0.61 |
| 2024 | +15.8% | 36.4% | 22 | 24.1% | 0.48 |
| 2025 | +57.7% | 50.0% | 18 | 18.5% | 1.97 |
| Window | Train Period | Val Period | Val Return | Val | Test Period | Test Return | Status |
|---|---|---|---|---|---|---|---|
| WF-1 | 2024-01→2025-06 | 2025-07→2025-12 | +30.0% | OK | 2026-01→ongoing | +0.0% | PASS |
"""
HH/HL Swing Structure Trading Strategy for SOLUSDT
This strategy identifies trend continuation patterns using swing point analysis:
- Higher High (HH) + Higher Low (HL) patterns signal bullish continuation
- Lower High (LH) + Lower Low (LL) patterns signal bearish continuation
Entry Logic:
- LONG: HH+HL pattern in bullish regime (EMA50 > EMA200), breakout above swing high
- SHORT: LH+LL pattern in bearish regime (EMA50 < EMA200), breakdown below swing low
Exit Logic:
- Close position when regime flips or price breaks EMA50
- Fixed 5% stop loss, 10% take profit (2:1 R:R)
Key Design Principles:
- Regime filter prevents trading against major trend
- Swing point detection uses 3-bar pivots (round parameter)
- 40-bar lookback for swing detection (round parameter)
- Volume confirmation (1.2x average) filters false breakouts
- No specific price levels - all relative calculations
"""
import sys
sys.path.insert(0, '/root/trade_rules')
from lib import ema, sma
def init_strategy():
return {
'name': 'hhhl_swing_structure_sol',
'role': 'momentum',
'warmup': 200,
'subscriptions': [
{'symbol': 'SOLUSDT', 'exchange': 'binance', 'timeframe': '4h'},
],
'parameters': {
'swing_period': 3, # bars on each side for swing detection
'lookback': 40, # bars to find swing points
'volume_mult': 1.2, # volume confirmation multiplier
'stop_loss_pct': 5.0, # stop loss percentage
'take_profit_pct': 10.0 # take profit percentage (2:1 R:R)
}
}
def find_swing_points(bars, i, lookback=40, swing_period=3):
"""
Find swing highs and lows in the lookback period.
A swing high is a bar whose high is >= all bars within swing_period on each side.
A swing low is a bar whose low is <= all bars within swing_period on each side.
Returns:
(swing_highs, swing_lows) - lists of (bar_index, price) tuples
"""
swing_highs = []
swing_lows = []
for j in range(i - lookback, i - swing_period):
if j < swing_period:
continue
# Check if this is a swing high
is_swing_high = all(
bars[j].high >= bars[k].high
for k in range(j - swing_period, j + swing_period + 1)
if k != j and 0 <= k < len(bars)
)
if is_swing_high:
swing_highs.append((j, bars[j].high))
# Check if this is a swing low
is_swing_low = all(
bars[j].low <= bars[k].low
for k in range(j - swing_period, j + swing_period + 1)
if k != j and 0 <= k < len(bars)
)
if is_swing_low:
swing_lows.append((j, bars[j].low))
return swing_highs, swing_lows
def process_time_step(ctx):
"""
Process each time step and generate trading actions.
Looks for HH/HL patterns in bullish regime for longs,
and LH/LL patterns in bearish regime for shorts.
"""
key = ('SOLUSDT', 'binance')
bars = ctx['bars'][key]
i = ctx['i']
positions = ctx['positions']
params = ctx['parameters']
actions = []
# Calculate indicators
closes = [b.close for b in bars]
highs = [b.high for b in bars]
lows = [b.low for b in bars]
volumes = [b.volume for b in bars]
ema20 = ema(closes, 20)
ema50 = ema(closes, 50)
ema200 = ema(closes, 200)
vol_sma = sma(volumes, 20)
# Check indicator validity
if not ema50[i] or not ema200[i] or not ema20[i] or not vol_sma[i]:
return []
# Determine regime
bullish_regime = ema50[i] > ema200[i]
bearish_regime = ema50[i] < ema200[i]
# Find swing points
swing_highs, swing_lows = find_swing_points(
bars, i,
lookback=params['lookback'],
swing_period=params['swing_period']
)
# Need at least 2 swing highs and 2 swing lows for pattern
if len(swing_highs) < 2 or len(swing_lows) < 2:
return []
# Get most recent swing points (sorted by time)
recent_highs = sorted(swing_highs, key=lambda x: x[0])[-2:]
recent_lows = sorted(swing_lows, key=lambda x: x[0])[-2:]
# Detect patterns
higher_high = recent_highs[-1][1] > recent_highs[-2][1]
higher_low = recent_lows[-1][1] > recent_lows[-2][1]
lower_high = recent_highs[-1][1] < recent_highs[-2][1]
lower_low = recent_lows[-1][1] < recent_lows[-2][1]
latest_swing_high = recent_highs[-1][1]
latest_swing_low = recent_lows[-1][1]
bar = bars[i]
volume_mult = params['volume_mult']
if key not in positions:
# LONG ENTRY:
# 1. Bullish regime (EMA50 > EMA200)
# 2. Higher High AND Higher Low pattern
# 3. Price breaks above latest swing high
# 4. Price above EMA20 (short-term bullish)
# 5. Volume above average * multiplier
if (bullish_regime and
higher_high and higher_low and
bar.close > latest_swing_high and
bar.close > ema20[i] and
volumes[i] > vol_sma[i] * volume_mult):
actions.append({
'action': 'open_long',
'symbol': 'SOLUSDT',
'exchange': 'binance',
'size': 1.0,
'stop_loss_pct': params['stop_loss_pct'],
'take_profit_pct': params['take_profit_pct'],
})
# SHORT ENTRY:
# 1. Bearish regime (EMA50 < EMA200)
# 2. Lower High AND Lower Low pattern
# 3. Price breaks below latest swing low
# 4. Price below EMA20 (short-term bearish)
# 5. Volume above average * multiplier
elif (bearish_regime and
lower_high and lower_low and
bar.close < latest_swing_low and
bar.close < ema20[i] and
volumes[i] > vol_sma[i] * volume_mult):
actions.append({
'action': 'open_short',
'symbol': 'SOLUSDT',
'exchange': 'binance',
'size': 1.0,
'stop_loss_pct': params['stop_loss_pct'],
'take_profit_pct': params['take_profit_pct'],
})
else:
pos = positions[key]
if pos.side == 'long':
# Exit long if:
# - Regime flips bearish (EMA50 < EMA200)
# - Price closes below EMA50 (trend weakening)
if not bullish_regime or bar.close < ema50[i]:
actions.append({
'action': 'close_long',
'symbol': 'SOLUSDT',
'exchange': 'binance',
})
elif pos.side == 'short':
# Exit short if:
# - Regime flips bullish (EMA50 > EMA200)
# - Price closes above EMA50 (trend weakening)
if not bearish_regime or bar.close > ema50[i]:
actions.append({
'action': 'close_short',
'symbol': 'SOLUSDT',
'exchange': 'binance',
})
return actions
if __name__ == '__main__':
from strategy import backtest_strategy
results, profitable, _ = backtest_strategy(init_strategy, process_time_step)
print(f"\nProfitable years: {profitable}")