Spaces:
Running
Running
Daniel Alnawaz commited on
large changes
Browse files- _backtest_breakout.py +0 -58
- _backtest_hma.py +0 -58
- _backtest_rsi_extreme.py +0 -58
- _backtest_st_pullback.py +0 -58
- _backtest_vw_reversion.py +0 -58
- _backtest_zscore.py +0 -58
- _debug_ml.py +0 -39
- _diversification_analysis.py +0 -131
- _find_coins.py +0 -44
- _scan_30m.py +0 -44
- _scan_all.py +0 -84
- _scan_coins.py +5 -32
- _validate_bots.py +0 -63
- autonomous_bot.py +6 -8
- backtest.py +0 -8
- bot_config.json +10 -16
- reports/funding_arb.log +4 -0
- reports/funding_monitor.log +6 -0
- strategy.py +0 -194
- strategy_breakout.py +0 -152
- strategy_hma_cross.py +0 -110
- strategy_mean_reversion_zscore.py +0 -116
- strategy_vw_reversion.py +0 -116
_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.
|
| 61 |
-
'strategy_conservative': 0.
|
| 62 |
-
'strategy_scalp': 0.
|
| 63 |
-
'
|
| 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 |
-
#
|
| 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 |
-
("
|
| 241 |
-
("
|
| 242 |
],
|
| 243 |
"extended": [
|
| 244 |
-
("strategy_hype",
|
| 245 |
-
("strategy_scalp",
|
| 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 =
|
| 967 |
-
"extended = hype/TIA + scalp/NEAR
|
| 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-
|
| 3 |
"scan_days": 180,
|
| 4 |
-
"ml_backtest_seed_path": "/
|
| 5 |
"slots": [
|
| 6 |
{
|
| 7 |
"strategy": "strategy_hype",
|
| 8 |
"coin": "TIA",
|
| 9 |
"timeframe": "1h",
|
| 10 |
-
"capital_frac": 0.
|
| 11 |
},
|
| 12 |
{
|
| 13 |
"strategy": "strategy_conservative",
|
| 14 |
"coin": "WIF",
|
| 15 |
"timeframe": "1h",
|
| 16 |
-
"capital_frac": 0.
|
| 17 |
},
|
| 18 |
{
|
| 19 |
-
"strategy": "
|
| 20 |
"coin": "ARB",
|
| 21 |
-
"timeframe": "
|
| 22 |
-
"capital_frac": 0.
|
| 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.
|
| 35 |
},
|
| 36 |
{
|
| 37 |
"strategy": "strategy_ml_auto",
|
| 38 |
-
"coin": "
|
| 39 |
"timeframe": "1h",
|
| 40 |
-
"capital_frac": 0.
|
| 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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|