Initial commit: multi-symbol bot with backtest engine and RSI trend strategy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-28 21:09:12 +02:00
commit ad8dfa27d7
32 changed files with 2644 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
"""
Position sizing and trade-permission checks.
"""
import logging
import math
from datetime import date
from . import config
logger = logging.getLogger(__name__)
MIN_LOT = 0.01
MAX_LOT = 100.0
def calculate_lot_size(
symbol_info: dict,
balance: float,
entry_price: float,
sl_price: float,
) -> float:
"""Risk `config.RISK_PER_TRADE` fraction of balance on a single trade."""
risk_amount = balance * config.RISK_PER_TRADE
tick_value = symbol_info["trade_tick_value"]
tick_size = symbol_info["trade_tick_size"]
if tick_value == 0 or tick_size == 0:
return MIN_LOT
pip_size = tick_size * 10
pip_value = tick_value * (pip_size / tick_size)
dist_pips = abs(entry_price - sl_price) / pip_size
if dist_pips < config.MIN_SL_PIPS:
logger.warning("SL too tight for %s: %.1f pips — skipping",
symbol_info.get("name"), dist_pips)
return 0.0
lot = risk_amount / (pip_value * dist_pips)
lot = max(MIN_LOT, min(MAX_LOT, lot))
step = symbol_info.get("volume_step", 0.01)
lot = math.floor(lot / step) * step
return round(lot, 2)
def can_trade(account_info: dict, open_positions: list[dict], state: dict) -> bool:
if len(open_positions) >= config.MAX_POSITIONS:
logger.debug("Max positions reached (%d)", config.MAX_POSITIONS)
return False
today = date.today().isoformat()
daily_pnl = state.get("daily_pnl", {}).get(today, 0.0)
balance = account_info.get("balance", 1.0)
if daily_pnl / balance < -config.MAX_DAILY_LOSS:
logger.warning("Daily loss limit hit: %.1f%% of balance",
-daily_pnl / balance * 100)
return False
return True