Daniel Alnawaz commited on
Commit
e118e27
·
unverified ·
1 Parent(s): d67f432

large changes

Browse files
_backtest_breakout.py DELETED
@@ -1,58 +0,0 @@
1
- #!/usr/bin/env python3
2
- import sys
3
- import os
4
-
5
- # Force local imports
6
- sys.path.insert(0, os.getcwd())
7
-
8
- import strategy_breakout
9
- import backtest as bt_mod
10
- from hbot import HyperliquidAPI
11
- from datetime import datetime, timedelta, timezone
12
- import pandas as pd
13
-
14
- api = HyperliquidAPI(verify_ssl=False)
15
-
16
- def run_backtest(coin, timeframe, days):
17
- print(f"--- Backtesting {coin} ({timeframe}) for last {days} days ---")
18
- start_ms = int((datetime.now(timezone.utc) - timedelta(days=days)).timestamp() * 1000)
19
- raw = api.get_candles(coin, timeframe, start_ms)
20
- if not raw:
21
- print(f"No data for {coin}")
22
- return None
23
-
24
- rows = [{'timestamp': pd.to_datetime(int(x['t']), unit='ms', utc=True),
25
- 'open': float(x['o']), 'high': float(x['h']), 'low': float(x['l']),
26
- 'close': float(x['c']), 'volume': float(x['v'])} for x in raw]
27
- df = pd.DataFrame(rows).drop_duplicates('timestamp').sort_values('timestamp').set_index('timestamp')
28
-
29
- strategy = strategy_breakout.StrategyEngine({})
30
- bt = bt_mod.StrategyBacktester(
31
- strategy=strategy,
32
- starting_capital=1000.0,
33
- fee_bps=4.32,
34
- slippage_bps=1.0,
35
- position_risk_fraction=0.01, # 1% risk per trade
36
- strategy_name="strategy_breakout"
37
- )
38
-
39
- result = bt.backtest(df, coin, timeframe)
40
- return result
41
-
42
- COINS = ['BTC', 'ETH', 'SOL', 'TIA', 'ARB', 'WIF', 'DOGE']
43
- TIMEFRAMES = ['1h']
44
- DAYS = 180
45
-
46
- results = []
47
- for coin in COINS:
48
- for tf in TIMEFRAMES:
49
- res = run_backtest(coin, tf, DAYS)
50
- if res:
51
- results.append(res)
52
-
53
- print("\n" + "="*80)
54
- print(f"{'COIN':<8} | {'TF':<4} | {'TRADES':<6} | {'WIN%':<6} | {'RETURN%':<10} | {'PF':<5} | {'DD%':<6}")
55
- print("-" * 80)
56
- for r in results:
57
- print(f"{r.coin:<8} | {r.timeframe:<4} | {r.trades:<6} | {r.win_rate*100:<6.1f} | {r.total_return_pct:<10.2f} | {r.profit_factor:<5.2f} | {r.max_drawdown_pct:<6.2f}")
58
- print("="*80)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
_backtest_hma.py DELETED
@@ -1,58 +0,0 @@
1
- #!/usr/bin/env python3
2
- import sys
3
- import os
4
-
5
- # Force local imports
6
- sys.path.insert(0, os.getcwd())
7
-
8
- import strategy_hma_cross
9
- import backtest as bt_mod
10
- from hbot import HyperliquidAPI
11
- from datetime import datetime, timedelta, timezone
12
- import pandas as pd
13
-
14
- api = HyperliquidAPI(verify_ssl=False)
15
-
16
- def run_backtest(coin, timeframe, days):
17
- print(f"--- Backtesting {coin} ({timeframe}) for last {days} days ---")
18
- start_ms = int((datetime.now(timezone.utc) - timedelta(days=days)).timestamp() * 1000)
19
- raw = api.get_candles(coin, timeframe, start_ms)
20
- if not raw:
21
- print(f"No data for {coin}")
22
- return None
23
-
24
- rows = [{'timestamp': pd.to_datetime(int(x['t']), unit='ms', utc=True),
25
- 'open': float(x['o']), 'high': float(x['h']), 'low': float(x['l']),
26
- 'close': float(x['c']), 'volume': float(x['v'])} for x in raw]
27
- df = pd.DataFrame(rows).drop_duplicates('timestamp').sort_values('timestamp').set_index('timestamp')
28
-
29
- strategy = strategy_hma_cross.StrategyEngine({})
30
- bt = bt_mod.StrategyBacktester(
31
- strategy=strategy,
32
- starting_capital=1000.0,
33
- fee_bps=4.32,
34
- slippage_bps=1.0,
35
- position_risk_fraction=0.01,
36
- strategy_name="strategy_hma_cross"
37
- )
38
-
39
- result = bt.backtest(df, coin, timeframe)
40
- return result
41
-
42
- COINS = ['BTC', 'ETH', 'SOL', 'TIA', 'ARB', 'WIF', 'DOGE']
43
- TIMEFRAMES = ['1h']
44
- DAYS = 90
45
-
46
- results = []
47
- for coin in COINS:
48
- for tf in TIMEFRAMES:
49
- res = run_backtest(coin, tf, DAYS)
50
- if res:
51
- results.append(res)
52
-
53
- print("\n" + "="*80)
54
- print(f"{'COIN':<8} | {'TF':<4} | {'TRADES':<6} | {'WIN%':<6} | {'RETURN%':<10} | {'PF':<5} | {'DD%':<6}")
55
- print("-" * 80)
56
- for r in results:
57
- print(f"{r.coin:<8} | {r.timeframe:<4} | {r.trades:<6} | {r.win_rate*100:<6.1f} | {r.total_return_pct:<10.2f} | {r.profit_factor:<5.2f} | {r.max_drawdown_pct:<6.2f}")
58
- print("="*80)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
_backtest_rsi_extreme.py DELETED
@@ -1,58 +0,0 @@
1
- #!/usr/bin/env python3
2
- import sys
3
- import os
4
-
5
- # Force local imports
6
- sys.path.insert(0, os.getcwd())
7
-
8
- import strategy_rsi_extreme
9
- import backtest as bt_mod
10
- from hbot import HyperliquidAPI
11
- from datetime import datetime, timedelta, timezone
12
- import pandas as pd
13
-
14
- api = HyperliquidAPI(verify_ssl=False)
15
-
16
- def run_backtest(coin, timeframe, days):
17
- print(f"--- Backtesting {coin} ({timeframe}) for last {days} days ---")
18
- start_ms = int((datetime.now(timezone.utc) - timedelta(days=days)).timestamp() * 1000)
19
- raw = api.get_candles(coin, timeframe, start_ms)
20
- if not raw:
21
- print(f"No data for {coin}")
22
- return None
23
-
24
- rows = [{'timestamp': pd.to_datetime(int(x['t']), unit='ms', utc=True),
25
- 'open': float(x['o']), 'high': float(x['h']), 'low': float(x['l']),
26
- 'close': float(x['c']), 'volume': float(x['v'])} for x in raw]
27
- df = pd.DataFrame(rows).drop_duplicates('timestamp').sort_values('timestamp').set_index('timestamp')
28
-
29
- strategy = strategy_rsi_extreme.StrategyEngine({})
30
- bt = bt_mod.StrategyBacktester(
31
- strategy=strategy,
32
- starting_capital=1000.0,
33
- fee_bps=4.32,
34
- slippage_bps=1.0,
35
- position_risk_fraction=0.015, # Slightly higher risk for high-confidence setups
36
- strategy_name="strategy_rsi_extreme"
37
- )
38
-
39
- result = bt.backtest(df, coin, timeframe)
40
- return result
41
-
42
- COINS = ['BTC', 'ETH', 'SOL', 'TIA', 'ARB', 'WIF', 'DOGE']
43
- TIMEFRAMES = ['1h']
44
- DAYS = 180
45
-
46
- results = []
47
- for coin in COINS:
48
- for tf in TIMEFRAMES:
49
- res = run_backtest(coin, tf, DAYS)
50
- if res:
51
- results.append(res)
52
-
53
- print("\n" + "="*80)
54
- print(f"{'COIN':<8} | {'TF':<4} | {'TRADES':<6} | {'WIN%':<6} | {'RETURN%':<10} | {'PF':<5} | {'DD%':<6}")
55
- print("-" * 80)
56
- for r in results:
57
- print(f"{r.coin:<8} | {r.timeframe:<4} | {r.trades:<6} | {r.win_rate*100:<6.1f} | {r.total_return_pct:<10.2f} | {r.profit_factor:<5.2f} | {r.max_drawdown_pct:<6.2f}")
58
- print("="*80)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
_backtest_st_pullback.py DELETED
@@ -1,58 +0,0 @@
1
- #!/usr/bin/env python3
2
- import sys
3
- import os
4
-
5
- # Force local imports
6
- sys.path.insert(0, os.getcwd())
7
-
8
- import strategy_st_pullback
9
- import backtest as bt_mod
10
- from hbot import HyperliquidAPI
11
- from datetime import datetime, timedelta, timezone
12
- import pandas as pd
13
-
14
- api = HyperliquidAPI(verify_ssl=False)
15
-
16
- def run_backtest(coin, timeframe, days):
17
- print(f"--- Backtesting {coin} ({timeframe}) for last {days} days ---")
18
- start_ms = int((datetime.now(timezone.utc) - timedelta(days=days)).timestamp() * 1000)
19
- raw = api.get_candles(coin, timeframe, start_ms)
20
- if not raw:
21
- print(f"No data for {coin}")
22
- return None
23
-
24
- rows = [{'timestamp': pd.to_datetime(int(x['t']), unit='ms', utc=True),
25
- 'open': float(x['o']), 'high': float(x['h']), 'low': float(x['l']),
26
- 'close': float(x['c']), 'volume': float(x['v'])} for x in raw]
27
- df = pd.DataFrame(rows).drop_duplicates('timestamp').sort_values('timestamp').set_index('timestamp')
28
-
29
- strategy = strategy_st_pullback.StrategyEngine({})
30
- bt = bt_mod.StrategyBacktester(
31
- strategy=strategy,
32
- starting_capital=1000.0,
33
- fee_bps=4.32,
34
- slippage_bps=1.0,
35
- position_risk_fraction=0.01, # 1% risk per trade
36
- strategy_name="strategy_st_pullback"
37
- )
38
-
39
- result = bt.backtest(df, coin, timeframe)
40
- return result
41
-
42
- COINS = ['BTC', 'ETH', 'SOL', 'TIA', 'ARB', 'WIF', 'DOGE']
43
- TIMEFRAMES = ['1h']
44
- DAYS = 180
45
-
46
- results = []
47
- for coin in COINS:
48
- for tf in TIMEFRAMES:
49
- res = run_backtest(coin, tf, DAYS)
50
- if res:
51
- results.append(res)
52
-
53
- print("\n" + "="*80)
54
- print(f"{'COIN':<8} | {'TF':<4} | {'TRADES':<6} | {'WIN%':<6} | {'RETURN%':<10} | {'PF':<5} | {'DD%':<6}")
55
- print("-" * 80)
56
- for r in results:
57
- print(f"{r.coin:<8} | {r.timeframe:<4} | {r.trades:<6} | {r.win_rate*100:<6.1f} | {r.total_return_pct:<10.2f} | {r.profit_factor:<5.2f} | {r.max_drawdown_pct:<6.2f}")
58
- print("="*80)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
_backtest_vw_reversion.py DELETED
@@ -1,58 +0,0 @@
1
- #!/usr/bin/env python3
2
- import sys
3
- import os
4
-
5
- # Force local imports
6
- sys.path.insert(0, os.getcwd())
7
-
8
- import strategy_vw_reversion
9
- import backtest as bt_mod
10
- from hbot import HyperliquidAPI
11
- from datetime import datetime, timedelta, timezone
12
- import pandas as pd
13
-
14
- api = HyperliquidAPI(verify_ssl=False)
15
-
16
- def run_backtest(coin, timeframe, days):
17
- print(f"--- Backtesting {coin} ({timeframe}) for last {days} days ---")
18
- start_ms = int((datetime.now(timezone.utc) - timedelta(days=days)).timestamp() * 1000)
19
- raw = api.get_candles(coin, timeframe, start_ms)
20
- if not raw:
21
- print(f"No data for {coin}")
22
- return None
23
-
24
- rows = [{'timestamp': pd.to_datetime(int(x['t']), unit='ms', utc=True),
25
- 'open': float(x['o']), 'high': float(x['h']), 'low': float(x['l']),
26
- 'close': float(x['c']), 'volume': float(x['v'])} for x in raw]
27
- df = pd.DataFrame(rows).drop_duplicates('timestamp').sort_values('timestamp').set_index('timestamp')
28
-
29
- strategy = strategy_vw_reversion.StrategyEngine({})
30
- bt = bt_mod.StrategyBacktester(
31
- strategy=strategy,
32
- starting_capital=1000.0,
33
- fee_bps=1.44,
34
- slippage_bps=0.5,
35
- position_risk_fraction=0.015,
36
- strategy_name="strategy_vw_reversion"
37
- )
38
-
39
- result = bt.backtest(df, coin, timeframe)
40
- return result
41
-
42
- COINS = ['kPEPE', 'NEAR', 'RNDR', 'INJ', 'SUI', 'FET']
43
- TIMEFRAMES = ['1h']
44
- DAYS = 180
45
-
46
- results = []
47
- for coin in COINS:
48
- for tf in TIMEFRAMES:
49
- res = run_backtest(coin, tf, DAYS)
50
- if res:
51
- results.append(res)
52
-
53
- print("\n" + "="*80)
54
- print(f"{'COIN':<8} | {'TF':<4} | {'TRADES':<6} | {'WIN%':<6} | {'RETURN%':<10} | {'PF':<5} | {'DD%':<6}")
55
- print("-" * 80)
56
- for r in results:
57
- print(f"{r.coin:<8} | {r.timeframe:<4} | {r.trades:<6} | {r.win_rate*100:<6.1f} | {r.total_return_pct:<10.2f} | {r.profit_factor:<5.2f} | {r.max_drawdown_pct:<6.2f}")
58
- print("="*80)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
_backtest_zscore.py DELETED
@@ -1,58 +0,0 @@
1
- #!/usr/bin/env python3
2
- import sys
3
- import os
4
-
5
- # Force local imports
6
- sys.path.insert(0, os.getcwd())
7
-
8
- import strategy_mean_reversion_zscore
9
- import backtest as bt_mod
10
- from hbot import HyperliquidAPI
11
- from datetime import datetime, timedelta, timezone
12
- import pandas as pd
13
-
14
- api = HyperliquidAPI(verify_ssl=False)
15
-
16
- def run_backtest(coin, timeframe, days):
17
- print(f"--- Backtesting {coin} ({timeframe}) for last {days} days ---")
18
- start_ms = int((datetime.now(timezone.utc) - timedelta(days=days)).timestamp() * 1000)
19
- raw = api.get_candles(coin, timeframe, start_ms)
20
- if not raw:
21
- print(f"No data for {coin}")
22
- return None
23
-
24
- rows = [{'timestamp': pd.to_datetime(int(x['t']), unit='ms', utc=True),
25
- 'open': float(x['o']), 'high': float(x['h']), 'low': float(x['l']),
26
- 'close': float(x['c']), 'volume': float(x['v'])} for x in raw]
27
- df = pd.DataFrame(rows).drop_duplicates('timestamp').sort_values('timestamp').set_index('timestamp')
28
-
29
- strategy = strategy_mean_reversion_zscore.StrategyEngine({})
30
- bt = bt_mod.StrategyBacktester(
31
- strategy=strategy,
32
- starting_capital=1000.0,
33
- fee_bps=1.44,
34
- slippage_bps=0.5,
35
- position_risk_fraction=0.01,
36
- strategy_name="strategy_mean_reversion_zscore"
37
- )
38
-
39
- result = bt.backtest(df, coin, timeframe)
40
- return result
41
-
42
- COINS = ['kPEPE', 'NEAR', 'RNDR', 'INJ', 'SUI', 'FET']
43
- TIMEFRAMES = ['1h']
44
- DAYS = 180
45
-
46
- results = []
47
- for coin in COINS:
48
- for tf in TIMEFRAMES:
49
- res = run_backtest(coin, tf, DAYS)
50
- if res:
51
- results.append(res)
52
-
53
- print("\n" + "="*80)
54
- print(f"{'COIN':<8} | {'TF':<4} | {'TRADES':<6} | {'WIN%':<6} | {'RETURN%':<10} | {'PF':<5} | {'DD%':<6}")
55
- print("-" * 80)
56
- for r in results:
57
- print(f"{r.coin:<8} | {r.timeframe:<4} | {r.trades:<6} | {r.win_rate*100:<6.1f} | {r.total_return_pct:<10.2f} | {r.profit_factor:<5.2f} | {r.max_drawdown_pct:<6.2f}")
58
- print("="*80)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
_debug_ml.py DELETED
@@ -1,39 +0,0 @@
1
- #!/usr/bin/env python3
2
- import sys, importlib
3
- import strategy_ml_auto as m
4
- sys.modules['strategy'] = m
5
- import backtest as bt
6
- importlib.reload(bt)
7
- from hbot import HyperliquidAPI
8
- from datetime import datetime, timedelta, timezone
9
- import pandas as pd
10
-
11
- api = HyperliquidAPI(verify_ssl=False)
12
- start_ms = int((datetime.now(timezone.utc)-timedelta(days=180)).timestamp()*1000)
13
- raw = api.get_candles('WIF','1h',start_ms)
14
- rows=[{'timestamp':pd.to_datetime(int(x['t']),unit='ms',utc=True),
15
- 'open':float(x['o']),'high':float(x['h']),'low':float(x['l']),
16
- 'close':float(x['c']),'volume':float(x['v'])} for x in raw]
17
- df=pd.DataFrame(rows).drop_duplicates('timestamp').sort_values('timestamp').set_index('timestamp')
18
- print('raw candles:', len(df))
19
-
20
- engine = m.StrategyEngine({'coin':'WIF'})
21
- data = engine.add_indicators(df)
22
- print('after add_indicators:', len(data))
23
- print('LGB_AVAILABLE:', m.LGB_AVAILABLE)
24
- print('TRAIN_WINDOW:', m.TRAIN_WINDOW)
25
-
26
- # Check MIN_TRAIN_ROWS exists in strategy_ml_auto
27
- print('MIN_TRAIN_ROWS in m:', hasattr(m, 'MIN_TRAIN_ROWS'), getattr(m,'MIN_TRAIN_ROWS','MISSING'))
28
-
29
- # Check features after _build_features
30
- from strategy_ml_auto import _build_features, FEATURE_COLS
31
- d2 = _build_features(data)
32
- print('after _build_features:', len(d2))
33
- nans = d2[FEATURE_COLS].isna().sum()
34
- bad = nans[nans>0]
35
- print('cols still NaN:', bad.to_dict() if len(bad) else 'none - all clean')
36
- print('feature cols count:', len(FEATURE_COLS))
37
- clean = d2.dropna(subset=FEATURE_COLS)
38
- print('rows after dropna(FEATURE_COLS):', len(clean))
39
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
_diversification_analysis.py DELETED
@@ -1,131 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Diversification analysis — answers: how many coins per strategy is optimal?
4
-
5
- For each strategy, backtests all candidate coins independently at equal
6
- $1000 capital, then simulates portfolios of 1 to N coins (best-first) to
7
- show the return/drawdown tradeoff of adding more slots.
8
- """
9
- import sys, importlib
10
- from hbot import HyperliquidAPI
11
- from datetime import datetime, timedelta, timezone
12
- import pandas as pd
13
-
14
- api = HyperliquidAPI(verify_ssl=False)
15
-
16
- def get_df(coin, tf, days):
17
- start_ms = int((datetime.now(timezone.utc) - timedelta(days=days)).timestamp() * 1000)
18
- raw = api.get_candles(coin, tf, start_ms)
19
- if not raw:
20
- return pd.DataFrame()
21
- rows = [{'timestamp': pd.to_datetime(int(x['t']), unit='ms', utc=True),
22
- 'open': float(x['o']), 'high': float(x['h']), 'low': float(x['l']),
23
- 'close': float(x['c']), 'volume': float(x['v'])} for x in raw]
24
- return pd.DataFrame(rows).drop_duplicates('timestamp').sort_values('timestamp').set_index('timestamp')
25
-
26
- def run_bt(strat_name, coin, tf, days=180, capital=1000.0):
27
- for k in ['strategy', 'backtest']:
28
- sys.modules.pop(k, None)
29
- m = importlib.import_module(strat_name)
30
- sys.modules['strategy'] = m
31
- import backtest as bt
32
- importlib.reload(bt)
33
- df = get_df(coin, tf, days)
34
- if df.empty or len(df) < 100:
35
- return None
36
- s = m.StrategyEngine({'use_regime_filter': False, 'coin': coin})
37
- b = bt.StrategyBacktester(s, capital, 0, 1.0, 0.01, strategy_name=strat_name)
38
- return b.backtest(df, coin, tf)
39
-
40
- CANDIDATES = {
41
- ('strategy_conservative', '1h'): ['WIF','TIA','NEAR','OP','SOL','INJ','DOT','LINK'],
42
- ('strategy_hype', '1h'): ['TIA','APT','DOT','NEAR','DOGE','WIF','ETH','ARB'],
43
- ('strategy_scalp', '30m'): ['ARB','TIA','NEAR','BTC','DOT','APT','SOL','OP'],
44
- ('strategy_scalp', '15m'): ['OP','DOGE','INJ','ARB','SUI','DOT','TIA','ETH'],
45
- }
46
-
47
- print("=" * 75)
48
- print("PER-COIN PERFORMANCE (180d, $1000 each, realistic fees, 1% risk/trade)")
49
- print("=" * 75)
50
-
51
- all_results = {}
52
- for (strat, tf), coins in CANDIDATES.items():
53
- key = f"{strat}_{tf}"
54
- print(f"\n── {strat} {tf} ──")
55
- print(f" {'coin':>5} | {'trades':>6} | {'win%':>5} | {'ret%':>7} | {'pf':>4} | {'dd%':>5} | {'sharpe':>6} | {'ret$':>7}")
56
- results = []
57
- for coin in coins:
58
- r = run_bt(strat, coin, tf)
59
- if r and r.trades > 3:
60
- ret_dollar = r.final_equity - 1000.0
61
- results.append((coin, r))
62
- flag = ' ✓' if r.total_return_pct > 0 and r.profit_factor > 1.1 else ''
63
- print(f" {coin:>5} | {r.trades:>6} | {r.win_rate*100:>5.1f} | "
64
- f"{r.total_return_pct:>7.2f} | {r.profit_factor:>4.2f} | "
65
- f"{r.max_drawdown_pct:>5.2f} | {r.sharpe_ratio:>6.2f} | {ret_dollar:>+7.2f}{flag}")
66
- all_results[key] = results
67
-
68
- print()
69
- print("=" * 75)
70
- print("PORTFOLIO SIMULATION — returns by number of slots (best-first, equal weight)")
71
- print("=" * 75)
72
- print(" ◀ marks the last profitable coin (adding more coins beyond this hurts returns)")
73
-
74
- for key, results in all_results.items():
75
- if not results:
76
- continue
77
- strat, tf = key.rsplit('_', 1)
78
- sorted_r = sorted(results, key=lambda x: x[1].total_return_pct, reverse=True)
79
- profitable = [x for x in sorted_r if x[1].total_return_pct > 0 and x[1].profit_factor > 1.0]
80
-
81
- print(f"\n── {key} ──")
82
- print(f" {'n':>2} | {'coins':<28} | {'port ret%':>9} | {'avg dd%':>7} | {'worst dd%':>9} | note")
83
-
84
- for n in range(1, min(len(sorted_r) + 1, 7)):
85
- top_n = sorted_r[:n]
86
- cap = 1000.0 / n
87
- port_final = sum(cap * (1 + r.total_return_pct / 100) for _, r in top_n)
88
- port_ret = (port_final - 1000.0) / 1000.0 * 100
89
- avg_dd = sum(r.max_drawdown_pct for _, r in top_n) / n
90
- worst_dd = max(r.max_drawdown_pct for _, r in top_n)
91
- coin_list = '+'.join(c for c, _ in top_n)
92
- note = ''
93
- if n == 1:
94
- note = 'best only'
95
- elif n == len(profitable):
96
- note = '◀ all profitable'
97
- elif n > len(profitable):
98
- note = 'adding losers'
99
- print(f" {n:>2} | {coin_list:<28} | {port_ret:>+9.2f} | {avg_dd:>7.2f} | {worst_dd:>9.2f} | {note}")
100
-
101
- print()
102
- print("=" * 75)
103
- print("VERDICT")
104
- print("=" * 75)
105
-
106
- # Cross-strategy summary
107
- for key, results in all_results.items():
108
- if not results:
109
- continue
110
- sorted_r = sorted(results, key=lambda x: x[1].total_return_pct, reverse=True)
111
- profitable = [x for x in sorted_r if x[1].total_return_pct > 0 and x[1].profit_factor > 1.0]
112
- n_opt = len(profitable)
113
-
114
- if n_opt == 0:
115
- best_ret = sorted_r[0][1].total_return_pct if sorted_r else 0
116
- print(f" {key}: 0 profitable coins — consider skipping this strategy right now")
117
- elif n_opt == 1:
118
- c, r = profitable[0]
119
- print(f" {key}: 1 strong coin ({c} {r.total_return_pct:+.1f}%) — concentrate, don't dilute")
120
- else:
121
- one_ret = sorted_r[0][1].total_return_pct
122
- n_cap = 1000.0 / n_opt
123
- n_final = sum(n_cap * (1 + r.total_return_pct / 100) for _, r in profitable)
124
- n_ret = (n_final - 1000.0) / 1000.0 * 100
125
- avg_dd_1 = sorted_r[0][1].max_drawdown_pct
126
- avg_dd_n = sum(r.max_drawdown_pct for _, r in profitable) / n_opt
127
- print(f" {key}: {n_opt} profitable coins | "
128
- f"1-coin ret={one_ret:+.1f}% dd={avg_dd_1:.1f}% vs "
129
- f"{n_opt}-coin ret={n_ret:+.1f}% dd={avg_dd_n:.1f}% "
130
- f"→ {'diversify ✓' if n_ret > one_ret * 0.8 and avg_dd_n < avg_dd_1 else 'concentrate ✓' if one_ret > n_ret else 'diversify ✓'}")
131
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
_find_coins.py DELETED
@@ -1,44 +0,0 @@
1
- #!/usr/bin/env python3
2
- import sys, importlib
3
-
4
- def run_bt(strat_name, coin, tf, days, fee_bps, slip_bps, risk_frac):
5
- sys.modules.pop('strategy', None)
6
- sys.modules.pop('backtest', None)
7
- m = importlib.import_module(strat_name)
8
- sys.modules['strategy'] = m
9
- import backtest as bt
10
- importlib.reload(bt)
11
- from hbot import HyperliquidAPI
12
- from datetime import datetime, timedelta, timezone
13
- import pandas as pd
14
- api = HyperliquidAPI(verify_ssl=False)
15
- start_ms = int((datetime.now(timezone.utc) - timedelta(days=days)).timestamp() * 1000)
16
- raw = api.get_candles(coin, tf, start_ms)
17
- if not raw: return None
18
- rows = [{'timestamp': pd.to_datetime(int(x['t']), unit='ms', utc=True),
19
- 'open': float(x['o']), 'high': float(x['h']), 'low': float(x['l']),
20
- 'close': float(x['c']), 'volume': float(x['v'])} for x in raw]
21
- df = pd.DataFrame(rows).drop_duplicates('timestamp').sort_values('timestamp').set_index('timestamp')
22
- if len(df) < 150: return None
23
- s = m.StrategyEngine({'use_regime_filter': False})
24
- b = bt.StrategyBacktester(s, 100.0, fee_bps, slip_bps, risk_frac,
25
- strategy_name=strat_name)
26
- return b.backtest(df, coin, tf)
27
-
28
- print('Tier 1 candidates (strategy_conservative, 1h, 180d, maker fees) — NOT TIA/SOL:')
29
- print(f' {"coin":>5} | {"trades":>6} | {"win%":>5} | {"ret%":>7} | {"pf":>4} | {"dd%":>5} | {"fees$":>6}')
30
- for coin in ['BTC','ETH','LINK','AVAX','BNB','INJ','ATOM','NEAR','FIL','OP','ARB','SUI']:
31
- r = run_bt('strategy_conservative', coin, '1h', 180, 1.44, 0.5, 0.003)
32
- if r and r.trades > 0:
33
- m = ' ✓' if r.total_return_pct > 3 and r.profit_factor > 1.3 and r.max_drawdown_pct < 5 else ''
34
- print(f' {coin:>5} | {r.trades:>6} | {r.win_rate*100:>5.1f} | {r.total_return_pct:>7.2f} | {r.profit_factor:>4.2f} | {r.max_drawdown_pct:>5.2f} | {r.total_fees_paid:>6.2f}{m}')
35
-
36
- print()
37
- print('Tier 3 candidates (strategy_scalp, 15m, 90d, hybrid fees) — NOT TIA/SOL:')
38
- print(f' {"coin":>5} | {"trades":>6} | {"win%":>5} | {"ret%":>7} | {"pf":>4} | {"dd%":>5} | {"fees$":>6}')
39
- for coin in ['BTC','ETH','LINK','AVAX','BNB','INJ','ARB','WIF','DOGE','OP','NEAR','SUI','APT']:
40
- r = run_bt('strategy_scalp', coin, '15m', 90, 4.32, 1.5, 0.005)
41
- if r and r.trades > 5:
42
- m = ' ✓' if r.total_return_pct > 0 and r.profit_factor > 1.1 else ''
43
- print(f' {coin:>5} | {r.trades:>6} | {r.win_rate*100:>5.1f} | {r.total_return_pct:>7.2f} | {r.profit_factor:>4.2f} | {r.max_drawdown_pct:>5.2f} | {r.total_fees_paid:>6.2f}{m}')
44
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
_scan_30m.py DELETED
@@ -1,44 +0,0 @@
1
- #!/usr/bin/env python3
2
- import sys, importlib
3
- from hbot import HyperliquidAPI
4
- from datetime import datetime, timedelta, timezone
5
- import pandas as pd
6
-
7
- api = HyperliquidAPI(verify_ssl=False)
8
-
9
- def run_bt(strat_name, coin, tf, days, fee_bps=4.32, slip_bps=1.0, risk_frac=0.005):
10
- sys.modules.pop('strategy', None)
11
- sys.modules.pop('backtest', None)
12
- m = importlib.import_module(strat_name)
13
- sys.modules['strategy'] = m
14
- import backtest as bt
15
- importlib.reload(bt)
16
- start_ms = int((datetime.now(timezone.utc) - timedelta(days=days)).timestamp() * 1000)
17
- raw = api.get_candles(coin, tf, start_ms)
18
- if not raw: return None
19
- rows = [{'timestamp': pd.to_datetime(int(x['t']), unit='ms', utc=True),
20
- 'open': float(x['o']), 'high': float(x['h']), 'low': float(x['l']),
21
- 'close': float(x['c']), 'volume': float(x['v'])} for x in raw]
22
- df = pd.DataFrame(rows).drop_duplicates('timestamp').sort_values('timestamp').set_index('timestamp')
23
- if len(df) < 150: return None
24
- s = m.StrategyEngine({'use_regime_filter': False})
25
- b = bt.StrategyBacktester(s, 100.0, fee_bps, slip_bps, risk_frac,
26
- strategy_name=strat_name)
27
- return b.backtest(df, coin, tf)
28
-
29
- print('=== strategy_hype on SOL 1h (current aggressive bot) ===')
30
- print(f' {"days":>4} | {"trades":>6} | {"win%":>5} | {"ret%":>7} | {"pf":>4} | {"dd%":>5} | {"sharpe":>6}')
31
- for days in [30, 60, 90, 120, 180]:
32
- r = run_bt('strategy_hype', 'SOL', '1h', days)
33
- if r:
34
- m = ' ✓' if r.total_return_pct > 0 and r.profit_factor > 1.1 else ' ✗'
35
- print(f' {days:>4} | {r.trades:>6} | {r.win_rate*100:>5.1f} | {r.total_return_pct:>7.2f} | {r.profit_factor:>4.2f} | {r.max_drawdown_pct:>5.2f} | {r.sharpe_ratio:>6.2f}{m}')
36
-
37
- print()
38
- print('=== strategy_hype: SOL vs TIA vs other coins (180d, 1h) ===')
39
- print(f' {"coin":>5} | {"trades":>6} | {"win%":>5} | {"ret%":>7} | {"pf":>4} | {"dd%":>5}')
40
- for coin in ['SOL', 'TIA', 'LINK', 'NEAR', 'AVAX', 'BNB', 'INJ', 'ARB']:
41
- r = run_bt('strategy_hype', coin, '1h', 180)
42
- if r:
43
- m = ' ✓' if r.total_return_pct > 3 and r.profit_factor > 1.2 else ''
44
- print(f' {coin:>5} | {r.trades:>6} | {r.win_rate*100:>5.1f} | {r.total_return_pct:>7.2f} | {r.profit_factor:>4.2f} | {r.max_drawdown_pct:>5.2f}{m}')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
_scan_all.py DELETED
@@ -1,84 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Scans coins across timeframes to find the best fit for each bot tier."""
3
- import sys, importlib
4
- from hbot import HyperliquidAPI
5
- from datetime import datetime, timedelta, timezone
6
- import pandas as pd
7
-
8
- api = HyperliquidAPI(verify_ssl=False)
9
-
10
- def get_df(coin, tf, days):
11
- start_ms = int((datetime.now(timezone.utc) - timedelta(days=days)).timestamp() * 1000)
12
- raw = api.get_candles(coin, tf, start_ms)
13
- if not raw: return pd.DataFrame()
14
- rows = [{'timestamp': pd.to_datetime(int(x['t']), unit='ms', utc=True),
15
- 'open': float(x['o']), 'high': float(x['h']), 'low': float(x['l']),
16
- 'close': float(x['c']), 'volume': float(x['v'])} for x in raw]
17
- df = pd.DataFrame(rows).drop_duplicates('timestamp').sort_values('timestamp').set_index('timestamp')
18
- return df
19
-
20
- def run_bt(strategy_mod, df, coin, tf, fee_bps, slip_bps, risk_frac, strat_name=""):
21
- sys.modules.pop('strategy', None)
22
- sys.modules.pop('backtest', None)
23
- sys.modules['strategy'] = strategy_mod
24
- import backtest as bt_mod
25
- importlib.reload(bt_mod)
26
- s = strategy_mod.StrategyEngine({'use_regime_filter': False})
27
- bt = bt_mod.StrategyBacktester(s, 100.0, fee_bps, slip_bps, risk_frac,
28
- strategy_name=strat_name)
29
- return bt.backtest(df, coin, tf)
30
-
31
- COINS = ['BTC','ETH','SOL','TIA','AVAX','LINK','ARB','DOGE','BNB','WIF','SUI','INJ','OP','MATIC','APT','NEAR','FIL','SEI','ATOM']
32
-
33
- # ── TIER 1: Long-term (4h) — conservative mean reversion ──────────────────
34
- print('\n══ TIER 1: Long-term bot (4h, 180d, conservative) ══')
35
- import strategy as t1_mod
36
- print(f'{"coin":>5} | {"trades":>6} | {"win%":>5} | {"ret%":>7} | {"pf":>4} | {"dd%":>5} | {"sharpe":>6}')
37
- t1_results = []
38
- for coin in COINS:
39
- try:
40
- df = get_df(coin, '4h', 180)
41
- if len(df) < 150: continue
42
- r = run_bt(t1_mod, df, coin, '4h', 1.44, 0.5, 0.003, strat_name='strategy_conservative')
43
- t1_results.append(r)
44
- except Exception as e:
45
- pass
46
- t1_results.sort(key=lambda r: (r.total_return_pct if r.profit_factor > 1.1 and r.trades >= 8 else -999), reverse=True)
47
- for r in t1_results[:10]:
48
- m = ' ✓' if r.total_return_pct > 5 and r.profit_factor > 1.2 and r.trades >= 8 and r.max_drawdown_pct < 8 else ''
49
- print(f'{r.coin:>5} | {r.trades:>6} | {r.win_rate*100:>5.1f} | {r.total_return_pct:>7.2f} | {r.profit_factor:>4.2f} | {r.max_drawdown_pct:>5.2f} | {r.sharpe_ratio:>6.2f}{m}')
50
-
51
- # ── TIER 3: Scalp (15m) — use strategy_hype (RSI pullback) ────────────────
52
- print('\n══ TIER 3: Scalp bot (15m, 90d, hybrid fees) ══')
53
- import strategy_hype as t3_mod
54
- print(f'{"coin":>5} | {"trades":>6} | {"win%":>5} | {"ret%":>7} | {"pf":>4} | {"dd%":>5} | {"sharpe":>6}')
55
- t3_results = []
56
- for coin in COINS:
57
- try:
58
- df = get_df(coin, '15m', 90)
59
- if len(df) < 300: continue
60
- r = run_bt(t3_mod, df, coin, '15m', 4.32, 1.5, 0.005, strat_name='strategy_hype')
61
- t3_results.append(r)
62
- except Exception as e:
63
- pass
64
- t3_results.sort(key=lambda r: (r.total_return_pct if r.profit_factor > 1.1 and r.trades >= 15 else -999), reverse=True)
65
- for r in t3_results[:10]:
66
- m = ' ✓' if r.total_return_pct > 0 and r.profit_factor > 1.1 and r.trades >= 15 and r.max_drawdown_pct < 10 else ''
67
- print(f'{r.coin:>5} | {r.trades:>6} | {r.win_rate*100:>5.1f} | {r.total_return_pct:>7.2f} | {r.profit_factor:>4.2f} | {r.max_drawdown_pct:>5.2f} | {r.sharpe_ratio:>6.2f}{m}')
68
-
69
- # Also try 5m
70
- print('\n══ TIER 3: Scalp bot (5m, 60d, hybrid fees) ══')
71
- for coin in COINS:
72
- try:
73
- df = get_df(coin, '5m', 60)
74
- if len(df) < 500: continue
75
- r = run_bt(t3_mod, df, coin, '5m', 4.32, 1.5, 0.005, strat_name='strategy_hype')
76
- t3_results.append(r)
77
- except Exception as e:
78
- pass
79
- t3_5m = [r for r in t3_results if r.timeframe == '5m']
80
- t3_5m.sort(key=lambda r: (r.total_return_pct if r.profit_factor > 1.1 and r.trades >= 20 else -999), reverse=True)
81
- for r in t3_5m[:10]:
82
- m = ' ✓' if r.total_return_pct > 0 and r.profit_factor > 1.1 and r.trades >= 20 and r.max_drawdown_pct < 10 else ''
83
- print(f'{r.coin:>5} | {r.trades:>6} | {r.win_rate*100:>5.1f} | {r.total_return_pct:>7.2f} | {r.profit_factor:>4.2f} | {r.max_drawdown_pct:>5.2f} | {r.sharpe_ratio:>6.2f}{m}')
84
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
_scan_coins.py CHANGED
@@ -38,8 +38,6 @@ STRATEGY_FEES = {
38
  'strategy_hype': dict(fee_bps=4.32, slip_bps=1.0),
39
  'strategy_scalp': dict(fee_bps=4.32, slip_bps=1.5),
40
  'strategy_ml_auto': dict(fee_bps=4.32, slip_bps=1.0),
41
- 'strategy_vw_reversion': dict(fee_bps=1.44, slip_bps=0.5),
42
- 'strategy_mean_reversion_zscore': dict(fee_bps=1.44, slip_bps=0.5),
43
  }
44
 
45
  COINS = ['TIA', 'LINK', 'NEAR', 'INJ', 'ARB', 'AVAX', 'SOL', 'BTC', 'ETH',
@@ -51,18 +49,14 @@ Q = {
51
  'strategy_hype': dict(min_ret=3.0, min_pf=1.15, max_dd=99.0),
52
  'strategy_scalp': dict(min_ret=1.0, min_pf=1.05, max_dd=15.0),
53
  'strategy_ml_auto': dict(min_ret=-5.0, min_pf=0.90, max_dd=25.0),
54
- 'strategy_vw_reversion': dict(min_ret=2.0, min_pf=1.10, max_dd=15.0),
55
- 'strategy_mean_reversion_zscore': dict(min_ret=2.0, min_pf=1.10, max_dd=15.0),
56
  }
57
 
58
  # ── Capital allocation per strategy (must sum to 1.0) ────────────────────────
59
  CAPITAL = {
60
- 'strategy_hype': 0.15,
61
- 'strategy_conservative': 0.15,
62
- 'strategy_scalp': 0.15,
63
- 'strategy_vw_reversion': 0.15,
64
- 'strategy_mean_reversion_zscore': 0.15,
65
- 'strategy_ml_auto': 0.125, # per ML slot; 2 slots = 0.25 total
66
  }
67
 
68
 
@@ -153,25 +147,10 @@ scalp_qualified = print_table('strategy_scalp 30m 180d', r3_30[:8], Q['strategy_
153
  scalp_combined = [(r, '30m') for r in scalp_qualified]
154
 
155
  # ─────────────────────────────────────────────────────────────────────────────
156
- # 4. VW Reversion
157
- # ─────────────────────────────────────────────────────────────────────────────
158
- rvw = sorted(
159
- filter(None, [run_bt('strategy_vw_reversion', c, '1h') for c in COINS]),
160
- key=lambda x: x.total_return_pct, reverse=True)
161
- vw_qualified = print_table(
162
- 'strategy_vw_reversion 1h 180d', rvw[:8], Q['strategy_vw_reversion'])
163
 
164
- # ─────────────────────────────────────────────────────────────────────────────
165
- # 5. Z-Score Mean Reversion
166
- # ─────────────────────────────────────────────────────────────────────────────
167
- rzs = sorted(
168
- filter(None, [run_bt('strategy_mean_reversion_zscore', c, '1h') for c in COINS]),
169
- key=lambda x: x.total_return_pct, reverse=True)
170
- zscore_qualified = print_table(
171
- 'strategy_mean_reversion_zscore 1h 180d', rzs[:8], Q['strategy_mean_reversion_zscore'])
172
 
173
  # ─────────────────────────────────────────────────────────────────────────────
174
- # 6. ML Auto — ranked from seed file
175
  # ─────────────────────────────────────────────────────────────────────────────
176
  print(f'\n=== strategy_ml_auto 1h 180d (ranked from backtest seed) ===')
177
  print(f' {"coin":>5} | {"trades":>6} | {"win%":>5} | {"ret%":>7} | {"pf":>4} | '
@@ -244,10 +223,6 @@ def _pick_best(qualified, strategy, timeframe_or_pairs, max_slots=1):
244
  slots += _pick_best(hype_qualified, 'strategy_hype', '1h', max_slots=1)
245
  # Conservative (lowest drawdown)
246
  slots += _pick_best(conservative_qualified, 'strategy_conservative', '1h', max_slots=1)
247
- # VW Reversion
248
- slots += _pick_best(vw_qualified, 'strategy_vw_reversion', '1h', max_slots=1)
249
- # Z-Score
250
- slots += _pick_best(zscore_qualified, 'strategy_mean_reversion_zscore', '1h', max_slots=1)
251
  # Scalp best coin+tf combo
252
  slots += _pick_best(scalp_combined, 'strategy_scalp', scalp_combined, max_slots=1)
253
 
@@ -266,8 +241,6 @@ if not slots:
266
  for strat, results, tf in [
267
  ('strategy_hype', r2, '1h'),
268
  ('strategy_conservative', r1, '1h'),
269
- ('strategy_vw_reversion', rvw, '1h'),
270
- ('strategy_mean_reversion_zscore', rzs, '1h'),
271
  ('strategy_scalp', r3_30,'30m'),
272
  ]:
273
  for r in results:
 
38
  'strategy_hype': dict(fee_bps=4.32, slip_bps=1.0),
39
  'strategy_scalp': dict(fee_bps=4.32, slip_bps=1.5),
40
  'strategy_ml_auto': dict(fee_bps=4.32, slip_bps=1.0),
 
 
41
  }
42
 
43
  COINS = ['TIA', 'LINK', 'NEAR', 'INJ', 'ARB', 'AVAX', 'SOL', 'BTC', 'ETH',
 
49
  'strategy_hype': dict(min_ret=3.0, min_pf=1.15, max_dd=99.0),
50
  'strategy_scalp': dict(min_ret=1.0, min_pf=1.05, max_dd=15.0),
51
  'strategy_ml_auto': dict(min_ret=-5.0, min_pf=0.90, max_dd=25.0),
 
 
52
  }
53
 
54
  # ── Capital allocation per strategy (must sum to 1.0) ────────────────────────
55
  CAPITAL = {
56
+ 'strategy_hype': 0.33,
57
+ 'strategy_conservative': 0.33,
58
+ 'strategy_scalp': 0.34,
59
+ 'strategy_ml_auto': 0.50, # 2 slots = 1.0 total
 
 
60
  }
61
 
62
 
 
147
  scalp_combined = [(r, '30m') for r in scalp_qualified]
148
 
149
  # ─────────────────────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
150
 
 
 
 
 
 
 
 
 
151
 
152
  # ─────────────────────────────────────────────────────────────────────────────
153
+ # ML Auto — ranked from seed file
154
  # ─────────────────────────────────────────────────────────────────────────────
155
  print(f'\n=== strategy_ml_auto 1h 180d (ranked from backtest seed) ===')
156
  print(f' {"coin":>5} | {"trades":>6} | {"win%":>5} | {"ret%":>7} | {"pf":>4} | '
 
223
  slots += _pick_best(hype_qualified, 'strategy_hype', '1h', max_slots=1)
224
  # Conservative (lowest drawdown)
225
  slots += _pick_best(conservative_qualified, 'strategy_conservative', '1h', max_slots=1)
 
 
 
 
226
  # Scalp best coin+tf combo
227
  slots += _pick_best(scalp_combined, 'strategy_scalp', scalp_combined, max_slots=1)
228
 
 
241
  for strat, results, tf in [
242
  ('strategy_hype', r2, '1h'),
243
  ('strategy_conservative', r1, '1h'),
 
 
244
  ('strategy_scalp', r3_30,'30m'),
245
  ]:
246
  for r in results:
_validate_bots.py DELETED
@@ -1,63 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Validate all three strategy tiers."""
3
- import sys, importlib
4
- from hbot import HyperliquidAPI
5
- from datetime import datetime, timedelta, timezone
6
- import pandas as pd
7
-
8
- api = HyperliquidAPI(verify_ssl=False)
9
-
10
- def get_df(coin, tf, days):
11
- start_ms = int((datetime.now(timezone.utc) - timedelta(days=days)).timestamp() * 1000)
12
- raw = api.get_candles(coin, tf, start_ms)
13
- if not raw: return pd.DataFrame()
14
- rows = [{'timestamp': pd.to_datetime(int(x['t']), unit='ms', utc=True),
15
- 'open': float(x['o']), 'high': float(x['h']), 'low': float(x['l']),
16
- 'close': float(x['c']), 'volume': float(x['v'])} for x in raw]
17
- return pd.DataFrame(rows).drop_duplicates('timestamp').sort_values('timestamp').set_index('timestamp')
18
-
19
- def run_bt(strat_name, coin, tf, days, fee_bps, slip_bps, risk_frac):
20
- sys.modules.pop('strategy', None)
21
- sys.modules.pop('backtest', None)
22
- m = importlib.import_module(strat_name)
23
- sys.modules['strategy'] = m
24
- import backtest as bt
25
- importlib.reload(bt)
26
- df = get_df(coin, tf, days)
27
- if len(df) < 100: return None
28
- s = m.StrategyEngine({'use_regime_filter': False})
29
- b = bt.StrategyBacktester(s, 100.0, fee_bps, slip_bps, risk_frac,
30
- strategy_name=strat_name)
31
- return b.backtest(df, coin, tf)
32
-
33
- print("╔══════════════════════════════════════════════════════════════════╗")
34
- print("║ 3-BOT SYSTEM VALIDATION ║")
35
- print("╠══════════════════════════════════════════════════════════════════╣")
36
-
37
- # TIER 1 — Conservative TIA 1h (wider stops, patience)
38
- print("║ TIER 1 — Conservative (strategy_conservative) TIA 1h ║")
39
- print("╠══════════════════════════════════════════════════════════════════╣")
40
- print(f" {'days':>4} | {'trades':>6} | {'win%':>5} | {'ret%':>7} | {'pf':>4} | {'dd%':>5} | {'sharpe':>6} | {'fees$':>6}")
41
- for days in [60, 90, 120, 180]:
42
- r = run_bt('strategy_conservative', 'TIA', '1h', days, 1.44, 0.5, 0.003)
43
- if r: print(f" {days:>4} | {r.trades:>6} | {r.win_rate*100:>5.1f} | {r.total_return_pct:>7.2f} | {r.profit_factor:>4.2f} | {r.max_drawdown_pct:>5.2f} | {r.sharpe_ratio:>6.2f} | {r.total_fees_paid:>6.2f}")
44
-
45
- # TIER 2 — Aggressive SOL 1h
46
- print("╠══════════════════════════════════════════════════════════════════╣")
47
- print("║ TIER 2 — Aggressive (strategy_hype) SOL 1h ║")
48
- print("╠══════════════════════════════════════════════════════════════════╣")
49
- for days in [60, 90, 120, 180]:
50
- r = run_bt('strategy_hype', 'SOL', '1h', days, 4.32, 3.0, 0.005)
51
- if r: print(f" {days:>4} | {r.trades:>6} | {r.win_rate*100:>5.1f} | {r.total_return_pct:>7.2f} | {r.profit_factor:>4.2f} | {r.max_drawdown_pct:>5.2f} | {r.sharpe_ratio:>6.2f} | {r.total_fees_paid:>6.2f}")
52
-
53
- # TIER 3 — Scalp TIA 15m
54
- print("╠══════════════════════════════════════════════════════════════════╣")
55
- print("║ TIER 3 — Scalp (strategy_scalp) TIA 15m ║")
56
- print("╠══════════════════════════════════════════════════════════════════╣")
57
- for days in [60, 90, 120, 180]:
58
- r = run_bt('strategy_scalp', 'TIA', '15m', days, 4.32, 1.5, 0.005)
59
- if r: print(f" {days:>4} | {r.trades:>6} | {r.win_rate*100:>5.1f} | {r.total_return_pct:>7.2f} | {r.profit_factor:>4.2f} | {r.max_drawdown_pct:>5.2f} | {r.sharpe_ratio:>6.2f} | {r.total_fees_paid:>6.2f}")
60
-
61
- print("╚══════════════════════════════════════════════════════════════════╝")
62
-
63
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
autonomous_bot.py CHANGED
@@ -237,14 +237,12 @@ SLOT_PRESETS = {
237
  ("strategy_ml_auto", "SOL", "1h", 0.15),
238
  ],
239
  "new_mix": [
240
- ("strategy_vw_reversion", "INJ", "1h", 0.50),
241
- ("strategy_mean_reversion_zscore", "NEAR", "1h", 0.50),
242
  ],
243
  "extended": [
244
- ("strategy_hype", "TIA", "1h", 0.25),
245
- ("strategy_scalp", "NEAR", "30m", 0.25),
246
- ("strategy_vw_reversion", "INJ", "1h", 0.25),
247
- ("strategy_mean_reversion_zscore", "NEAR", "1h", 0.25),
248
  ],
249
  }
250
 
@@ -963,8 +961,8 @@ def main():
963
  "all = conservative/LINK + hype/TIA + scalp/NEAR\n"
964
  "ml = ml_auto/BTC + ml_auto/ETH\n"
965
  "all3 = hype + scalp + ml_auto (5 slots, all 3 strategies)\n"
966
- "new_mix = vw_reversion/INJ + zscore/NEAR (newly optimized)\n"
967
- "extended = hype/TIA + scalp/NEAR + vw_reversion/INJ + zscore/NEAR\n"
968
  "Ignored when --config (or auto-detected bot_config.json) is provided.\n"
969
  ),
970
  )
 
237
  ("strategy_ml_auto", "SOL", "1h", 0.15),
238
  ],
239
  "new_mix": [
240
+ ("strategy_hype", "TIA", "1h", 0.50),
241
+ ("strategy_scalp", "NEAR", "30m", 0.50),
242
  ],
243
  "extended": [
244
+ ("strategy_hype", "TIA", "1h", 0.50),
245
+ ("strategy_scalp", "NEAR", "30m", 0.50),
 
 
246
  ],
247
  }
248
 
 
961
  "all = conservative/LINK + hype/TIA + scalp/NEAR\n"
962
  "ml = ml_auto/BTC + ml_auto/ETH\n"
963
  "all3 = hype + scalp + ml_auto (5 slots, all 3 strategies)\n"
964
+ "new_mix = hype/TIA + scalp/NEAR\n"
965
+ "extended = hype/TIA + scalp/NEAR\n"
966
  "Ignored when --config (or auto-detected bot_config.json) is provided.\n"
967
  ),
968
  )
backtest.py CHANGED
@@ -9,8 +9,6 @@ from typing import List, Optional
9
  import numpy as np
10
  import pandas as pd
11
 
12
- from strategy import StrategyEngine
13
-
14
  # ── Realistic per-strategy fee profiles (your account rates) ─────────────────
15
  # Taker: 0.0432% (4.32 bps) Maker: 0.0144% (1.44 bps)
16
  #
@@ -33,12 +31,6 @@ STRATEGY_FEE_PROFILES = {
33
  "strategy_hype": dict(entry_fee_bps=4.32, exit_fee_bps=2.88),
34
  "strategy_scalp": dict(entry_fee_bps=4.32, exit_fee_bps=2.88),
35
  "strategy_ml_auto": dict(entry_fee_bps=4.32, exit_fee_bps=2.88),
36
- "strategy_breakout": dict(entry_fee_bps=4.32, exit_fee_bps=2.88),
37
- "strategy_mean_reversion_zscore": dict(entry_fee_bps=1.44, exit_fee_bps=1.44),
38
- "strategy_hma_cross": dict(entry_fee_bps=4.32, exit_fee_bps=2.88),
39
- "strategy_rsi_extreme": dict(entry_fee_bps=4.32, exit_fee_bps=2.88),
40
- "strategy_st_pullback": dict(entry_fee_bps=4.32, exit_fee_bps=2.88),
41
- "strategy_vw_reversion": dict(entry_fee_bps=1.44, exit_fee_bps=1.44),
42
  }
43
 
44
 
 
9
  import numpy as np
10
  import pandas as pd
11
 
 
 
12
  # ── Realistic per-strategy fee profiles (your account rates) ─────────────────
13
  # Taker: 0.0432% (4.32 bps) Maker: 0.0144% (1.44 bps)
14
  #
 
31
  "strategy_hype": dict(entry_fee_bps=4.32, exit_fee_bps=2.88),
32
  "strategy_scalp": dict(entry_fee_bps=4.32, exit_fee_bps=2.88),
33
  "strategy_ml_auto": dict(entry_fee_bps=4.32, exit_fee_bps=2.88),
 
 
 
 
 
 
34
  }
35
 
36
 
bot_config.json CHANGED
@@ -1,43 +1,37 @@
1
  {
2
- "generated_at": "2026-05-26T17:02:05.363830+00:00",
3
  "scan_days": 180,
4
- "ml_backtest_seed_path": "/home/chiggin/hyperliquid-bot/reports/ml_auto_backtest_seed.json",
5
  "slots": [
6
  {
7
  "strategy": "strategy_hype",
8
  "coin": "TIA",
9
  "timeframe": "1h",
10
- "capital_frac": 0.15
11
  },
12
  {
13
  "strategy": "strategy_conservative",
14
  "coin": "WIF",
15
  "timeframe": "1h",
16
- "capital_frac": 0.15
17
  },
18
  {
19
- "strategy": "strategy_vw_reversion",
20
  "coin": "ARB",
21
- "timeframe": "1h",
22
- "capital_frac": 0.15
23
- },
24
- {
25
- "strategy": "strategy_mean_reversion_zscore",
26
- "coin": "NEAR",
27
- "timeframe": "1h",
28
- "capital_frac": 0.15
29
  },
30
  {
31
  "strategy": "strategy_ml_auto",
32
  "coin": "SUI",
33
  "timeframe": "1h",
34
- "capital_frac": 0.125
35
  },
36
  {
37
  "strategy": "strategy_ml_auto",
38
- "coin": "INJ",
39
  "timeframe": "1h",
40
- "capital_frac": 0.125
41
  }
42
  ]
43
  }
 
1
  {
2
+ "generated_at": "2026-05-27T01:44:42.822174+00:00",
3
  "scan_days": 180,
4
+ "ml_backtest_seed_path": "/Users/daniel.a/hyperliquid-bot/reports/ml_auto_backtest_seed.json",
5
  "slots": [
6
  {
7
  "strategy": "strategy_hype",
8
  "coin": "TIA",
9
  "timeframe": "1h",
10
+ "capital_frac": 0.33
11
  },
12
  {
13
  "strategy": "strategy_conservative",
14
  "coin": "WIF",
15
  "timeframe": "1h",
16
+ "capital_frac": 0.33
17
  },
18
  {
19
+ "strategy": "strategy_scalp",
20
  "coin": "ARB",
21
+ "timeframe": "30m",
22
+ "capital_frac": 0.17
 
 
 
 
 
 
23
  },
24
  {
25
  "strategy": "strategy_ml_auto",
26
  "coin": "SUI",
27
  "timeframe": "1h",
28
+ "capital_frac": 0.085
29
  },
30
  {
31
  "strategy": "strategy_ml_auto",
32
+ "coin": "NEAR",
33
  "timeframe": "1h",
34
+ "capital_frac": 0.085
35
  }
36
  ]
37
  }
reports/funding_arb.log ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ 2026-05-26 11:21:01,628 - INFO - Health-check server on port 7860
2
+ 2026-05-26 11:21:14,671 - WARNING - SSL error — disabling verification.
3
+ 2026-05-26 11:21:25,451 - WARNING - SSL error — disabling verification.
4
+ 2026-05-26 11:24:56,770 - WARNING - SSL error — disabling verification.
reports/funding_monitor.log ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ 2026-05-26 11:16:19,059 - INFO - Single-poll mode.
2
+ 2026-05-26 11:16:19,172 - WARNING - SSL error — retrying without verification.
3
+ 2026-05-26 11:16:19,648 - WARNING - SSL error — retrying without verification.
4
+ 2026-05-26 11:18:06,707 - INFO - Single-poll mode.
5
+ 2026-05-26 11:18:06,828 - WARNING - SSL error — retrying without verification.
6
+ 2026-05-26 11:18:07,324 - WARNING - SSL error — retrying without verification.
strategy.py DELETED
@@ -1,194 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Strategy: Mean Reversion Bollinger Bounce for SOL (15m/1h)
3
- With optional ML regime filter (regime.py).
4
-
5
- Capitalizes on sideways chop by fading extremes, and now uses a Hidden Markov
6
- Model-based regime classifier to ONLY trade when the market is in 'ranging' mode.
7
- In trending regimes, signals are suppressed to avoid getting run over.
8
-
9
- Core Logic:
10
- - Uses Bollinger Bands (2.25 Standard Deviations) to find extremes.
11
- - ADX Filter: Only takes trades when ADX < 25 (market is ranging).
12
- - RSI Filter: Adds confluence for oversold/overbought conditions.
13
- - HMM Regime Gate: Skips signal if ML says market is trending (not ranging).
14
- - Target: The mean (20 SMA).
15
-
16
- To disable ML regime filter: set use_regime_filter=False in config.
17
- """
18
-
19
- from __future__ import annotations
20
- from dataclasses import dataclass
21
- from typing import Dict, Optional, Tuple
22
- import numpy as np
23
- import pandas as pd
24
-
25
- try:
26
- from regime import RegimeDetector
27
- REGIME_AVAILABLE = True
28
- except ImportError:
29
- REGIME_AVAILABLE = False
30
-
31
-
32
- @dataclass
33
- class TradeIntent:
34
- coin: str
35
- timeframe: str
36
- side: str
37
- entry_price: float
38
- stop_loss: float
39
- take_profit: float
40
- confidence: float
41
-
42
-
43
- class StrategyEngine:
44
- def __init__(self, config: Dict):
45
- self.config = config
46
- self._use_regime = config.get("use_regime_filter", True) and REGIME_AVAILABLE
47
- self._regime_detector: Optional[object] = None # RegimeDetector instance
48
-
49
- # ------------------------------------------------------------------
50
- # Fit ML regime model on a full DataFrame (call once before backtesting)
51
- # ------------------------------------------------------------------
52
- def fit_regime(self, df: pd.DataFrame) -> None:
53
- """Train the HMM regime detector on the provided OHLCV+indicator DataFrame."""
54
- if not self._use_regime:
55
- return
56
- det = RegimeDetector(n_states=3)
57
- det.fit(df)
58
- self._regime_detector = det
59
- dist = det.regime_distribution()
60
- print(f"[Regime] Distribution: {dist}")
61
-
62
- def regime_for_row(self, row: pd.Series) -> str:
63
- """Return regime label for a single row. 'unknown' if not fitted."""
64
- if not self._use_regime or self._regime_detector is None:
65
- return "ranging" # pass-through: assume ranging (strategy works)
66
- return self._regime_detector.predict(row)
67
-
68
- # ------------------------------------------------------------------
69
- # Indicators (unchanged)
70
- # ------------------------------------------------------------------
71
- def add_indicators(self, df: pd.DataFrame) -> pd.DataFrame:
72
- d = df.copy()
73
-
74
- # Bollinger Bands (20, 2.25 SD)
75
- d["sma_20"] = d["close"].rolling(window=20).mean()
76
- d["std_20"] = d["close"].rolling(window=20).std()
77
- d["bb_upper"] = d["sma_20"] + (2.25 * d["std_20"])
78
- d["bb_lower"] = d["sma_20"] - (2.25 * d["std_20"])
79
-
80
- # RSI-14
81
- delta = d["close"].diff()
82
- up = delta.clip(lower=0); dn = -delta.clip(upper=0)
83
- rs = up.rolling(14).mean() / dn.rolling(14).mean().replace(0, np.nan)
84
- d["rsi"] = 100 - (100 / (1 + rs))
85
-
86
- # ATR-14
87
- tr = pd.concat([
88
- d["high"] - d["low"],
89
- (d["high"] - d["close"].shift()).abs(),
90
- (d["low"] - d["close"].shift()).abs(),
91
- ], axis=1).max(axis=1)
92
- d["atr"] = tr.rolling(14).mean()
93
- d["atr_pct"] = d["atr"] / d["close"] * 100
94
- d["atr_pct_sma"] = d["atr_pct"].rolling(100).mean()
95
-
96
- # ADX-14 (Trend strength filter)
97
- pdm = d["high"].diff().clip(lower=0)
98
- mdm = (-d["low"].diff()).clip(lower=0)
99
- pdm = pdm.where(pdm > mdm, 0.0)
100
- mdm = mdm.where(mdm > pdm, 0.0)
101
- pdi = 100 * pdm.rolling(14).mean() / d["atr"].replace(0, np.nan)
102
- mdi = 100 * mdm.rolling(14).mean() / d["atr"].replace(0, np.nan)
103
- dx = 100 * (pdi - mdi).abs() / (pdi + mdi).replace(0, np.nan)
104
- d["adx"] = dx.rolling(14).mean()
105
-
106
- # Professional Indicators
107
- d["ema_9"] = d["close"].ewm(span=9, adjust=False).mean()
108
- d["ema_21"] = d["close"].ewm(span=21, adjust=False).mean()
109
-
110
- # Donchian Channels (20)
111
- d["donchian_upper"] = d["high"].rolling(20).max()
112
- d["donchian_lower"] = d["low"].rolling(20).min()
113
-
114
- # Volume Filter
115
- d["volume_sma"] = d["volume"].rolling(20).mean()
116
-
117
- # Structural extremes
118
- d["swing_low5"] = d["low"].rolling(5).min()
119
- d["swing_high5"] = d["high"].rolling(5).max()
120
-
121
- return d.dropna()
122
-
123
- def signal_for_row(self, row: pd.Series) -> int:
124
- regime = self.regime_for_row(row)
125
-
126
- close = float(row["close"])
127
- open_price = float(row["open"])
128
- rsi = float(row["rsi"])
129
- vol = float(row["volume"])
130
- vol_sma = float(row["volume_sma"])
131
-
132
- # Volume Confirmation
133
- vol_filter = vol > (vol_sma * 0.9)
134
-
135
- # --- INSTITUTIONAL TREND-FOLLOWING ---
136
- if regime == "uptrend":
137
- # 1. Breakout Confirmation
138
- if close > (row["donchian_upper"] * 1.002) and vol_filter:
139
- return 1
140
- # 2. Momentum Confluence
141
- if close > row["ema_9"] and close > row["ema_21"] and rsi > 60:
142
- return 1
143
-
144
- elif regime == "downtrend":
145
- # 1. Breakout Confirmation
146
- if close < (row["donchian_lower"] * 0.998) and vol_filter:
147
- return -1
148
- # 2. Momentum Confluence
149
- if close < row["ema_9"] and close < row["ema_21"] and rsi < 40:
150
- return -1
151
-
152
- # In Ranging regimes, we wait for a transition to trend
153
- return 0
154
-
155
- def risk_levels(
156
- self, entry: float, atr: float, side: int, atr_pct: float = 0.0, row: Optional[pd.Series] = None
157
- ) -> Tuple[float, float]:
158
- # Institutional Standard: 3.0 ATR Stop to survive noise, dynamic target
159
- if side == 1:
160
- sl = entry - (3.0 * atr)
161
- tp = entry + (7.0 * atr) # Wide TP, reliance on trail_stop
162
- else:
163
- sl = entry + (3.0 * atr)
164
- tp = entry - (7.0 * atr)
165
-
166
- return sl, tp
167
-
168
- def trail_stop(self, side: int, entry: float, current_sl: float, close: float, atr: float) -> float:
169
- """Professional Trailing Stop: Moves SL as trade progresses in profit."""
170
- if side == 1: # Long
171
- # Move SL up to (close - 3.5*ATR) but NEVER move it down
172
- new_sl = max(current_sl, close - (3.5 * atr))
173
- return new_sl
174
- else: # Short
175
- # Move SL down to (close + 3.5*ATR) but NEVER move it up
176
- new_sl = min(current_sl, close + (3.5 * atr))
177
- return new_sl
178
-
179
- # ------------------------------------------------------------------
180
- # Build intent (live scanning)
181
- # ------------------------------------------------------------------
182
- def build_intent(self, coin: str, timeframe: str, row: pd.Series) -> Optional[TradeIntent]:
183
- signal = self.signal_for_row(row)
184
- if signal == 0:
185
- return None
186
- side = "long" if signal == 1 else "short"
187
- entry = float(row["close"])
188
- stop, tp = self.risk_levels(entry, float(row["atr"]), signal, float(row["atr_pct"]), row)
189
- conf = round(min(0.95, max(0.5, 1.0 - (row["adx"] / 50.0))), 2)
190
- return TradeIntent(coin, timeframe, side, entry, stop, tp, conf)
191
-
192
-
193
-
194
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
strategy_breakout.py DELETED
@@ -1,152 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Strategy: Volatility & Volume Breakout (1h/4h)
4
- ==============================================
5
- This strategy targets high-momentum breakouts from consolidation zones.
6
-
7
- Core Logic:
8
- - Donchian Channels: Defines the consolidation range. Breakout above/below triggers interest.
9
- - SuperTrend: Confirms the prevailing trend direction to avoid counter-trend fakeouts.
10
- - Volume Surge: Requires volume to be at least 50% higher than its 20-period SMA.
11
- - ADX Filter: Ensures the market is actually starting to trend (ADX > 25).
12
-
13
- Risk Management:
14
- - Stop Loss: 2.0 * ATR for breathing room.
15
- - Take Profit: 4.0 * ATR (2:1 Reward-to-Risk).
16
- - Trailing Stop: Moves with price once in profit.
17
- """
18
-
19
- from __future__ import annotations
20
- from dataclasses import dataclass
21
- from typing import Dict, Optional, Tuple
22
- import numpy as np
23
- import pandas as pd
24
- import ta
25
-
26
- @dataclass
27
- class TradeIntent:
28
- coin: str
29
- timeframe: str
30
- side: str
31
- entry_price: float
32
- stop_loss: float
33
- take_profit: float
34
- confidence: float
35
-
36
- class StrategyEngine:
37
- def __init__(self, config: Dict):
38
- self.config = config
39
- self.use_maker_orders = False # Breakouts often need market entries
40
-
41
- def add_indicators(self, df: pd.DataFrame) -> pd.DataFrame:
42
- d = df.copy()
43
-
44
- # Donchian Channels (20) - Shifted to avoid lookahead (breakout of PREVIOUS 20 highs)
45
- d["donchian_high"] = d["high"].shift(1).rolling(20).max()
46
- d["donchian_low"] = d["low"].shift(1).rolling(20).min()
47
-
48
- # ATR (14)
49
- d["atr"] = ta.volatility.AverageTrueRange(d["high"], d["low"], d["close"], window=14).average_true_range()
50
- d["atr_pct"] = d["atr"] / d["close"] * 100
51
-
52
- # ADX (14)
53
- adx_ind = ta.trend.ADXIndicator(d["high"], d["low"], d["close"], window=14)
54
- d["adx"] = adx_ind.adx()
55
-
56
- # Volume SMA (20)
57
- d["volume_sma"] = d["volume"].rolling(20).mean()
58
-
59
- # SuperTrend (10, 3.0)
60
- multiplier = 3.0
61
- period = 10
62
- hl2 = (d["high"] + d["low"]) / 2
63
-
64
- # Basic Upper/Lower bands
65
- d["st_upper_basic"] = hl2 + (multiplier * d["atr"])
66
- d["st_lower_basic"] = hl2 - (multiplier * d["atr"])
67
-
68
- # Final Bands
69
- upper_band = d["st_upper_basic"].values
70
- lower_band = d["st_lower_basic"].values
71
- close = d["close"].values
72
-
73
- final_upper = np.zeros(len(d))
74
- final_lower = np.zeros(len(d))
75
- st = np.zeros(len(d))
76
- direction = np.zeros(len(d))
77
-
78
- for i in range(1, len(d)):
79
- # Final Upper Band
80
- if upper_band[i] < final_upper[i-1] or close[i-1] > final_upper[i-1]:
81
- final_upper[i] = upper_band[i]
82
- else:
83
- final_upper[i] = final_upper[i-1]
84
-
85
- # Final Lower Band
86
- if lower_band[i] > final_lower[i-1] or close[i-1] < final_lower[i-1]:
87
- final_lower[i] = lower_band[i]
88
- else:
89
- final_lower[i] = final_lower[i-1]
90
-
91
- # SuperTrend
92
- if i == 1:
93
- direction[i] = 1
94
- elif st[i-1] == final_upper[i-1]:
95
- direction[i] = -1 if close[i] <= final_upper[i] else 1
96
- else:
97
- direction[i] = 1 if close[i] >= final_lower[i] else -1
98
-
99
- st[i] = final_upper[i] if direction[i] == -1 else final_lower[i]
100
-
101
- d["supertrend"] = st
102
- d["st_direction"] = direction
103
-
104
- return d.dropna()
105
-
106
- def signal_for_row(self, row: pd.Series) -> int:
107
- close = float(row["close"])
108
- vol = float(row["volume"])
109
- vol_sma = float(row["volume_sma"])
110
- adx = float(row["adx"])
111
- st_dir = int(row["st_direction"])
112
-
113
- # Volume Surge: 1.5x average
114
- vol_surge = vol > (vol_sma * 1.5)
115
- # Trending: ADX > 25
116
- trending = adx > 25
117
-
118
- # LONG: Breakout High + SuperTrend Up + Volume Surge + Trending
119
- if close > row["donchian_high"] and st_dir == 1 and vol_surge and trending:
120
- return 1
121
-
122
- # SHORT: Breakout Low + SuperTrend Down + Volume Surge + Trending
123
- if close < row["donchian_low"] and st_dir == -1 and vol_surge and trending:
124
- return -1
125
-
126
- return 0
127
-
128
- def risk_levels(self, entry: float, atr: float, side: int, atr_pct: float = 0.0, row: Optional[pd.Series] = None) -> Tuple[float, float]:
129
- if side == 1:
130
- sl = entry - (2.0 * atr)
131
- tp = entry + (4.0 * atr)
132
- else:
133
- sl = entry + (2.0 * atr)
134
- tp = entry - (4.0 * atr)
135
- return sl, tp
136
-
137
- def trail_stop(self, side: int, entry: float, current_sl: float, close: float, atr: float) -> float:
138
- # Tighten stop as price moves in our favor
139
- if side == 1:
140
- return max(current_sl, close - (2.5 * atr))
141
- else:
142
- return min(current_sl, close + (2.5 * atr))
143
-
144
- def build_intent(self, coin: str, timeframe: str, row: pd.Series) -> Optional[TradeIntent]:
145
- signal = self.signal_for_row(row)
146
- if signal == 0:
147
- return None
148
- side = "long" if signal == 1 else "short"
149
- entry = float(row["close"])
150
- stop, tp = self.risk_levels(entry, float(row["atr"]), signal, float(row["atr_pct"]), row)
151
- conf = 0.8
152
- return TradeIntent(coin, timeframe, side, entry, stop, tp, conf)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
strategy_hma_cross.py DELETED
@@ -1,110 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Strategy: Hull Moving Average (HMA) Cross (1h)
4
- ==============================================
5
- Uses the Hull Moving Average to reduce lag while maintaining smoothness.
6
-
7
- Core Logic:
8
- - Fast HMA (9) and Slow HMA (21).
9
- - Entry (Long): HMA(9) crosses above HMA(21) AND RSI > 55.
10
- - Entry (Short): HMA(9) crosses below HMA(21) AND RSI < 45.
11
- - Exit: Opposite HMA cross or Stop Loss / Take Profit.
12
- - ATR-based risk management.
13
- """
14
-
15
- from __future__ import annotations
16
- from dataclasses import dataclass
17
- from typing import Dict, Optional, Tuple
18
- import numpy as np
19
- import pandas as pd
20
- import ta
21
-
22
- @dataclass
23
- class TradeIntent:
24
- coin: str
25
- timeframe: str
26
- side: str
27
- entry_price: float
28
- stop_loss: float
29
- take_profit: float
30
- confidence: float
31
-
32
- def hma(series, window):
33
- """Hull Moving Average implementation."""
34
- w_half = int(window / 2)
35
- w_sqrt = int(np.sqrt(window))
36
-
37
- wma_half = series.rolling(w_half).apply(lambda x: np.dot(x, np.arange(1, w_half + 1)) / np.sum(np.arange(1, w_half + 1)), raw=True)
38
- wma_full = series.rolling(window).apply(lambda x: np.dot(x, np.arange(1, window + 1)) / np.sum(np.arange(1, window + 1)), raw=True)
39
-
40
- diff = 2 * wma_half - wma_full
41
- hma = diff.rolling(w_sqrt).apply(lambda x: np.dot(x, np.arange(1, w_sqrt + 1)) / np.sum(np.arange(1, w_sqrt + 1)), raw=True)
42
- return hma
43
-
44
- class StrategyEngine:
45
- def __init__(self, config: Dict):
46
- self.config = config
47
- self.use_maker_orders = False # Trend crosses usually need market orders
48
-
49
- def add_indicators(self, df: pd.DataFrame) -> pd.DataFrame:
50
- d = df.copy()
51
-
52
- # HMA 9 and 21
53
- d["hma_fast"] = hma(d["close"], 9)
54
- d["hma_slow"] = hma(d["close"], 21)
55
-
56
- # RSI (14)
57
- d["rsi"] = ta.momentum.RSIIndicator(d["close"], window=14).rsi()
58
-
59
- # ATR (14)
60
- d["atr"] = ta.volatility.AverageTrueRange(d["high"], d["low"], d["close"], window=14).average_true_range()
61
- d["atr_pct"] = d["atr"] / d["close"] * 100
62
-
63
- # Previous values for crossover detection
64
- d["hma_fast_prev"] = d["hma_fast"].shift(1)
65
- d["hma_slow_prev"] = d["hma_slow"].shift(1)
66
-
67
- return d.dropna()
68
-
69
- def signal_for_row(self, row: pd.Series) -> int:
70
- f = float(row["hma_fast"])
71
- s = float(row["hma_slow"])
72
- f_prev = float(row["hma_fast_prev"])
73
- s_prev = float(row["hma_slow_prev"])
74
- rsi = float(row["rsi"])
75
-
76
- # LONG: HMA Fast crosses ABOVE HMA Slow + RSI > 55
77
- if f_prev <= s_prev and f > s and rsi > 55:
78
- return 1
79
-
80
- # SHORT: HMA Fast crosses BELOW HMA Slow + RSI < 45
81
- if f_prev >= s_prev and f < s and rsi < 45:
82
- return -1
83
-
84
- return 0
85
-
86
- def risk_levels(self, entry: float, atr: float, side: int, atr_pct: float = 0.0, row: Optional[pd.Series] = None) -> Tuple[float, float]:
87
- # 1.5 ATR Stop, 3.0 ATR Target (2:1 RR)
88
- if side == 1:
89
- sl = entry - (1.5 * atr)
90
- tp = entry + (3.0 * atr)
91
- else:
92
- sl = entry + (1.5 * atr)
93
- tp = entry - (3.0 * atr)
94
- return sl, tp
95
-
96
- def trail_stop(self, side: int, entry: float, current_sl: float, close: float, atr: float) -> float:
97
- if side == 1:
98
- return max(current_sl, close - (2.0 * atr))
99
- else:
100
- return min(current_sl, close + (2.0 * atr))
101
-
102
- def build_intent(self, coin: str, timeframe: str, row: pd.Series) -> Optional[TradeIntent]:
103
- signal = self.signal_for_row(row)
104
- if signal == 0:
105
- return None
106
- side = "long" if signal == 1 else "short"
107
- entry = float(row["close"])
108
- stop, tp = self.risk_levels(entry, float(row["atr"]), signal, float(row["atr_pct"]), row)
109
- conf = 0.75
110
- return TradeIntent(coin, timeframe, side, entry, stop, tp, conf)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
strategy_mean_reversion_zscore.py DELETED
@@ -1,116 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Strategy: Z-Score Mean Reversion (1h)
4
- =====================================
5
- This strategy identifies extreme price deviations from the mean (20-period SMA)
6
- and trades the reversion back to that mean, with 200 EMA directional filter.
7
-
8
- Core Logic:
9
- - Z-Score: (Close - SMA) / StdDev. Measures how many standard deviations price is from the mean.
10
- - Entry (Long): Z-Score < -1.5 (Oversold) AND Price > 200 EMA (reversion in uptrend).
11
- - Entry (Short): Z-Score > 1.5 (Overbought) AND Price < 200 EMA (reversion in downtrend).
12
- - Exit: Price hits the SMA (Z-Score returns to 0) or Stop Loss is hit.
13
- - Filter: ADX < 30 (avoid strong trends), 200 EMA for trend direction.
14
- - Philosophy: Trade extreme reversals only in the direction of the longer-term trend.
15
- """
16
-
17
- from __future__ import annotations
18
- from dataclasses import dataclass
19
- from typing import Dict, Optional, Tuple
20
- import numpy as np
21
- import pandas as pd
22
- import ta
23
-
24
- @dataclass
25
- class TradeIntent:
26
- coin: str
27
- timeframe: str
28
- side: str
29
- entry_price: float
30
- stop_loss: float
31
- take_profit: float
32
- confidence: float
33
-
34
- class StrategyEngine:
35
- def __init__(self, config: Dict):
36
- self.config = config
37
- self.use_maker_orders = True # Mean reversion can often use limit orders at extremes
38
-
39
- def add_indicators(self, df: pd.DataFrame) -> pd.DataFrame:
40
- d = df.copy()
41
-
42
- # SMA (20) and StdDev (20)
43
- d["sma"] = d["close"].rolling(20).mean()
44
- d["std"] = d["close"].rolling(20).std()
45
-
46
- # Z-Score
47
- d["zscore"] = (d["close"] - d["sma"]) / d["std"]
48
-
49
- # EMA (200) for trend filter
50
- d["ema200"] = d["close"].ewm(span=200, adjust=False).mean()
51
-
52
- # ATR (14) for stop loss
53
- d["atr"] = ta.volatility.AverageTrueRange(d["high"], d["low"], d["close"], window=14).average_true_range()
54
- d["atr_pct"] = d["atr"] / d["close"] * 100
55
-
56
- # ADX (14) to filter out strong trends (lowering threshold slightly)
57
- adx_ind = ta.trend.ADXIndicator(d["high"], d["low"], d["close"], window=14)
58
- d["adx"] = adx_ind.adx()
59
-
60
- return d.dropna()
61
-
62
- def signal_for_row(self, row: pd.Series) -> int:
63
- z = float(row["zscore"])
64
- adx = float(row["adx"])
65
- close = float(row["close"])
66
- ema200 = float(row["ema200"])
67
-
68
- # Only trade in moderately trending or ranging markets
69
- if adx > 30:
70
- return 0
71
-
72
- # ── 200 EMA directional filter (only trade extreme reversion in direction of trend) ──
73
- # LONG only if z-score is BELOW -1.5 AND price is ABOVE 200 EMA (reversion in uptrend)
74
- # This avoids catching falling knives in downtrends
75
- if z < -1.5 and close > ema200:
76
- return 1
77
-
78
- # SHORT only if z-score is ABOVE 1.5 AND price is BELOW 200 EMA (reversion in downtrend)
79
- # This avoids catching rallies in uptrends
80
- if z > 1.5 and close < ema200:
81
- return -1
82
-
83
- return 0
84
-
85
- def risk_levels(self, entry: float, atr: float, side: int, atr_pct: float = 0.0, row: Optional[pd.Series] = None) -> Tuple[float, float]:
86
- # TP is the mean (SMA), which we can approximate as the current entry minus/plus some distance
87
- # but the backtest engine expects a fixed TP.
88
- # Since we want to exit at mean, let's set TP to 1.5 * ATR and SL to 1.5 * ATR for a 1:1 ratio.
89
- # Actually, if we are at Z=2.0, reverting to 0 is roughly 2.0 * StdDev.
90
-
91
- # Let's use a dynamic TP based on the SMA if available in row
92
- if row is not None and "sma" in row:
93
- tp = float(row["sma"])
94
- else:
95
- tp = entry + (2.0 * atr) if side == 1 else entry - (2.0 * atr)
96
-
97
- if side == 1:
98
- sl = entry - (2.0 * atr)
99
- else:
100
- sl = entry + (2.0 * atr)
101
-
102
- return sl, tp
103
-
104
- def trail_stop(self, side: int, entry: float, current_sl: float, close: float, atr: float) -> float:
105
- # Mean reversion usually doesn't trail stop, or trails very loosely
106
- return current_sl
107
-
108
- def build_intent(self, coin: str, timeframe: str, row: pd.Series) -> Optional[TradeIntent]:
109
- signal = self.signal_for_row(row)
110
- if signal == 0:
111
- return None
112
- side = "long" if signal == 1 else "short"
113
- entry = float(row["close"])
114
- stop, tp = self.risk_levels(entry, float(row["atr"]), signal, float(row["atr_pct"]), row)
115
- conf = 0.7
116
- return TradeIntent(coin, timeframe, side, entry, stop, tp, conf)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
strategy_vw_reversion.py DELETED
@@ -1,116 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Strategy: Volatility-Weighted Mean Reversion (1h)
4
- ==================================================
5
- Combines trend-alignment with high-conviction mean reversion entries.
6
- Inspired by the 'hype' strategy's use of multi-indicator filters.
7
-
8
- Core Logic:
9
- - Trend: 200-EMA for long-term direction.
10
- - Momentum: RSI(14) with tight entry windows (Long: 30-45, Short: 55-70).
11
- - Volatility: Entry ONLY when price touches/crosses Bollinger Band extremes.
12
- - Filters: ADX < 35 (not parabolic) and Volume > 0.9x SMA20.
13
- - Target: The SMA(20) "Mean".
14
- - Risk: 2.0 ATR Stop, SMA(20) Take Profit (min 1.5:1 RR).
15
- """
16
-
17
- from __future__ import annotations
18
- from dataclasses import dataclass
19
- from typing import Dict, Optional, Tuple
20
- import numpy as np
21
- import pandas as pd
22
- import ta
23
-
24
- @dataclass
25
- class TradeIntent:
26
- coin: str
27
- timeframe: str
28
- side: str
29
- entry_price: float
30
- stop_loss: float
31
- take_profit: float
32
- confidence: float
33
-
34
- class StrategyEngine:
35
- def __init__(self, config: Dict):
36
- self.config = config
37
- self.use_maker_orders = True
38
-
39
- def add_indicators(self, df: pd.DataFrame) -> pd.DataFrame:
40
- d = df.copy()
41
-
42
- # Trend
43
- d["ema200"] = d["close"].ewm(span=200, adjust=False).mean()
44
-
45
- # Volatility (Bollinger Bands 20, 2.0)
46
- bb = ta.volatility.BollingerBands(d["close"], window=20, window_dev=2.0)
47
- d["bb_high"] = bb.bollinger_hband()
48
- d["bb_low"] = bb.bollinger_lband()
49
- d["sma20"] = bb.bollinger_mavg()
50
-
51
- # Momentum
52
- d["rsi"] = ta.momentum.RSIIndicator(d["close"], window=14).rsi()
53
- d["adx"] = ta.trend.ADXIndicator(d["high"], d["low"], d["close"], window=14).adx()
54
-
55
- # Volume
56
- d["volume_sma"] = d["volume"].rolling(20).mean()
57
-
58
- # ATR for risk
59
- d["atr"] = ta.volatility.AverageTrueRange(d["high"], d["low"], d["close"], window=14).average_true_range()
60
- d["atr_pct"] = d["atr"] / d["close"] * 100
61
-
62
- return d.dropna()
63
-
64
- def signal_for_row(self, row: pd.Series) -> int:
65
- close = float(row["close"])
66
- ema200 = float(row["ema200"])
67
- bb_high = float(row["bb_high"])
68
- bb_low = float(row["bb_low"])
69
- rsi = float(row["rsi"])
70
- adx = float(row["adx"])
71
- vol = float(row["volume"])
72
- vol_sma = float(row["volume_sma"])
73
-
74
- # Global Filters
75
- if adx > 35 or vol < vol_sma * 0.9:
76
- return 0
77
-
78
- # LONG: Above EMA200 (uptrend) AND price touches/below BB_LOW AND RSI in 30-45 zone
79
- if close > ema200 and close <= bb_low * 1.002 and 30 <= rsi <= 45:
80
- return 1
81
-
82
- # SHORT: Below EMA200 (downtrend) AND price touches/above BB_HIGH AND RSI in 55-70 zone
83
- if close < ema200 and close >= bb_high * 0.998 and 55 <= rsi <= 70:
84
- return -1
85
-
86
- return 0
87
-
88
- def risk_levels(self, entry: float, atr: float, side: int, atr_pct: float = 0.0, row: Optional[pd.Series] = None) -> Tuple[float, float]:
89
- # TP is the mean (SMA20)
90
- if row is not None and "sma20" in row.index:
91
- tp = float(row["sma20"])
92
- else:
93
- tp = entry + (2.0 * atr) if side == 1 else entry - (2.0 * atr)
94
-
95
- # SL is 2.0 ATR
96
- if side == 1:
97
- sl = entry - (2.0 * atr)
98
- # Ensure TP is at least slightly above entry, else use ATR
99
- if tp <= entry + (0.5 * atr): tp = entry + (2.5 * atr)
100
- else:
101
- sl = entry + (2.0 * atr)
102
- if tp >= entry - (0.5 * atr): tp = entry - (2.5 * atr)
103
-
104
- return sl, tp
105
-
106
- def trail_stop(self, side: int, entry: float, current_sl: float, close: float, atr: float) -> float:
107
- return current_sl
108
-
109
- def build_intent(self, coin: str, timeframe: str, row: pd.Series) -> Optional[TradeIntent]:
110
- signal = self.signal_for_row(row)
111
- if signal == 0:
112
- return None
113
- side = "long" if signal == 1 else "short"
114
- entry = float(row["close"])
115
- sl, tp = self.risk_levels(entry, float(row["atr"]), signal, 0, row)
116
- return TradeIntent(coin, timeframe, side, entry, sl, tp, 0.8)