| Overall Statistics |
|
Total Orders 119 Average Win 0.60% Average Loss -0.53% Compounding Annual Return 3.197% Drawdown 3.600% Expectancy 0.412 Start Equity 100000 End Equity 107869.72 Net Profit 7.870% Sharpe Ratio -1.358 Sortino Ratio -1.355 Probabilistic Sharpe Ratio 46.093% Loss Rate 34% Win Rate 66% Profit-Loss Ratio 1.15 Alpha -0.047 Beta 0.151 Annual Standard Deviation 0.023 Annual Variance 0.001 Information Ratio -1.14 Tracking Error 0.115 Treynor Ratio -0.208 Total Fees $71.00 Estimated Strategy Capacity $890000000.00 Lowest Capacity Asset SPY R735QTJ8XC9X Portfolio Turnover 1.60% |
from AlgorithmImports import *
class LimitOrderAlphaStrategy(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2023, 1, 1)
self.SetEndDate(2025, 5, 28)
self.SetCash(100000)
self.spy = self.AddEquity("SPY", Resolution.Hour).Symbol
self.atr = self.ATR(self.spy, 14, MovingAverageType.Simple, Resolution.Hour)
self.window_size = 20
self.window = RollingWindow[TradeBar](self.window_size)
self.last_order_ticket = None
self.last_order_time = None
self.order_ttl = timedelta(hours=4)
self.Schedule.On(self.DateRules.EveryDay(self.spy),
self.TimeRules.Every(TimeSpan.FromHours(1)),
self.EvaluateLimitOrderStrategy)
def OnData(self, data: Slice):
if data.Bars.ContainsKey(self.spy):
self.window.Add(data[self.spy])
def EvaluateLimitOrderStrategy(self):
if self.window.Count < self.window_size or not self.atr.IsReady:
return
price = self.Securities[self.spy].Price
volatility = self.atr.Current.Value
# Cancel stale limit order
if self.last_order_ticket and self.Time - self.last_order_time > self.order_ttl:
self.last_order_ticket.Cancel("Canceled: TTL expired")
self.last_order_ticket = None
# Don't place new order if one is pending
if self.last_order_ticket and self.last_order_ticket.Status in [OrderStatus.Submitted, OrderStatus.PartiallyFilled]:
return
holdings = self.Portfolio[self.spy].Quantity
recent_closes = [bar.Close for bar in self.window]
avg_price = sum(recent_closes) / len(recent_closes)
direction = 0
threshold = 0.005 # 0.5% instead of 2%
if price < avg_price * (1 - threshold) and holdings == 0:
direction = 1 # Buy
elif price > avg_price * (1 + threshold) and holdings > 0:
direction = -1 # Sell
self.Debug(f"{self.Time} | Price: {price:.2f}, Avg: {avg_price:.2f}, ATR: {volatility:.2f}, Dir: {direction}")
if direction == 0:
return
quantity = self.CalculateOrderQuantity(self.spy, 0.2)
if quantity == 0:
return
# Tighter limit buffer
buffer = volatility * 0.2
if direction == 1:
limit_price = price - buffer
self.last_order_ticket = self.LimitOrder(self.spy, quantity, round(limit_price, 2), "Buy limit order")
else:
limit_price = price + buffer
self.last_order_ticket = self.LimitOrder(self.spy, -holdings, round(limit_price, 2), "Sell limit order")
self.last_order_time = self.Time
self.Debug(f"Submitted {'BUY' if direction == 1 else 'SELL'} limit at {round(limit_price, 2)}")
def OnOrderEvent(self, orderEvent):
if orderEvent.Status == OrderStatus.Filled:
self.Debug(f"FILLED: {orderEvent.Symbol}, Qty: {orderEvent.FillQuantity}, Price: {orderEvent.FillPrice}")