Prepare source-only public release for develop.
Add cluster audit pipeline, united EA updates, brochure generators, and publication hygiene (gitignore, MT5 path desensitization, pre-upload scan). Remove tracked reports, models, and binary artifacts from the repo. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
3f75a08848
commit
605faf5310
@@ -0,0 +1,88 @@
|
||||
"""
|
||||
Lot sizing guidance for United EA combined portfolio.
|
||||
|
||||
Compares per-strategy risk at 123.set nominal lots and suggests relative weights.
|
||||
|
||||
Usage:
|
||||
python -m cluster_audit.lot_sizing
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from cluster_audit.united_registry import UNITED_STRATEGIES
|
||||
|
||||
REPORTS = Path(__file__).parent / "reports" / "united_sequential"
|
||||
MANIFEST = REPORTS / "united_manifest.json"
|
||||
OUT = REPORTS / "lot_sizing.json"
|
||||
REF_BALANCE = 1000.0
|
||||
|
||||
|
||||
def main() -> None:
|
||||
manifest = {}
|
||||
if MANIFEST.exists():
|
||||
manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
|
||||
|
||||
rows: list[dict] = []
|
||||
for spec in UNITED_STRATEGIES:
|
||||
sid = spec["id"]
|
||||
info = manifest.get("strategies", {}).get(sid, {})
|
||||
if not info.get("passed"):
|
||||
continue
|
||||
o_net = float(info.get("net_profit", 0))
|
||||
trades = int(info.get("trades", 1))
|
||||
dd = float(info.get("max_drawdown_pct", info.get("issues", [""])[0] if False else 5))
|
||||
lot = float(spec["lot"])
|
||||
per_trade = o_net / max(trades, 1)
|
||||
# risk proxy: lot * avg loss magnitude; use net/trades as PnL per trade signal
|
||||
rows.append({
|
||||
"id": sid,
|
||||
"symbol": spec["symbol"],
|
||||
"lot_123set": lot,
|
||||
"net_profit": o_net,
|
||||
"trades": trades,
|
||||
"pnl_per_trade": round(per_trade, 2),
|
||||
"max_dd_pct": dd,
|
||||
})
|
||||
|
||||
if not rows:
|
||||
print("No passed strategies in manifest — run united sequential audit first.")
|
||||
return
|
||||
|
||||
# Target: equal risk contribution via inverse DD weighting
|
||||
inv_dd = [1.0 / max(r.get("max_dd_pct", 5), 0.5) for r in rows]
|
||||
total_inv = sum(inv_dd)
|
||||
for i, r in enumerate(rows):
|
||||
weight = inv_dd[i] / total_inv
|
||||
r["risk_weight"] = round(weight, 4)
|
||||
r["suggested_lot_vs_darvas"] = round(weight / (inv_dd[0] / total_inv), 3) if rows else 1.0
|
||||
|
||||
# Scale all lots so combined net at ref balance ~ sum of individuals / sqrt(N)
|
||||
import math
|
||||
n = len(rows)
|
||||
diversification = math.sqrt(n)
|
||||
base_lot = rows[0]["lot_123set"]
|
||||
for r in rows:
|
||||
r["suggested_lot_at_1k"] = round(base_lot * r["suggested_lot_vs_darvas"] / diversification, 4)
|
||||
|
||||
result = {
|
||||
"reference_balance": REF_BALANCE,
|
||||
"passed_count": n,
|
||||
"diversification_factor": diversification,
|
||||
"note": "suggested_lot_at_1k scales 123.set lots by inverse-DD weight / sqrt(N)",
|
||||
"strategies": rows,
|
||||
}
|
||||
OUT.write_text(json.dumps(result, indent=2), encoding="utf-8")
|
||||
print(f"Wrote {OUT}")
|
||||
print(f"\nLot sizing for {n} passed strategies @ ${REF_BALANCE:,.0f} reference:\n")
|
||||
for r in rows:
|
||||
print(
|
||||
f" {r['id']:28} lot={r['lot_123set']:8} -> suggested={r['suggested_lot_at_1k']:8} "
|
||||
f"(weight={r['risk_weight']:.2%}, pnl/trade=${r['pnl_per_trade']:.2f})"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user