fix: harden drift verifier and validation flow

This commit is contained in:
mindesbunister
2025-12-10 15:05:44 +01:00
parent 0a45279c64
commit 4e286c91ef
12 changed files with 620 additions and 579 deletions

View File

@@ -26,7 +26,7 @@ Progressive test sweep parameters (512 combinations):
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional
from typing import Callable, Optional
try:
from typing import Literal
@@ -36,6 +36,8 @@ except ImportError:
import numpy as np
import pandas as pd
from backtester.simulator import SimulationResult, TradeConfig, _simulate_trade
from backtester.math_utils import calculate_adx, calculate_atr, rma
Direction = Literal["long", "short"]
@@ -85,6 +87,7 @@ class MoneyLineV11Signal:
rsi: float
volume_ratio: float
price_position: float
signal_type: str = "moneyline_v11"
def ema(series: pd.Series, length: int) -> pd.Series:
@@ -209,7 +212,9 @@ def supertrend_v11(df: pd.DataFrame, atr_period: int, multiplier: float,
return pd.Series(tsl, index=df.index), pd.Series(trend, index=df.index)
def money_line_v11_signals(df: pd.DataFrame, inputs: Optional[MoneyLineV11Inputs] = None) -> list[MoneyLineV11Signal]:
def money_line_v11_signals(
df: pd.DataFrame, inputs: Optional[MoneyLineV11Inputs] = None
) -> tuple[list[MoneyLineV11Signal], pd.DataFrame]:
"""
v11 "Money Line All Filters" signal generation.
@@ -229,8 +234,9 @@ def money_line_v11_signals(df: pd.DataFrame, inputs: Optional[MoneyLineV11Inputs
if inputs is None:
inputs = MoneyLineV11Inputs()
data = df.copy()
data = data.sort_index()
# Work in-place on provided DataFrame so downstream consumers (e.g., two-stage
# confirmation) can access calculated indicator columns.
data = df.sort_index()
# Calculate Money Line
supertrend, trend = supertrend_v11(
@@ -380,4 +386,88 @@ def money_line_v11_signals(df: pd.DataFrame, inputs: Optional[MoneyLineV11Inputs
)
cooldown_remaining = inputs.cooldown_bars
return signals
return signals, data
def _two_stage_confirmation(
signals: list[MoneyLineV11Signal],
df: pd.DataFrame,
confirm_pct: float = 0.15,
) -> list[MoneyLineV11Signal]:
"""Filter signals with a simple two-stage confirmation on the next bar."""
confirmed: list[MoneyLineV11Signal] = []
index_positions = {ts: idx for idx, ts in enumerate(df.index)}
threshold = confirm_pct / 100.0
for signal in signals:
idx = index_positions.get(signal.timestamp)
if idx is None or idx + 1 >= len(df):
continue
next_bar = df.iloc[idx + 1]
next_close = float(next_bar["close"])
if signal.direction == "long":
needed = signal.entry_price * (1 + threshold)
if next_close < needed:
continue
else:
needed = signal.entry_price * (1 - threshold)
if next_close > needed:
continue
confirmed.append(
MoneyLineV11Signal(
timestamp=df.index[idx + 1],
direction=signal.direction,
entry_price=next_close,
adx=float(next_bar["adx"]),
atr=float(next_bar["atr"]),
rsi=float(next_bar["rsi"]),
volume_ratio=float(next_bar["volume_ratio"]),
price_position=float(next_bar["price_position"]),
)
)
return confirmed
def simulate_money_line_v11(
df: pd.DataFrame,
symbol: str,
inputs: Optional[MoneyLineV11Inputs] = None,
config: Optional[TradeConfig] = None,
quality_filter: Optional[Callable[[MoneyLineV11Signal], bool]] = None,
two_stage: bool = False,
confirm_pct: float = 0.15,
) -> SimulationResult:
if inputs is None:
inputs = MoneyLineV11Inputs()
if config is None:
config = TradeConfig()
if quality_filter is None:
quality_filter = lambda _: True # type: ignore
data = df.sort_index().copy()
index_positions = {ts: idx for idx, ts in enumerate(data.index)}
signals, enriched = money_line_v11_signals(data, inputs)
if two_stage:
signals = _two_stage_confirmation(signals, enriched, confirm_pct)
trades = []
next_available_index = 0
for signal in signals:
if not quality_filter(signal):
continue
start_idx = index_positions.get(signal.timestamp)
if start_idx is None or start_idx < next_available_index:
continue
trade = _simulate_trade(data, start_idx, signal, symbol, config)
if trade is None:
continue
trades.append(trade)
next_available_index = trade._exit_index
return SimulationResult(trades=trades)