feat: finalize portfolio system and quantitative validation- Finalized MA_Crossover(30,100) and TrendFiltered_MA(30,100,ADX=15)

- Implemented portfolio engine with risk-based allocation (50/50)
- Added equity-based metrics for system-level evaluation
- Validated portfolio against standalone strategies
- Reduced max drawdown and volatility at system level
- Quantitative decision closed before paper trading phase
This commit is contained in:
DaM
2026-02-02 14:38:05 +01:00
parent c569170fcc
commit f85c522f22
53 changed files with 2389 additions and 104 deletions

View File

@@ -0,0 +1,185 @@
import os
import sys
from pathlib import Path
from dotenv import load_dotenv
from datetime import datetime, timedelta
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# --------------------------------------------------
# Path setup
# --------------------------------------------------
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from src.utils.logger import log
from src.data.storage import StorageManager
from src.core.engine import Engine
from src.strategies import MovingAverageCrossover
from src.risk.sizing.percent_risk import PercentRiskSizer
from src.risk.stops.atr_stop import ATRStop
# --------------------------------------------------
# CONFIG
# --------------------------------------------------
SYMBOL = "BTC/USDT"
TIMEFRAME = "1h"
DAYS_BACK = 180
INITIAL_CAPITAL = 10_000
RISK_PER_TRADE = 0.01 # 1%
ATR_PERIOD = 14
ATR_MULTIPLIER = 2.0
COMMISSION = 0.001
SLIPPAGE = 0.0005
def setup_environment():
"""Carga variables de entorno"""
env_path = Path(__file__).parent.parent.parent / 'config' / 'secrets.env'
load_dotenv(dotenv_path=env_path)
# --------------------------------------------------
# Load data
# --------------------------------------------------
def load_data():
# Setup
setup_environment()
storage = StorageManager(
db_host=os.getenv("DB_HOST"),
db_port=int(os.getenv("DB_PORT", 5432)),
db_name=os.getenv("DB_NAME"),
db_user=os.getenv("DB_USER"),
db_password=os.getenv("DB_PASSWORD"),
)
end_date = datetime.now()
start_date = end_date - timedelta(days=DAYS_BACK)
data = storage.load_ohlcv(
symbol=SYMBOL,
timeframe=TIMEFRAME,
start_date=start_date,
end_date=end_date,
use_cache=False,
)
storage.close()
if data.empty:
raise RuntimeError("No data loaded")
return data
# --------------------------------------------------
# Main
# --------------------------------------------------
def run():
log.info("=" * 70)
log.info("📐 RISK VALIDATION PercentRiskSizer + ATRStop")
log.info("=" * 70)
data = load_data()
strategy = MovingAverageCrossover(
fast_period=10,
slow_period=30,
ma_type="ema",
use_adx=False,
)
engine = Engine(
strategy=strategy,
initial_capital=INITIAL_CAPITAL,
commission=COMMISSION,
slippage=SLIPPAGE,
position_sizer=PercentRiskSizer(RISK_PER_TRADE),
stop_loss=ATRStop(
atr_period=ATR_PERIOD,
multiplier=ATR_MULTIPLIER,
),
)
results = engine.run(data)
trades = results["trades"]
# --------------------------------------------------
# Compute real risk per trade
# --------------------------------------------------
risks = []
for trade in trades:
if trade.exit_reason != "Stop Loss":
continue
risk_amount = abs(
trade.entry_price - trade.stop_price_at_entry
) * trade.size
risk_pct = risk_amount / trade.capital_at_entry
risks.append(risk_pct)
risks = np.array(risks)
if len(risks) == 0:
log.warning("No stop-loss trades found")
return
# --------------------------------------------------
# Print summary
# --------------------------------------------------
print()
print("=" * 70)
print("📊 RISK PER TRADE SUMMARY")
print("=" * 70)
print(f"Trades analysed : {len(risks)}")
print(f"Target risk : {RISK_PER_TRADE*100:.2f}%")
print(f"Mean risk : {risks.mean()*100:.2f}%")
print(f"Std deviation : {risks.std()*100:.2f}%")
print(f"Min risk : {risks.min()*100:.2f}%")
print(f"Max risk : {risks.max()*100:.2f}%")
print("=" * 70)
output_dir = Path(__file__).parent / "output"
output_dir.mkdir(parents=True, exist_ok=True)
# --------------------------------------------------
# Plot risk distribution
# --------------------------------------------------
plt.figure(figsize=(10, 5))
plt.hist(risks * 100, bins=25, edgecolor="black", alpha=0.7)
plt.axvline(RISK_PER_TRADE * 100, color="red", linestyle="--", label="Target")
plt.title("Risk distribution per trade (%)")
plt.xlabel("Risk (%)")
plt.ylabel("Trades")
plt.legend()
plt.grid(alpha=0.3)
plt.tight_layout()
plt.savefig(output_dir / "risk_distribution.png")
plt.close()
# --------------------------------------------------
# Plot risk over time
# --------------------------------------------------
plt.figure(figsize=(12, 5))
plt.plot(risks * 100, marker="o", linewidth=1)
plt.axhline(RISK_PER_TRADE * 100, color="red", linestyle="--", label="Target")
plt.title("Risk per trade over time")
plt.xlabel("Trade #")
plt.ylabel("Risk (%)")
plt.legend()
plt.grid(alpha=0.3)
plt.tight_layout()
plt.savefig(output_dir / "risk_over_time.png")
plt.close()
log.success("Risk validation completed ✔")
if __name__ == "__main__":
run()