""" 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