Overall Statistics
Total Orders
2
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Start Equity
100000
End Equity
99908.2
Net Profit
0%
Sharpe Ratio
0
Sortino Ratio
0
Probabilistic Sharpe Ratio
0%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
0
Tracking Error
0
Treynor Ratio
0
Total Fees
$4.30
Estimated Strategy Capacity
$29000000.00
Lowest Capacity Asset
ES YJHOAMPYKQGX
Portfolio Turnover
265.59%
from AlgorithmImports import *
class CustomFutureFillModelAlgorithm(QCAlgorithm):
    def Initialize(self):
        self.set_start_date(2024, 6, 3)
        self.set_end_date(2024, 6, 4)
        self.set_security_initializer(CustomSecurityInitializer(self))
        self.es = self.add_future("ES", extended_market_hours=True)
        self.ticket = None
    def on_data(self, slice):
        if not self.ticket:
            self.market_order(self.es.mapped, 1)
            self.ticket = self.trailing_stop_order(self.es.mapped, -1, 5, False)
        if self.ticket and self.ticket.status != OrderStatus.FILLED:
            self.plot(self.es.mapped.value, 'Price', self.es.price)
            self.plot(self.es.mapped.value, 'Stop', self.ticket.get(OrderField.STOP_PRICE))

class CustomSecurityInitializer(BrokerageModelSecurityInitializer):
    def __init__(self, algorithm):
        super().__init__(algorithm.brokerage_model, SecuritySeeder.NULL)
    def initialize(self, security):
        super().initialize(security)
        security.set_fill_model(CustomFutureFillModel())

class CustomFutureFillModel(FutureFillModel):
    def trailing_stop_fill(self, asset, order):
        utc_time = Extensions.convert_to_utc(asset.local_time, asset.exchange.time_zone)
        fill = OrderEvent(order, utc_time, OrderFee.ZERO)
        if order.Status == OrderStatus.CANCELED:
            return fill

        bar = asset.cache.get_data[TradeBar]()

        # Do not fill on stale data
        if Extensions.convert_to_utc(bar.end_time, asset.exchange.time_zone) <= order.time:
            return fill

        if order.direction == OrderDirection.BUY and bar.high >= order.stop_price:
            fill.Status = OrderStatus.FILLED
            fill.FillQuantity = order.quantity
            # Assuming worse case scenario fill - fill at highest of the stop & asset bid price.
            slip = asset.SlippageModel.GetSlippageApproximation(asset, order)
            fill.FillPrice = max(order.stop_price, asset.ask_price + slip)
        if order.direction == OrderDirection.SELL and bar.low <= order.stop_price:
            fill.Status = OrderStatus.FILLED
            fill.FillQuantity = order.quantity
            # Assuming worse case scenario fill - fill at lowest of the stop & asset bid price.
            slip = asset.SlippageModel.GetSlippageApproximation(asset, order)
            fill.FillPrice = min(order.stop_price, asset.bid_price - slip)

        if fill.Status != OrderStatus.FILLED:
            current_market_price = bar.high if order.direction == OrderDirection.SELL else bar.low
            is_update, updated_stop_price = TrailingStopOrder.try_update_stop_price(current_market_price, order.stop_price, order.trailing_amount, order.trailing_as_percentage, order.Direction, 0)
            if is_update:
                order.stop_price = updated_stop_price
                self.parameters.on_order_updated(order)

        return fill