Overall Statistics
Total Orders
24
Average Win
6.24%
Average Loss
-3.72%
Compounding Annual Return
15.983%
Drawdown
12.600%
Expectancy
0.461
Start Equity
100000
End Equity
134412.37
Net Profit
34.412%
Sharpe Ratio
0.642
Sortino Ratio
0.662
Probabilistic Sharpe Ratio
41.094%
Loss Rate
45%
Win Rate
55%
Profit-Loss Ratio
1.68
Alpha
0.08
Beta
0.38
Annual Standard Deviation
0.127
Annual Variance
0.016
Information Ratio
0.473
Tracking Error
0.165
Treynor Ratio
0.214
Total Fees
$33.46
Estimated Strategy Capacity
$980000000.00
Lowest Capacity Asset
QQQ RIWIV7K5Z9LX
Portfolio Turnover
2.83%
# region imports
from AlgorithmImports import *
# endregion

class VirtualAsparagusSheep(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2022, 1, 1)
        self.set_end_date(2024, 1, 1)
        self.set_cash(100000)

        self.qqq = self.add_equity("QQQ", Resolution.DAILY).symbol

        # qqq.set_data_normalization_mode(DataNormalizationMode.RAW)

        self.entryTicket = None
        self.stopMarketTicket = None
        self.entryTime = datetime.min
        self.stopMarketOrderFillTime = datetime.min
        self.highestPrice = 0

        self.set_benchmark("QQQ")
        # self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.CASH)

    def on_data(self, data: Slice):
        # wait 30 days after last exit
        if (self.time - self.stopMarketOrderFillTime).days < 30:
            return

        price = self.securities[self.qqq].price
        
        # send entry limit order if not already invested or sent the order
        if not self.portfolio.invested and not self.transactions.get_open_orders(self.qqq):
            quantity = self.calculate_order_quantity(self.qqq, 0.9)
            # LIMIT ORDER SENT HERE
            self.entryTicket = self.limit_order(self.qqq, quantity, price, "Entry order")
            self.entryTime = self.time

        # move limit price if limit order not filled after 1 day
        if (self.time - self.entryTime).days > 1 and self.entryTicket.status != OrderStatus.FILLED:
            self.entryTime = self.time
            updateFields = UpdateOrderFields()
            updateFields.limit_price = price
            self.entryTicket.update(updateFields)

        # move up trailing stop price if invested and stop loss ticket exists
        if self.stopMarketTicket is not None and self.portfolio.invested:
            if price > self.highestPrice:
                self.highestPrice = price
                updateFields = UpdateOrderFields()
                updateFields.stop_price = price * 0.95
                self.stopMarketTicket.update(updateFields)


    def on_order_event(self, orderEvent):
        # waiting
        if orderEvent.status != OrderStatus.FILLED:
            return

        # send trailing stop loss order if entry limit order is filled
        if self.entryTicket is not None and self.entryTicket.order_id == orderEvent.order_id:
            # STOP LOSS ORDER SENT HERE
            self.stopMarketTicket = self.stop_market_order(self.qqq, -self.entryTicket.quantity, 0.95 * self.entryTicket.average_fill_price) 

        # save fill time of stop loss order
        if self.stopMarketTicket is not None and self.stopMarketTicket.order_id == orderEvent.order_id:
            self.stopMarketOrderFillTime = self.time
            self.highestPrice = 0