scripts: pl_calc, vision_schema, calendar_parse + tests (67 passing)
This commit is contained in:
76
scripts/pl_calc.py
Normal file
76
scripts/pl_calc.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""P/L overlays for M2D backtesting.
|
||||
|
||||
Two overlays computed from the same trade outcome:
|
||||
|
||||
- ``pl_marius``: real overlay used by the trader. 50% closed at TP0 (+0.2 R),
|
||||
BE move on the remaining half, then close 50% of that at ~TP1 (+0.3 R total
|
||||
contribution) or at SL/BE depending on outcome. TP1 is treated as the final
|
||||
exit even when the chart subsequently reaches TP2.
|
||||
|
||||
- ``pl_theoretical``: reference 1/3-1/3-1/3 overlay that holds to TP2. Used
|
||||
as an opportunity-cost benchmark vs. ``pl_marius``.
|
||||
|
||||
Returns are expressed in multiples of R (risk per trade). ``None`` from
|
||||
``pl_marius`` denotes a still-pending trade.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__all__ = [
|
||||
"PL_MARIUS_TABLE",
|
||||
"PL_THEORETICAL_TABLE",
|
||||
"pl_marius",
|
||||
"pl_theoretical",
|
||||
]
|
||||
|
||||
|
||||
PL_MARIUS_TABLE: dict[tuple[str, bool], float | None] = {
|
||||
("SL", True): -1.0,
|
||||
("SL", False): -1.0,
|
||||
("TP0->SL", True): 0.20,
|
||||
("TP0->SL", False): -0.30,
|
||||
("TP0->TP1", True): 0.50,
|
||||
("TP0->TP1", False): 0.50,
|
||||
("TP0->TP2", True): 0.50,
|
||||
("TP0->TP2", False): 0.50,
|
||||
("TP0->pending", True): None,
|
||||
("TP0->pending", False): None,
|
||||
("pending", True): None,
|
||||
("pending", False): None,
|
||||
}
|
||||
|
||||
|
||||
PL_THEORETICAL_TABLE: dict[str, float] = {
|
||||
"SL_first": -1.0,
|
||||
"TP0": 0.133,
|
||||
"TP1": 0.333,
|
||||
"TP2": 0.667,
|
||||
}
|
||||
|
||||
|
||||
_VALID_OUTCOME_PATHS: frozenset[str] = frozenset(
|
||||
{"SL", "TP0->SL", "TP0->TP1", "TP0->TP2", "TP0->pending", "pending"}
|
||||
)
|
||||
|
||||
|
||||
def _normalize_outcome_path(outcome_path: str) -> str:
|
||||
return outcome_path.replace("→", "->").replace("→", "->")
|
||||
|
||||
|
||||
def pl_marius(outcome_path: str, be_moved: bool) -> float | None:
|
||||
"""Return the P/L (in R) for the real Marius overlay.
|
||||
|
||||
Accepts both ASCII arrow ``"TP0->TP1"`` and unicode arrow ``"TP0→TP1"``.
|
||||
Returns ``None`` for pending outcomes.
|
||||
"""
|
||||
normalized = _normalize_outcome_path(outcome_path)
|
||||
if normalized not in _VALID_OUTCOME_PATHS:
|
||||
raise ValueError(f"invalid outcome_path: {outcome_path!r}")
|
||||
return PL_MARIUS_TABLE[(normalized, be_moved)]
|
||||
|
||||
|
||||
def pl_theoretical(max_reached: str) -> float:
|
||||
"""Return the P/L (in R) for the theoretical 1/3-1/3-1/3 hold-to-TP2 overlay."""
|
||||
if max_reached not in PL_THEORETICAL_TABLE:
|
||||
raise ValueError(f"invalid max_reached: {max_reached!r}")
|
||||
return PL_THEORETICAL_TABLE[max_reached]
|
||||
Reference in New Issue
Block a user