Cost Models#

Pluggable commission, slippage and trading-record models used by the backtester to simulate execution costs and capture intermediate trading state.

Commission Models#

Commission model implementations module.

This module provides concrete implementations of CommissionModelInterface for various commission structures. The default PerShareCommission matches the original backtester behavior.

Available models: - PerShareCommission: Fixed rate per share (default) - ZeroCommission: No commission for testing - PercentageCommission: Percentage of notional value - FixedCommission: Fixed amount per trade - TieredCommission: Volume-based tiered rates

All commission models are immutable after construction, ensuring consistent behavior throughout a backtest.

class FixedCommission(amount: float)#

Bases: CommissionModelInterface

Fixed commission amount per trade regardless of size.

Charges the same flat fee for every trade, independent of the number of shares traded. Common with discount brokers that charge a flat rate per trade.

Variables:

amount (float) – Fixed commission per trade (read-only).

Examples

>>> model = FixedCommission(amount=4.95)
>>> model.calculate(shares=1)
4.95
>>> model.calculate(shares=10000)
4.95
property amount: float#

Get the fixed commission amount.

calculate(shares: float) float#

Return fixed commission amount.

Returns the same fixed amount regardless of how many shares are traded. This is typical of flat-fee broker pricing.

Parameters:

shares (float) – Number of shares traded. This parameter is ignored since commission is fixed.

Returns:

Fixed commission amount in currency units.

Return type:

float

Examples

>>> model = FixedCommission(amount=4.95)
>>> model.calculate(shares=1)
4.95
>>> model.calculate(shares=10000)
4.95
class PerShareCommission(rate: float)#

Bases: CommissionModelInterface

Commission as a fixed rate per share traded.

This is the default commission model matching the original backtester behavior where commission = abs(shares) * rate.

Variables:

rate (float) – Commission rate per share (read-only). For example, 0.01 means $0.01 per share.

Examples

Basic usage:

>>> model = PerShareCommission(rate=0.01)
>>> model.calculate(shares=100)
1.0
>>> model.calculate(shares=-100)  # Sell order
1.0

Zero commission rate:

>>> model = PerShareCommission(rate=0.0)
>>> model.calculate(shares=1000)
0.0

Notes

This matches the original _calculate_commission method: return abs(shares) * self.commission_pct_float

See also

ZeroCommission

Convenience class for zero commission

calculate(shares: float) float#

Calculate the commission for a trade based on share count.

Computes abs(shares) * rate and rounds the result to two decimal places (cents). The absolute value is used because commission is charged on both buys and sells; the sign of shares only carries information about trade direction, which is irrelevant to fee computation.

Parameters:

shares (float) – Number of shares traded. Positive for buys, negative for sells. Zero shares yields zero commission. Fractional values are supported (e.g. fractional-share brokers).

Returns:

Commission amount in currency units, rounded to two decimal places. Always non-negative.

Return type:

float

Examples

Buy and sell of the same size produce the same fee:

>>> model = PerShareCommission(rate=0.01)
>>> model.calculate(shares=100)
1.0
>>> model.calculate(shares=-100)
1.0

Zero shares is a no-op:

>>> model.calculate(shares=0)
0.0

Fractional shares are honoured:

>>> model = PerShareCommission(rate=0.02)
>>> model.calculate(shares=12.5)
0.25

Notes

Rounding is applied at the end with two decimal precision (round(value, 2)), matching the original backtester to the cent. Callers that need higher precision should bypass this model and reimplement the multiplication.

See also

CommissionModelInterface.calculate

Abstract contract.

PercentageCommission

Notional-based commission.

ZeroCommission

No-fee model used as a baseline.

property rate: float#

Get the commission rate per share.

Returns:

Commission rate in currency units per share.

Return type:

float

class PercentageCommission(pct: float, notional_per_share: float = 100.0)#

Bases: CommissionModelInterface

Commission as percentage of notional value.

Calculates commission based on the estimated notional value of the trade (shares x price). Since the interface only receives shares, an approximate notional_per_share is used by default.

Variables:
  • pct (float) – Commission percentage as decimal (read-only). For example, 0.001 for 0.1% (10 basis points).

  • notional_per_share (float) – Approximate price per share for commission calculation (read-only). Default is 100.0.

Examples

>>> model = PercentageCommission(pct=0.001, notional_per_share=50.0)
>>> model.calculate(shares=100)
5.0  # 100 shares x $50 x 0.001
>>> model = PercentageCommission(pct=0.001)  # Default $100/share
>>> model.calculate(shares=100)
10.0  # 100 shares x $100 x 0.001

Notes

For accurate commission with actual trade price, use the calculate_with_price() method when price is available.

calculate(shares: float) float#

Calculate commission as percentage of estimated notional.

Computes commission as: abs(shares) x notional_per_share x pct. This provides an estimate when actual trade price is not available.

Parameters:

shares (float) – Number of shares traded. Can be positive (buy) or negative (sell). The absolute value is used for calculation.

Returns:

Estimated commission amount in currency units. Always non-negative.

Return type:

float

Examples

>>> model = PercentageCommission(pct=0.001, notional_per_share=50.0)
>>> model.calculate(shares=100)
5.0
>>> model.calculate(shares=-100)
5.0
calculate_with_price(shares: float, price: float) float#

Calculate commission with actual price.

Computes commission as: abs(shares) x price x pct. Use this when the actual trade price is known.

Parameters:
  • shares (float) – Number of shares traded.

  • price (float) – Actual price per share for the trade.

Returns:

Actual commission amount in currency units.

Return type:

float

Examples

>>> model = PercentageCommission(pct=0.001)
>>> model.calculate_with_price(shares=100, price=150.0)
15.0  # 100 x $150 x 0.001
property notional_per_share: float#

Get the notional per share value.

property pct: float#

Get the commission percentage.

class TieredCommission(tiers: tuple[tuple[float, float], ...], minimum: float = 0.0)#

Bases: CommissionModelInterface

Commission with volume-based tiered per-share rates.

Applies different per-share rates based on the trade size. Larger trades typically receive better (lower) rates. A minimum commission can be specified.

Variables:
  • tiers (tuple[tuple[float, float], ...]) – Tuple of (share_threshold, rate) tuples (read-only). Each tier specifies the rate that applies when shares >= threshold.

  • minimum (float) – Minimum commission per trade (read-only). Default is 0.0.

Examples

>>> tiers = ((0, 0.01), (1000, 0.008), (5000, 0.005))
>>> model = TieredCommission(tiers=tiers, minimum=1.0)
>>> model.calculate(shares=500)    # Uses 0.01 rate
5.0
>>> model.calculate(shares=2000)   # Uses 0.008 rate
16.0
>>> model.calculate(shares=10000)  # Uses 0.005 rate
50.0

Notes

The tier with the highest threshold that is <= shares is used. If the calculated commission is less than minimum, minimum is returned.

calculate(shares: float) float#

Calculate commission using applicable tier rate.

Determines the appropriate rate based on share count, then calculates commission as abs(shares) x rate. Returns at least the minimum commission if specified.

Parameters:

shares (float) – Number of shares traded. The absolute value is used for tier selection and calculation.

Returns:

Commission amount in currency units. At least the minimum commission if specified.

Return type:

float

Examples

>>> tiers = ((0, 0.01), (1000, 0.008), (5000, 0.005))
>>> model = TieredCommission(tiers=tiers, minimum=1.0)
>>> model.calculate(shares=500)
5.0  # 500 x 0.01 = 5.0
>>> model.calculate(shares=50)
1.0  # 50 x 0.01 = 0.5, but minimum is 1.0
>>> model.calculate(shares=2000)
16.0  # 2000 x 0.008 = 16.0
property minimum: float#

Get the minimum commission amount.

property tiers: tuple[tuple[float, float], ...]#

Get the commission tiers.

class ZeroCommission#

Bases: CommissionModelInterface

Commission model that charges no fees.

Useful for backtesting scenarios where transaction costs should be ignored, such as: - Testing strategy logic without cost friction - Simulating zero-commission brokers - Baseline performance comparison

Examples

>>> model = ZeroCommission()
>>> model.calculate(shares=1000)
0.0
>>> model.calculate(shares=1_000_000)
0.0

Notes

This is equivalent to PerShareCommission(rate=0.0) but provides clearer intent in code.

calculate(shares: float) float#

Return zero commission for any trade.

Always returns 0.0 regardless of the number of shares traded. This model is useful for backtesting without transaction costs.

Parameters:

shares (float) – Number of shares traded. This parameter is ignored since no commission is charged.

Returns:

Always returns 0.0.

Return type:

float

Examples

>>> model = ZeroCommission()
>>> model.calculate(shares=100)
0.0
>>> model.calculate(shares=1_000_000)
0.0

Slippage Models#

Slippage model implementations module.

This module provides concrete implementations of SlippageModelInterface for various slippage scenarios. NoSlippage is the default, maintaining backward compatibility with the original backtester.

Available models: - NoSlippage: Zero slippage (default) - FixedSlippage: Constant percentage - BasisPointSlippage: Slippage in basis points - VolumeBasedSlippage: Order-size dependent - SquareRootSlippage: Academic market impact model - RandomSlippage: Stochastic slippage for testing

All slippage models are immutable after construction (except RandomSlippage which maintains internal RNG state for reproducibility).

class BasisPointSlippage(basis_points: float)#

Bases: SlippageModelInterface

Slippage specified in basis points.

Convenience class that accepts slippage in basis points (bp) rather than decimal percentage.

Variables:

basis_points (float) – Slippage in basis points (read-only). For example, 10 for 10bp = 0.1%.

Examples

>>> model = BasisPointSlippage(basis_points=10)
>>> model.apply(price=100.0, shares=100, is_buy=True)
100.1  # 10bp = 0.1% increase

Notes

1 basis point = 0.01% = 0.0001 in decimal form.

apply(price: float, shares: float, is_buy: bool) float#

Apply basis point slippage.

Parameters:
  • price (float) – Original quoted price before slippage.

  • shares (float) – Number of shares in the order. Ignored.

  • is_buy (bool) – True for buy orders, False for sell orders.

Returns:

Adjusted execution price with slippage applied.

Return type:

float

property basis_points: float#

Get the slippage in basis points.

class FixedSlippage(slippage_pct: float)#

Bases: SlippageModelInterface

Fixed percentage slippage regardless of order size.

Applies a constant percentage adjustment to the price. Buys execute above the quoted price (unfavorable). Sells execute below the quoted price (unfavorable).

Variables:

slippage_pct (float) – Slippage as decimal (read-only). For example, 0.001 for 0.1% or 10 basis points.

Examples

>>> model = FixedSlippage(slippage_pct=0.001)
>>> model.apply(price=100.0, shares=100, is_buy=True)
100.1  # Price increased by 0.1%
>>> model.apply(price=100.0, shares=100, is_buy=False)
99.9   # Price decreased by 0.1%
apply(price: float, shares: float, is_buy: bool) float#

Apply fixed percentage slippage.

Adjusts the price by a fixed percentage in the direction unfavorable to the trader.

Parameters:
  • price (float) – Original quoted price before slippage.

  • shares (float) – Number of shares in the order. This parameter is ignored since slippage is fixed.

  • is_buy (bool) – True for buy orders (price increases). False for sell orders (price decreases).

Returns:

Adjusted execution price with slippage applied.

Return type:

float

Examples

>>> model = FixedSlippage(slippage_pct=0.001)
>>> model.apply(price=100.0, shares=100, is_buy=True)
100.1
>>> model.apply(price=100.0, shares=100, is_buy=False)
99.9
property slippage_pct: float#

Get the slippage percentage.

class NoSlippage#

Bases: SlippageModelInterface

Slippage model with no price impact.

Returns the original price unchanged. This is the default behavior matching the original backtester where slippage was not modeled.

This model is useful for: - Baseline backtesting without market impact - Highly liquid markets where slippage is negligible - Comparing strategy performance with/without slippage

Examples

>>> model = NoSlippage()
>>> model.apply(price=100.0, shares=1000, is_buy=True)
100.0
>>> model.apply(price=100.0, shares=1000, is_buy=False)
100.0

Notes

This is the default slippage model used when none is specified.

apply(price: float, shares: float, is_buy: bool) float#

Return price unchanged.

This method returns the input price without any modification, simulating a market with zero slippage or price impact.

Parameters:
  • price (float) – Original quoted price before slippage.

  • shares (float) – Number of shares in the order. This parameter is ignored since no slippage is applied.

  • is_buy (bool) – True for buy orders, False for sell orders. This parameter is ignored since no slippage is applied.

Returns:

The original price, unchanged.

Return type:

float

Examples

>>> model = NoSlippage()
>>> model.apply(price=150.25, shares=1000, is_buy=True)
150.25
>>> model.apply(price=150.25, shares=1000, is_buy=False)
150.25
class RandomSlippage(min_slippage: float = 0.0, max_slippage: float = 0.001, seed: int | None = None)#

Bases: SlippageModelInterface

Random slippage within specified bounds.

Generates random slippage uniformly distributed between minimum and maximum percentages.

Variables:
  • min_slippage (float) – Minimum slippage percentage. Default is 0.0.

  • max_slippage (float) – Maximum slippage percentage. Default is 0.001.

  • seed (int | None) – Random seed for reproducibility. Default is None.

Examples

>>> model = RandomSlippage(min_slippage=0.0, max_slippage=0.001, seed=42)
>>> model.apply(price=100.0, shares=100, is_buy=True)
100.06...  # Random slippage between 0% and 0.1%

Notes

Using a seed ensures reproducible results across runs.

apply(price: float, shares: float, is_buy: bool) float#

Apply random slippage within bounds.

Parameters:
  • price (float) – Original quoted price before slippage.

  • shares (float) – Number of shares in the order. Ignored.

  • is_buy (bool) – True for buy orders, False for sell orders.

Returns:

Adjusted execution price with random slippage applied.

Return type:

float

property max_slippage: float#

Get the maximum slippage percentage.

property min_slippage: float#

Get the minimum slippage percentage.

property seed: int | None#

Get the random seed.

class SquareRootSlippage(impact_coefficient: float = 0.1, volatility: float = 0.02, average_daily_volume: float = 1000000)#

Bases: SlippageModelInterface

Square root market impact model.

Implements the widely-used square root law of market impact, where price impact scales with the square root of order size.

Variables:
  • impact_coefficient (float) – Scaling factor for impact. Default is 0.1.

  • volatility (float) – Daily volatility as decimal. Default is 0.02.

  • average_daily_volume (float) – Reference daily volume. Default is 1,000,000.

Examples

>>> model = SquareRootSlippage(
...     impact_coefficient=0.1,
...     volatility=0.02,
...     average_daily_volume=1_000_000,
... )
>>> model.apply(price=100.0, shares=10_000, is_buy=True)
100.02  # sqrt(10000/1000000) * 0.1 * 0.02 impact

References

Almgren, R., et al. (2005). “Direct Estimation of Equity Market Impact”

apply(price: float, shares: float, is_buy: bool) float#

Apply square root market impact.

Parameters:
  • price (float) – Original quoted price before slippage.

  • shares (float) – Number of shares in the order.

  • is_buy (bool) – True for buy orders, False for sell orders.

Returns:

Adjusted execution price with market impact applied.

Return type:

float

property average_daily_volume: float#

Get the average daily volume.

property impact_coefficient: float#

Get the impact coefficient.

property volatility: float#

Get the volatility.

class VolumeBasedSlippage(base_slippage: float = 0.0005, volume_impact: float = 0.01, average_daily_volume: float = 1000000)#

Bases: SlippageModelInterface

Volume-proportional slippage model.

Slippage increases linearly with order size relative to average daily volume (ADV).

Variables:
  • base_slippage (float) – Minimum slippage percentage. Default is 0.0005 (5bp).

  • volume_impact (float) – Additional slippage per unit of volume ratio. Default is 0.01.

  • average_daily_volume (float) – Reference volume for normalizing order size. Default is 1,000,000.

Examples

>>> model = VolumeBasedSlippage(
...     base_slippage=0.0005,
...     volume_impact=0.01,
...     average_daily_volume=1_000_000,
... )
>>> model.apply(price=100.0, shares=100_000, is_buy=True)
100.15  # 0.05% base + 0.1% volume impact
apply(price: float, shares: float, is_buy: bool) float#

Apply volume-proportional slippage.

Parameters:
  • price (float) – Original quoted price before slippage.

  • shares (float) – Number of shares in the order.

  • is_buy (bool) – True for buy orders, False for sell orders.

Returns:

Adjusted execution price with slippage applied.

Return type:

float

property average_daily_volume: float#

Get the average daily volume reference.

property base_slippage: float#

Get the base slippage percentage.

property volume_impact: float#

Get the volume impact factor.

Trading Records#

Trading data structures module.

This module defines data classes for representing trade orders, rebalancing results, and portfolio records. These are entities (pure data containers) with minimal derived properties.

The module provides: - PriceData: Container for execution and commission prices - TradeOrder: Immutable representation of executed trade - RebalanceResult: Aggregate result of rebalancing operation - DailyRecord: Daily portfolio snapshot - Record types for commission, shares, and orders

Type aliases for portfolio structures are also defined here to ensure consistent typing across the codebase.

class CommissionRecord(date: Timestamp, ticker: str, commission: float)#

Bases: object

Record of commission payment for a single ticker.

Used for tracking and reporting commission breakdown by ticker and date.

Variables:
  • date (pd.Timestamp) – Date of the commission payment.

  • ticker (str) – Ticker symbol associated with the commission.

  • commission (float) – Commission amount in currency units.

Examples

>>> record = CommissionRecord(
...     date=pd.Timestamp('2024-01-15'),
...     ticker='AAPL',
...     commission=1.50,
... )
class DailyRecord(date: Timestamp, portfolio_value: float, total_value: float, cash: float, weights: dict[str, float]=<factory>)#

Bases: object

Daily portfolio status record.

Immutable snapshot of portfolio state at end of trading day. Created for each day in the backtest period.

Variables:
  • date (pd.Timestamp) – Date of the record. Normalized to midnight (00:00:00).

  • portfolio_value (float) – Value of holdings (excluding cash). Sum of (shares x price) for all positions.

  • total_value (float) – Holdings plus cash position. This is the total portfolio value.

  • cash (float) – Cash position in currency units. Can be negative if validation is disabled.

  • weights (dict[str, float]) – Portfolio weights by ticker. Includes ‘CASH_RESERVE’ key for cash weight. Weights should sum to approximately 1.0.

Examples

Creating a daily record:

>>> record = DailyRecord(
...     date=pd.Timestamp('2024-01-15'),
...     portfolio_value=95000.0,
...     total_value=100000.0,
...     cash=5000.0,
...     weights={'AAPL': 0.50, 'MSFT': 0.45, 'CASH_RESERVE': 0.05},
... )
>>> record.total_value
100000.0

Calculating return:

>>> daily_return = (today.total_value / yesterday.total_value) - 1

Notes

The weights dictionary always includes a ‘CASH_RESERVE’ key representing the cash portion of the portfolio. Ticker weights are calculated as (shares x price) / total_value.

class OrderRecord(date: Timestamp, ticker: str, shares: float, commission: float)#

Bases: object

Record of an executed order.

Simplified order representation for reporting and analysis.

Variables:
  • date (pd.Timestamp) – Date of order execution.

  • ticker (str) – Ticker symbol.

  • shares (float) – Number of shares traded. Signed: negative for sells, positive for buys.

  • commission (float) – Commission charged for the order.

Examples

Buy order:

>>> record = OrderRecord(
...     date=pd.Timestamp('2024-01-15'),
...     ticker='AAPL',
...     shares=100.0,  # Positive for buy
...     commission=1.00,
... )

Sell order:

>>> record = OrderRecord(
...     date=pd.Timestamp('2024-01-15'),
...     ticker='MSFT',
...     shares=-50.0,  # Negative for sell
...     commission=0.50,
... )
type PortfolioSchedule = dict[Timestamp, TickerWeights]#

Mapping of rebalance dates to ticker weight dictionaries.

Keys are pd.Timestamp objects representing rebalance dates. Values are TickerWeights dictionaries for that date.

Examples

>>> schedule: PortfolioSchedule = {
...     pd.Timestamp('2024-01-01'): {'AAPL': 0.5, 'MSFT': 0.5},
...     pd.Timestamp('2024-04-01'): {'AAPL': 0.6, 'GOOG': 0.4},
... }
class PriceData(execution_price: float, vwap_price: float)#

Bases: object

Container for prices used in trade execution.

Separates the price used for execution from the price used for commission calculation. This supports realistic scenarios where execution happens at one price (e.g., close) but commission is calculated on another (e.g., VWAP).

Variables:
  • execution_price (float) – Price at which the trade is executed. This is the price used to calculate the trade value. Must be positive.

  • vwap_price (float) – Volume-weighted average price. Typically used for commission calculation to match institutional trading practices. Must be positive.

Examples

Creating price data:

>>> prices = PriceData(execution_price=150.25, vwap_price=150.10)
>>> prices.execution_price
150.25
>>> prices.vwap_price
150.10

Using in trade execution:

>>> shares_value = shares * prices.execution_price
>>> commission = commission_model.calculate(
...     shares=math.floor(amount / prices.vwap_price)
... )

Notes

The separation of execution_price and vwap_price matches the original backtester behavior where shares are calculated using execution price but commission uses VWAP-derived shares.

class RebalanceResult(date: Timestamp, buy_orders: list[TradeOrder] = <factory>, sell_orders: list[TradeOrder] = <factory>, total_commissions: float = 0.0, total_buy_value: float = 0.0, total_sale_value: float = 0.0, status: RebalanceStatus = RebalanceStatus.SUCCESS, error_message: str = '')#

Bases: object

Result of a portfolio rebalancing operation.

Aggregates all trades and metrics from a single rebalancing event. This is a mutable dataclass to allow updating status and error messages during execution.

Variables:
  • date (pd.Timestamp) – Date of rebalancing. Normalized to midnight (00:00:00).

  • buy_orders (list[TradeOrder]) – List of executed buy orders. Empty list if no buys were executed.

  • sell_orders (list[TradeOrder]) – List of executed sell orders. Empty list if no sells were executed.

  • total_commissions (float) – Sum of all commissions from all orders. Default is 0.0.

  • total_buy_value (float) – Total value of all purchases (shares x price). Default is 0.0.

  • total_sale_value (float) – Total value of all sales (shares x price). Default is 0.0.

  • status (RebalanceStatus) – Outcome status of the rebalancing. Default is SUCCESS.

  • error_message (str) – Error description if status is not SUCCESS. Empty string for successful rebalancing.

Examples

Creating a rebalance result:

>>> result = RebalanceResult(
...     date=pd.Timestamp('2024-01-15'),
...     buy_orders=[buy_order],
...     sell_orders=[sell_order],
...     total_commissions=2.00,
...     total_buy_value=15000.0,
...     total_sale_value=10000.0,
... )
>>> result.status
<RebalanceStatus.SUCCESS: 'success'>

Handling failure:

>>> result.status = RebalanceStatus.INSUFFICIENT_CASH
>>> result.error_message = "Cash went negative: -$500.00"

Notes

This is not frozen because status and error_message may need to be updated after initial creation (e.g., when cash validation fails after trades are calculated).

The default_factory=list ensures each instance gets its own list objects, avoiding shared state issues.

class SharesRecord(date: Timestamp, ticker: str, shares: float)#

Bases: object

Record of share holdings for a single ticker.

Used for tracking share positions after rebalancing events.

Variables:
  • date (pd.Timestamp) – Date of the record (typically a rebalance date).

  • ticker (str) – Ticker symbol.

  • shares (float) – Number of shares held. Always non-negative after valid rebalancing.

Examples

>>> record = SharesRecord(
...     date=pd.Timestamp('2024-01-15'),
...     ticker='AAPL',
...     shares=100.0,
... )
type TickerWeights = dict[str, float]#

Mapping of ticker symbols to their portfolio weights.

Keys are ticker symbols (e.g., ‘AAPL’, ‘MSFT’). Values are decimal weights that should sum to 1.0 (or less with cash reserve).

Examples

>>> weights: TickerWeights = {'AAPL': 0.4, 'MSFT': 0.3, 'GOOG': 0.3}
class TradeOrder(ticker: str, shares: float, side: OrderSide, execution_price: float, commission: float)#

Bases: object

Immutable representation of an executed trade order.

This is a pure entity capturing trade execution details. Created by ExecutionBroker when processing rebalancing orders.

Variables:
  • ticker (str) – Security ticker symbol (e.g., ‘AAPL’, ‘MSFT’). Must be non-empty.

  • shares (float) – Number of shares traded. Always positive regardless of order side.

  • side (OrderSide) – Direction of the trade (BUY or SELL). Determines if shares are added or removed from portfolio.

  • execution_price (float) – Price per share at execution. After slippage adjustment if applicable.

  • commission (float) – Commission charged for this trade. Non-negative value in currency units.

Examples

Creating a buy order:

>>> order = TradeOrder(
...     ticker='AAPL',
...     shares=100.0,
...     side=OrderSide.BUY,
...     execution_price=150.00,
...     commission=1.00,
... )
>>> order.ticker
'AAPL'
>>> order.shares
100.0

Calculating trade value:

>>> trade_value = order.shares * order.execution_price
>>> print(f"Trade value: ${trade_value:,.2f}")
Trade value: $15,000.00

Notes

This is an immutable dataclass (frozen=True) to ensure trade records cannot be modified after creation, maintaining data integrity in backtest results.