149 lines
3.7 KiB
Python
149 lines
3.7 KiB
Python
"""
|
|
Portfolio simulation utilities for backtesting.
|
|
|
|
Handles long-only and long/short portfolio creation using VectorBT.
|
|
"""
|
|
import pandas as pd
|
|
import vectorbt as vbt
|
|
|
|
from engine.logging_config import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
def run_long_only_portfolio(
|
|
close: pd.Series,
|
|
entries: pd.DataFrame,
|
|
exits: pd.DataFrame,
|
|
init_cash: float,
|
|
fees: float,
|
|
slippage: float,
|
|
freq: str,
|
|
sl_stop: float | None,
|
|
tp_stop: float | None,
|
|
sl_trail: bool,
|
|
leverage: int
|
|
) -> vbt.Portfolio:
|
|
"""
|
|
Run a long-only portfolio simulation.
|
|
|
|
Args:
|
|
close: Close price series
|
|
entries: Entry signals
|
|
exits: Exit signals
|
|
init_cash: Initial capital
|
|
fees: Transaction fee percentage
|
|
slippage: Slippage percentage
|
|
freq: Data frequency string
|
|
sl_stop: Stop loss percentage
|
|
tp_stop: Take profit percentage
|
|
sl_trail: Enable trailing stop loss
|
|
leverage: Leverage multiplier
|
|
|
|
Returns:
|
|
VectorBT Portfolio object
|
|
"""
|
|
effective_cash = init_cash * leverage
|
|
|
|
return vbt.Portfolio.from_signals(
|
|
close=close,
|
|
entries=entries,
|
|
exits=exits,
|
|
init_cash=effective_cash,
|
|
fees=fees,
|
|
slippage=slippage,
|
|
freq=freq,
|
|
sl_stop=sl_stop,
|
|
tp_stop=tp_stop,
|
|
sl_trail=sl_trail,
|
|
size=1.0,
|
|
size_type='percent',
|
|
)
|
|
|
|
|
|
def run_long_short_portfolio(
|
|
close: pd.Series,
|
|
long_entries: pd.DataFrame,
|
|
long_exits: pd.DataFrame,
|
|
short_entries: pd.DataFrame,
|
|
short_exits: pd.DataFrame,
|
|
init_cash: float,
|
|
fees: float,
|
|
slippage: float,
|
|
freq: str,
|
|
sl_stop: float | None,
|
|
tp_stop: float | None,
|
|
sl_trail: bool,
|
|
leverage: int
|
|
) -> vbt.Portfolio:
|
|
"""
|
|
Run a portfolio supporting both long and short positions.
|
|
|
|
Runs two separate portfolios (long and short) and combines results.
|
|
Each gets half the capital.
|
|
|
|
Args:
|
|
close: Close price series
|
|
long_entries: Long entry signals
|
|
long_exits: Long exit signals
|
|
short_entries: Short entry signals
|
|
short_exits: Short exit signals
|
|
init_cash: Initial capital
|
|
fees: Transaction fee percentage
|
|
slippage: Slippage percentage
|
|
freq: Data frequency string
|
|
sl_stop: Stop loss percentage
|
|
tp_stop: Take profit percentage
|
|
sl_trail: Enable trailing stop loss
|
|
leverage: Leverage multiplier
|
|
|
|
Returns:
|
|
VectorBT Portfolio object (long portfolio, short stats logged)
|
|
"""
|
|
effective_cash = init_cash * leverage
|
|
half_cash = effective_cash / 2
|
|
|
|
# Run long-only portfolio
|
|
long_pf = vbt.Portfolio.from_signals(
|
|
close=close,
|
|
entries=long_entries,
|
|
exits=long_exits,
|
|
direction='longonly',
|
|
init_cash=half_cash,
|
|
fees=fees,
|
|
slippage=slippage,
|
|
freq=freq,
|
|
sl_stop=sl_stop,
|
|
tp_stop=tp_stop,
|
|
sl_trail=sl_trail,
|
|
size=1.0,
|
|
size_type='percent',
|
|
)
|
|
|
|
# Run short-only portfolio
|
|
short_pf = vbt.Portfolio.from_signals(
|
|
close=close,
|
|
entries=short_entries,
|
|
exits=short_exits,
|
|
direction='shortonly',
|
|
init_cash=half_cash,
|
|
fees=fees,
|
|
slippage=slippage,
|
|
freq=freq,
|
|
sl_stop=sl_stop,
|
|
tp_stop=tp_stop,
|
|
sl_trail=sl_trail,
|
|
size=1.0,
|
|
size_type='percent',
|
|
)
|
|
|
|
# Log both portfolio stats
|
|
# TODO: Implement proper portfolio combination
|
|
logger.info(
|
|
"Long portfolio: %.2f%% return, Short portfolio: %.2f%% return",
|
|
long_pf.total_return().mean() * 100,
|
|
short_pf.total_return().mean() * 100
|
|
)
|
|
|
|
return long_pf
|