feat: add per-symbol strategy params with sweep-optimized values
Support per-symbol strategy parameters (ATR_SL_MULT_XRPUSDT, etc.) via env vars, falling back to global defaults. Sweep results: - XRPUSDT: SL=1.5 TP=4.0 ADX=30 (PF 2.39, Sharpe 61.0) - TRXUSDT: SL=1.0 TP=4.0 ADX=30 (PF 3.87, Sharpe 62.8) - DOGEUSDT: SL=2.0 TP=2.0 ADX=30 (PF 1.80, Sharpe 44.1) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
14
.env.example
14
.env.example
@@ -8,11 +8,25 @@ DISCORD_WEBHOOK_URL=
|
|||||||
ML_THRESHOLD=0.55
|
ML_THRESHOLD=0.55
|
||||||
NO_ML_FILTER=true
|
NO_ML_FILTER=true
|
||||||
MAX_SAME_DIRECTION=2
|
MAX_SAME_DIRECTION=2
|
||||||
|
# Global defaults (fallback when no per-symbol override)
|
||||||
ATR_SL_MULT=2.0
|
ATR_SL_MULT=2.0
|
||||||
ATR_TP_MULT=2.0
|
ATR_TP_MULT=2.0
|
||||||
SIGNAL_THRESHOLD=3
|
SIGNAL_THRESHOLD=3
|
||||||
ADX_THRESHOLD=25
|
ADX_THRESHOLD=25
|
||||||
VOL_MULTIPLIER=2.5
|
VOL_MULTIPLIER=2.5
|
||||||
|
|
||||||
|
# Per-symbol strategy params (2026-03-17 sweep optimized)
|
||||||
|
ATR_SL_MULT_XRPUSDT=1.5
|
||||||
|
ATR_TP_MULT_XRPUSDT=4.0
|
||||||
|
ADX_THRESHOLD_XRPUSDT=30
|
||||||
|
|
||||||
|
ATR_SL_MULT_TRXUSDT=1.0
|
||||||
|
ATR_TP_MULT_TRXUSDT=4.0
|
||||||
|
ADX_THRESHOLD_TRXUSDT=30
|
||||||
|
|
||||||
|
ATR_SL_MULT_DOGEUSDT=2.0
|
||||||
|
ATR_TP_MULT_DOGEUSDT=2.0
|
||||||
|
ADX_THRESHOLD_DOGEUSDT=30
|
||||||
DASHBOARD_API_URL=http://10.1.10.24:8000
|
DASHBOARD_API_URL=http://10.1.10.24:8000
|
||||||
BINANCE_TESTNET_API_KEY=
|
BINANCE_TESTNET_API_KEY=
|
||||||
BINANCE_TESTNET_API_SECRET=
|
BINANCE_TESTNET_API_SECRET=
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
7511
results/dogeusdt/strategy_sweep_20260317_172011.json
Normal file
7511
results/dogeusdt/strategy_sweep_20260317_172011.json
Normal file
File diff suppressed because it is too large
Load Diff
7511
results/trxusdt/strategy_sweep_20260317_171133.json
Normal file
7511
results/trxusdt/strategy_sweep_20260317_171133.json
Normal file
File diff suppressed because it is too large
Load Diff
7579
results/xrpusdt/strategy_sweep_20260317_172135.json
Normal file
7579
results/xrpusdt/strategy_sweep_20260317_172135.json
Normal file
File diff suppressed because it is too large
Load Diff
18
src/bot.py
18
src/bot.py
@@ -18,6 +18,7 @@ class TradingBot:
|
|||||||
def __init__(self, config: Config, symbol: str = None, risk: RiskManager = None):
|
def __init__(self, config: Config, symbol: str = None, risk: RiskManager = None):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.symbol = symbol or config.symbol
|
self.symbol = symbol or config.symbol
|
||||||
|
self.strategy = config.get_symbol_params(self.symbol)
|
||||||
self.exchange = BinanceFuturesClient(config, symbol=self.symbol)
|
self.exchange = BinanceFuturesClient(config, symbol=self.symbol)
|
||||||
self.notifier = DiscordNotifier(config.discord_webhook_url)
|
self.notifier = DiscordNotifier(config.discord_webhook_url)
|
||||||
self.risk = risk or RiskManager(config)
|
self.risk = risk or RiskManager(config)
|
||||||
@@ -141,9 +142,9 @@ class TradingBot:
|
|||||||
df_with_indicators = ind.calculate_all()
|
df_with_indicators = ind.calculate_all()
|
||||||
raw_signal, signal_detail = ind.get_signal(
|
raw_signal, signal_detail = ind.get_signal(
|
||||||
df_with_indicators,
|
df_with_indicators,
|
||||||
signal_threshold=self.config.signal_threshold,
|
signal_threshold=self.strategy.signal_threshold,
|
||||||
adx_threshold=self.config.adx_threshold,
|
adx_threshold=self.strategy.adx_threshold,
|
||||||
volume_multiplier=self.config.volume_multiplier,
|
volume_multiplier=self.strategy.volume_multiplier,
|
||||||
)
|
)
|
||||||
|
|
||||||
current_price = df_with_indicators["close"].iloc[-1]
|
current_price = df_with_indicators["close"].iloc[-1]
|
||||||
@@ -198,8 +199,8 @@ class TradingBot:
|
|||||||
logger.info(f"[{self.symbol}] 포지션 크기: 잔고={per_symbol_balance:.2f}/{balance:.2f} USDT, 증거금비율={margin_ratio:.1%}, 수량={quantity}")
|
logger.info(f"[{self.symbol}] 포지션 크기: 잔고={per_symbol_balance:.2f}/{balance:.2f} USDT, 증거금비율={margin_ratio:.1%}, 수량={quantity}")
|
||||||
stop_loss, take_profit = Indicators(df).get_atr_stop(
|
stop_loss, take_profit = Indicators(df).get_atr_stop(
|
||||||
df, signal, price,
|
df, signal, price,
|
||||||
atr_sl_mult=self.config.atr_sl_mult,
|
atr_sl_mult=self.strategy.atr_sl_mult,
|
||||||
atr_tp_mult=self.config.atr_tp_mult,
|
atr_tp_mult=self.strategy.atr_tp_mult,
|
||||||
)
|
)
|
||||||
|
|
||||||
notional = quantity * price
|
notional = quantity * price
|
||||||
@@ -429,7 +430,12 @@ class TradingBot:
|
|||||||
self._is_reentering = False
|
self._is_reentering = False
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
logger.info(f"[{self.symbol}] 봇 시작, 레버리지 {self.config.leverage}x")
|
s = self.strategy
|
||||||
|
logger.info(
|
||||||
|
f"[{self.symbol}] 봇 시작, 레버리지 {self.config.leverage}x | "
|
||||||
|
f"SL={s.atr_sl_mult}x TP={s.atr_tp_mult}x Signal≥{s.signal_threshold} "
|
||||||
|
f"ADX≥{s.adx_threshold} Vol≥{s.volume_multiplier}x"
|
||||||
|
)
|
||||||
await self._recover_position()
|
await self._recover_position()
|
||||||
await self._init_oi_history()
|
await self._init_oi_history()
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,16 @@ from dotenv import load_dotenv
|
|||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SymbolStrategyParams:
|
||||||
|
"""Per-symbol strategy parameters (from sweep optimization)."""
|
||||||
|
atr_sl_mult: float = 2.0
|
||||||
|
atr_tp_mult: float = 2.0
|
||||||
|
signal_threshold: int = 3
|
||||||
|
adx_threshold: float = 25.0
|
||||||
|
volume_multiplier: float = 2.5
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Config:
|
class Config:
|
||||||
api_key: str = ""
|
api_key: str = ""
|
||||||
@@ -57,3 +67,24 @@ class Config:
|
|||||||
corr_env = os.getenv("CORRELATION_SYMBOLS", "BTCUSDT,ETHUSDT")
|
corr_env = os.getenv("CORRELATION_SYMBOLS", "BTCUSDT,ETHUSDT")
|
||||||
self.correlation_symbols = [s.strip() for s in corr_env.split(",") if s.strip()]
|
self.correlation_symbols = [s.strip() for s in corr_env.split(",") if s.strip()]
|
||||||
|
|
||||||
|
# Per-symbol strategy params: {symbol: SymbolStrategyParams}
|
||||||
|
self._symbol_params: dict[str, SymbolStrategyParams] = {}
|
||||||
|
for sym in self.symbols:
|
||||||
|
self._symbol_params[sym] = SymbolStrategyParams(
|
||||||
|
atr_sl_mult=float(os.getenv(f"ATR_SL_MULT_{sym}", str(self.atr_sl_mult))),
|
||||||
|
atr_tp_mult=float(os.getenv(f"ATR_TP_MULT_{sym}", str(self.atr_tp_mult))),
|
||||||
|
signal_threshold=int(os.getenv(f"SIGNAL_THRESHOLD_{sym}", str(self.signal_threshold))),
|
||||||
|
adx_threshold=float(os.getenv(f"ADX_THRESHOLD_{sym}", str(self.adx_threshold))),
|
||||||
|
volume_multiplier=float(os.getenv(f"VOL_MULTIPLIER_{sym}", str(self.volume_multiplier))),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_symbol_params(self, symbol: str) -> SymbolStrategyParams:
|
||||||
|
"""Get strategy params for a symbol. Falls back to global defaults."""
|
||||||
|
return self._symbol_params.get(symbol, SymbolStrategyParams(
|
||||||
|
atr_sl_mult=self.atr_sl_mult,
|
||||||
|
atr_tp_mult=self.atr_tp_mult,
|
||||||
|
signal_threshold=self.signal_threshold,
|
||||||
|
adx_threshold=self.adx_threshold,
|
||||||
|
volume_multiplier=self.volume_multiplier,
|
||||||
|
))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user