Bug Cleanup

This commit is contained in:
moen0
2026-05-11 19:14:32 +02:00
parent 130da68f4e
commit 473fe3bd88
12 changed files with 348 additions and 150 deletions
+121 -4
View File
@@ -30,18 +30,66 @@ def _apply_break_even_if_triggered(position, candle, strategy):
position["break_even_armed"] = True
def _apply_partial_tp_if_triggered(position, candle, strategy):
if not position:
return
if not getattr(strategy, "use_partial_tp", False):
return
if position.get("partial_tp_taken"):
return
trigger_rr = float(getattr(strategy, "partial_tp_rr", 1.0) or 0.0)
if trigger_rr <= 0:
return
partial_pct = float(getattr(strategy, "partial_tp_percent", 0.0) or 0.0)
if partial_pct <= 0:
return
is_long = position["direction"] == "long"
entry = position["entry_price"]
risk_distance = max(position.get("risk_distance", 0.0), 0.0)
if risk_distance <= 0:
return
trigger_price = entry + (risk_distance * trigger_rr) if is_long else entry - (risk_distance * trigger_rr)
reached_trigger = candle.high >= trigger_price if is_long else candle.low <= trigger_price
if not reached_trigger:
return
lot_size = max(position.get("lot_size", 0.0), 0.0)
if lot_size <= 0:
return
partial_pct = min(partial_pct, 100.0)
partial_lot = lot_size * (partial_pct / 100.0)
if partial_lot <= 0:
return
price_move = (trigger_price - entry) if is_long else (entry - trigger_price)
partial_pnl = price_move * partial_lot
position["lot_size"] = max(lot_size - partial_lot, 0.0)
position["partial_tp_taken"] = True
position["partial_tp_realized_pnl"] = position.get("partial_tp_realized_pnl", 0.0) + partial_pnl
def run_backtest(candles, strategy, starting_balance, risk_reward=1.0,
max_daily_loss=0.0, max_consecutive_losses=0, risk_pct=1.0):
trades = []
position = None
consecutive_losses = 0
daily_pnl = defaultdict(float)
equity = float(starting_balance)
if hasattr(strategy, "prepare"):
strategy.prepare(candles)
for i, candle in enumerate(candles):
if position:
_apply_break_even_if_triggered(position, candle, strategy)
_apply_partial_tp_if_triggered(position, candle, strategy)
is_long = position["direction"] == "long"
sl, tp = position["stop_loss"], position["take_profit"]
@@ -53,7 +101,8 @@ def run_backtest(candles, strategy, starting_balance, risk_reward=1.0,
exit_price = sl if hit_sl else tp
price_move = (exit_price - position["entry_price"]) if is_long else (position["entry_price"] - exit_price)
lot_size = max(position.get("lot_size", 0.0), 0.0)
pnl = price_move * lot_size
partial_pnl = float(position.get("partial_tp_realized_pnl", 0.0) or 0.0)
pnl = (price_move * lot_size) + partial_pnl
risk_distance = max(position.get("risk_distance", 0.0), 1e-12)
r_multiple = price_move / risk_distance
@@ -65,8 +114,11 @@ def run_backtest(candles, strategy, starting_balance, risk_reward=1.0,
exit_price=exit_price,
pnl=pnl,
r_multiple=r_multiple,
partial_tp_taken=bool(position.get("partial_tp_taken", False)),
partial_tp_realized_pnl=partial_pnl,
))
position = None
equity += pnl
if pnl <= 0:
consecutive_losses += 1
@@ -76,6 +128,8 @@ def run_backtest(candles, strategy, starting_balance, risk_reward=1.0,
daily_pnl[candle.time_open.date()] += pnl
if position is None:
if equity <= 0:
continue
if max_consecutive_losses > 0 and consecutive_losses >= max_consecutive_losses:
continue
if max_daily_loss > 0:
@@ -99,7 +153,7 @@ def run_backtest(candles, strategy, starting_balance, risk_reward=1.0,
):
continue
risk_amount = starting_balance * (risk_pct / 100)
risk_amount = equity * (risk_pct / 100)
if risk_amount <= 0 or not math.isfinite(risk_amount):
continue
@@ -118,8 +172,35 @@ def run_backtest(candles, strategy, starting_balance, risk_reward=1.0,
"risk_distance": sl_distance,
"lot_size": lot_size,
"break_even_armed": False,
"partial_tp_taken": False,
"partial_tp_realized_pnl": 0.0,
}
if position and candles:
last_candle = candles[-1]
is_long = position["direction"] == "long"
exit_price = last_candle.close
price_move = (exit_price - position["entry_price"]) if is_long else (position["entry_price"] - exit_price)
lot_size = max(position.get("lot_size", 0.0), 0.0)
partial_pnl = float(position.get("partial_tp_realized_pnl", 0.0) or 0.0)
pnl = (price_move * lot_size) + partial_pnl
risk_distance = max(position.get("risk_distance", 0.0), 1e-12)
r_multiple = price_move / risk_distance
trades.append(Trade(
enter_time=position["enter_time"],
enter_price=position["entry_price"],
direction=position["direction"],
exit_time=last_candle.time_open,
exit_price=exit_price,
pnl=pnl,
r_multiple=r_multiple,
partial_tp_taken=bool(position.get("partial_tp_taken", False)),
partial_tp_realized_pnl=partial_pnl,
))
equity += pnl
daily_pnl[last_candle.time_open.date()] += pnl
return trades
@@ -129,6 +210,7 @@ def run_backtest_stream(candles, strategy, starting_balance, risk_reward=1.0,
consecutive_losses = 0
daily_pnl = defaultdict(float)
total = len(candles)
equity = float(starting_balance)
if hasattr(strategy, "prepare"):
strategy.prepare(candles)
@@ -143,6 +225,7 @@ def run_backtest_stream(candles, strategy, starting_balance, risk_reward=1.0,
if position:
_apply_break_even_if_triggered(position, candle, strategy)
_apply_partial_tp_if_triggered(position, candle, strategy)
is_long = position["direction"] == "long"
sl, tp = position["stop_loss"], position["take_profit"]
@@ -154,7 +237,8 @@ def run_backtest_stream(candles, strategy, starting_balance, risk_reward=1.0,
exit_price = sl if hit_sl else tp
price_move = (exit_price - position["entry_price"]) if is_long else (position["entry_price"] - exit_price)
lot_size = max(position.get("lot_size", 0.0), 0.0)
pnl = price_move * lot_size
partial_pnl = float(position.get("partial_tp_realized_pnl", 0.0) or 0.0)
pnl = (price_move * lot_size) + partial_pnl
risk_distance = max(position.get("risk_distance", 0.0), 1e-12)
r_multiple = price_move / risk_distance
@@ -166,8 +250,11 @@ def run_backtest_stream(candles, strategy, starting_balance, risk_reward=1.0,
exit_price=exit_price,
pnl=pnl,
r_multiple=r_multiple,
partial_tp_taken=bool(position.get("partial_tp_taken", False)),
partial_tp_realized_pnl=partial_pnl,
)
position = None
equity += pnl
if pnl <= 0:
consecutive_losses += 1
@@ -179,6 +266,8 @@ def run_backtest_stream(candles, strategy, starting_balance, risk_reward=1.0,
yield {"type": "trade", "trade": trade, "processed_candles": i, "total_candles": total}
if position is None:
if equity <= 0:
continue
if max_consecutive_losses > 0 and consecutive_losses >= max_consecutive_losses:
continue
if max_daily_loss > 0:
@@ -202,7 +291,7 @@ def run_backtest_stream(candles, strategy, starting_balance, risk_reward=1.0,
):
continue
risk_amount = starting_balance * (risk_pct / 100)
risk_amount = equity * (risk_pct / 100)
if risk_amount <= 0 or not math.isfinite(risk_amount):
continue
@@ -221,6 +310,34 @@ def run_backtest_stream(candles, strategy, starting_balance, risk_reward=1.0,
"risk_distance": sl_distance,
"lot_size": lot_size,
"break_even_armed": False,
"partial_tp_taken": False,
"partial_tp_realized_pnl": 0.0,
}
if position and candles:
last_candle = candles[-1]
is_long = position["direction"] == "long"
exit_price = last_candle.close
price_move = (exit_price - position["entry_price"]) if is_long else (position["entry_price"] - exit_price)
lot_size = max(position.get("lot_size", 0.0), 0.0)
partial_pnl = float(position.get("partial_tp_realized_pnl", 0.0) or 0.0)
pnl = (price_move * lot_size) + partial_pnl
risk_distance = max(position.get("risk_distance", 0.0), 1e-12)
r_multiple = price_move / risk_distance
trade = Trade(
enter_time=position["enter_time"],
enter_price=position["entry_price"],
direction=position["direction"],
exit_time=last_candle.time_open,
exit_price=exit_price,
pnl=pnl,
r_multiple=r_multiple,
partial_tp_taken=bool(position.get("partial_tp_taken", False)),
partial_tp_realized_pnl=partial_pnl,
)
equity += pnl
daily_pnl[last_candle.time_open.date()] += pnl
yield {"type": "trade", "trade": trade, "processed_candles": total, "total_candles": total}
yield {"type": "done", "total_candles": total}