146 lines
5.3 KiB
Python
146 lines
5.3 KiB
Python
import os
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
_PROJECT_DIR = Path(__file__).resolve().parent
|
|
|
|
try:
|
|
from dotenv import load_dotenv
|
|
load_dotenv()
|
|
except ImportError:
|
|
pass
|
|
|
|
|
|
@dataclass
|
|
class IBConfig:
|
|
host: str = os.getenv("IB_HOST", "127.0.0.1")
|
|
port: int = int(os.getenv("IB_PORT", "4001"))
|
|
client_id: int = int(os.getenv("IB_CLIENT_ID", "1"))
|
|
account: Optional[str] = os.getenv("IB_ACCOUNT", "U4845070")
|
|
is_paper: bool = os.getenv("IB_PAPER", "false").lower() == "true"
|
|
|
|
@property
|
|
def port_label(self) -> str:
|
|
if self.port == 4002:
|
|
return "Gateway Paper (4002)"
|
|
elif self.port == 4001:
|
|
return "Gateway Live (4001)"
|
|
elif self.port == 7497:
|
|
return "TWS Paper (7497)"
|
|
elif self.port == 7496:
|
|
return "TWS Live (7496)"
|
|
return f"Unknown ({self.port})"
|
|
|
|
|
|
@dataclass
|
|
class StockStrategyConfig:
|
|
enabled: bool = True
|
|
symbols: list[str] = field(default_factory=lambda: ["AMZN", "AAPL", "NVDA", "NOK"])
|
|
currency: str = "USD"
|
|
exchange: str = "SMART"
|
|
bar_size: str = "1 min"
|
|
lookback_days: int = 5
|
|
fast_ma_period: int = 20
|
|
slow_ma_period: int = 50
|
|
trade_value_usd: float = 2000.0 # per-trade dollar amount; qty = floor(value / price)
|
|
min_profit_pct: float = 1.5
|
|
stop_loss_pct: float = 2.5
|
|
use_hard_stop: bool = True # place real GTC STP orders at the exchange
|
|
entry_confirm_bars: int = 2 # entry signal must hold for N consecutive bars
|
|
stop_cooldown_minutes: int = 30 # no re-entry on same symbol for N min after a stop-out
|
|
use_trailing_stop: bool = True # once profit >= min_profit, trail the stop below the peak high
|
|
trailing_stop_pct: float = 2.0 # trailing stop distance below peak high
|
|
adx_min: float = 30.0 # ADX trend filter for entries (was 25; 08-06 raised to avoid weak-trend whipsaw)
|
|
require_slow_ma_slope: bool = True # only enter if the slow MA is itself rising (no knife-catching in a downtrend) high
|
|
|
|
|
|
@dataclass
|
|
class ShortTermConfig:
|
|
enabled: bool = True
|
|
symbols: list[str] = field(default_factory=lambda: ["AMZN", "AAPL", "NVDA", "NOK"])
|
|
currency: str = "USD"
|
|
exchange: str = "SMART"
|
|
bar_size: str = "1 min"
|
|
lookback_days: int = 5
|
|
fast_ma_period: int = 5
|
|
slow_ma_period: int = 10
|
|
trade_value_usd: float = 2000.0
|
|
max_hold_days: int = 0 # 0 = unlimited hold (08-05 撤销 5 天上限)
|
|
min_profit_pct: float = 1.5
|
|
stop_loss_pct: float = 2.5
|
|
use_hard_stop: bool = True
|
|
entry_confirm_bars: int = 2
|
|
stop_cooldown_minutes: int = 30
|
|
use_trailing_stop: bool = True
|
|
trailing_stop_pct: float = 2.0
|
|
|
|
|
|
@dataclass
|
|
class ForexStrategyConfig:
|
|
enabled: bool = False
|
|
pairs: list[str] = field(default_factory=lambda: ["EUR.USD", "GBP.USD"])
|
|
exchange: str = "IDEALPRO"
|
|
bar_size: str = "1 min"
|
|
lookback_days: int = 5
|
|
fast_ma_period: int = 12
|
|
slow_ma_period: int = 26
|
|
trade_units: int = 3000
|
|
stop_loss_pct: float = 1.0
|
|
|
|
|
|
@dataclass
|
|
class MeanReversionConfig:
|
|
enabled: bool = True
|
|
symbols: list[str] = field(default_factory=lambda: ["AMZN", "AAPL", "NVDA", "NOK"])
|
|
currency: str = "USD"
|
|
exchange: str = "SMART"
|
|
bar_size: str = "1 min"
|
|
lookback_days: int = 5
|
|
trade_value_usd: float = 2000.0
|
|
rsi_period: int = 14
|
|
rsi_oversold: float = 30.0
|
|
rsi_overbought: float = 70.0
|
|
bb_period: int = 20
|
|
bb_std: float = 2.0
|
|
rapid_drop_pct: float = 1.0
|
|
rapid_drop_bars: int = 5
|
|
recovery_pct: float = 1.5
|
|
min_profit_pct: float = 1.5
|
|
stop_loss_pct: float = 2.5
|
|
use_hard_stop: bool = True
|
|
entry_confirm_bars: int = 2
|
|
stop_cooldown_minutes: int = 30
|
|
use_trailing_stop: bool = True
|
|
trailing_stop_pct: float = 2.0
|
|
trend_ma_period: int = 50 # mean-reversion buys only above this SMA
|
|
|
|
|
|
@dataclass
|
|
class AppConfig:
|
|
ib: IBConfig = field(default_factory=IBConfig)
|
|
stock: StockStrategyConfig = field(default_factory=StockStrategyConfig)
|
|
short_term: ShortTermConfig = field(default_factory=ShortTermConfig)
|
|
forex: ForexStrategyConfig = field(default_factory=ForexStrategyConfig)
|
|
mean_reversion: MeanReversionConfig = field(default_factory=MeanReversionConfig)
|
|
max_retries: int = 5
|
|
retry_delay: int = 5
|
|
loop_interval: float = 60.0
|
|
order_timeout: float = 30.0
|
|
max_positions: int = 12 # global cap on concurrent open lots across all strategies
|
|
max_daily_loss: float = 150.0 # stop opening new positions for the day beyond this realized loss
|
|
sell_only: bool = False # liquidation mode: no new buys, strategies only manage exits
|
|
max_symbol_value_usd: float = 2300.0 # per-symbol total position value cap (all strategies combined)
|
|
sell_cooldown_minutes: int = 30 # global: no strategy may (re)buy a symbol within N min of ANY sell
|
|
sell_improvement_pct: float = 0.5 # beyond the cooldown, re-buy only if price is >=0.5% below last sell
|
|
sell_improvement_window_minutes: int = 120 # how long the price-improvement rule applies after a sell
|
|
symbol_trade_value_usd: dict[str, float] = field(
|
|
# per-symbol per-trade dollar value override (halved for gap-prone NOK, 08-06)
|
|
default_factory=lambda: {"NOK": 1000.0}
|
|
)
|
|
# absolute path: independent of the working directory the bot is started from
|
|
state_file: str = str(_PROJECT_DIR / "bot_state.json")
|
|
|
|
|
|
config = AppConfig()
|