Document and abstract portfolio trading costs
This commit is contained in:
@@ -4,7 +4,8 @@ Execution model (documented convention): a position book targeted from
|
||||
information available on date ``t`` is executed at ``open[t+1]``. Trades that
|
||||
violate a :class:`~pipeline.portfolio.constraints.TradeConstraint` (suspension,
|
||||
price limit, volume cap, …) are clipped; a fully blocked buy leaves the position
|
||||
at its previous level. Realized PnL marks the *actually filled* book.
|
||||
at its previous level. Realized PnL marks the *actually filled* book. Trading
|
||||
cost defaults to a simplified open-execution proportional cash-cost model.
|
||||
|
||||
The simulator is an ABC + a :class:`ReferenceSimulator`; constraints compose by
|
||||
intersecting their per-name signed delta bounds.
|
||||
@@ -21,6 +22,7 @@ import pandas as pd
|
||||
|
||||
from pipeline.common.schema import FILL_COLUMNS, PNL_COLUMNS
|
||||
from pipeline.portfolio.constraints import TradeConstraint
|
||||
from pipeline.portfolio.costs import CostModel, SimpleProportionalCostModel
|
||||
from pipeline.portfolio.market_rules import MarketRule, compute_limit_status
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -65,10 +67,12 @@ class ExecutionSimulator(ABC):
|
||||
"""Abstract execution layer. Subclasses define how a target gets filled."""
|
||||
|
||||
def __init__(self, constraints: list[TradeConstraint] | None = None,
|
||||
cost_bps: float = 0.0, slippage_bps: float = 0.0):
|
||||
cost_bps: float = 0.0, slippage_bps: float = 0.0,
|
||||
cost_model: CostModel | None = None):
|
||||
self.constraints = constraints or []
|
||||
self.cost_bps = cost_bps
|
||||
self.slippage_bps = slippage_bps
|
||||
self.cost_model = cost_model or SimpleProportionalCostModel(
|
||||
cost_bps=cost_bps, slippage_bps=slippage_bps
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def fill(self, ctx: TradeContext) -> FillResult:
|
||||
@@ -104,9 +108,17 @@ class ReferenceSimulator(ExecutionSimulator):
|
||||
blocked = (traded != desired).astype(np.int64)
|
||||
|
||||
realized = prev + traded
|
||||
open_px = np.where(np.isfinite(ctx.slice.price), ctx.slice.price, 0.0)
|
||||
trade_value = np.abs(traded.astype(np.float64) * open_px)
|
||||
cost = trade_value * (self.cost_bps + self.slippage_bps) / 1e4
|
||||
cost = self.cost_model.compute(
|
||||
traded_shares=traded,
|
||||
execution_price=ctx.slice.price,
|
||||
side=np.sign(traded),
|
||||
date=ctx.slice.date,
|
||||
metadata={
|
||||
"symbol_ids": ctx.slice.symbol_ids,
|
||||
"booksize": ctx.booksize,
|
||||
"market_slice": ctx.slice,
|
||||
},
|
||||
)
|
||||
return FillResult(realized, traded, cost, blocked)
|
||||
|
||||
def run(
|
||||
|
||||
Reference in New Issue
Block a user