- 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
93 lines
2.2 KiB
Python
93 lines
2.2 KiB
Python
# tests/backtest/test_engine_sizing.py
|
|
import pytest
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
import pandas as pd
|
|
from datetime import datetime, timedelta
|
|
|
|
# Añadir raíz del proyecto al path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
|
|
|
from src.core.engine import Engine
|
|
from src.core.strategy import Strategy, Signal
|
|
from src.risk.sizing.fixed import FixedPositionSizer
|
|
|
|
class BuyOnceStrategy(Strategy):
|
|
"""
|
|
Estrategia dummy:
|
|
- BUY en la primera vela
|
|
- SELL en la segunda
|
|
"""
|
|
|
|
def __init__(self):
|
|
super().__init__(name="BuyOnce", params={})
|
|
|
|
def init_indicators(self, data: pd.DataFrame) -> pd.DataFrame:
|
|
return data
|
|
|
|
def generate_signal(self, idx: int) -> Signal:
|
|
if idx == 0:
|
|
return Signal.BUY
|
|
if idx == 1:
|
|
return Signal.SELL
|
|
return Signal.HOLD
|
|
|
|
def test_engine_uses_fixed_position_sizer():
|
|
"""
|
|
El engine debe usar el PositionSizer
|
|
y NO el position_size_fraction por defecto.
|
|
"""
|
|
|
|
# -------------------------
|
|
# Datos dummy
|
|
# -------------------------
|
|
dates = [
|
|
datetime(2024, 1, 1),
|
|
datetime(2024, 1, 2),
|
|
datetime(2024, 1, 3),
|
|
]
|
|
|
|
data = pd.DataFrame(
|
|
{
|
|
"open": [100, 100, 100],
|
|
"high": [100, 100, 100],
|
|
"low": [100, 100, 100],
|
|
"close": [100, 100, 100],
|
|
"volume": [1, 1, 1],
|
|
},
|
|
index=dates,
|
|
)
|
|
|
|
# -------------------------
|
|
# Engine + Sizer
|
|
# -------------------------
|
|
strategy = BuyOnceStrategy()
|
|
|
|
sizer = FixedPositionSizer(capital_fraction=0.5)
|
|
|
|
engine = Engine(
|
|
strategy=strategy,
|
|
initial_capital=10000,
|
|
commission=0.0,
|
|
slippage=0.0,
|
|
position_size=0.95,
|
|
position_sizer=sizer
|
|
)
|
|
|
|
results = engine.run(data)
|
|
# -------------------------
|
|
# Validaciones
|
|
# -------------------------
|
|
trades = results["trades"]
|
|
assert len(trades) == 1
|
|
|
|
trade = trades[0]
|
|
|
|
invested_value = trade.entry_price * trade.size
|
|
|
|
# Esperamos ~50% del capital
|
|
assert invested_value == pytest.approx(5000, rel=1e-3)
|
|
|
|
# Sanity check
|
|
assert invested_value < 9500 # NO debe usar 95% |