| Overall Statistics |
|
Total Orders 293 Average Win 0.90% Average Loss -0.67% Compounding Annual Return 16.467% Drawdown 20.100% Expectancy 0.068 Start Equity 100000 End Equity 113822.53 Net Profit 13.823% Sharpe Ratio 0.411 Sortino Ratio 0.503 Probabilistic Sharpe Ratio 35.586% Loss Rate 54% Win Rate 46% Profit-Loss Ratio 1.33 Alpha -0.002 Beta 0.898 Annual Standard Deviation 0.184 Annual Variance 0.034 Information Ratio -0.063 Tracking Error 0.163 Treynor Ratio 0.084 Total Fees $1630.98 Estimated Strategy Capacity $63000.00 Lowest Capacity Asset HNVR XYDJ6GBNI8V9 Portfolio Turnover 13.50% |
# region imports
from AlgorithmImports import *
from datetime import datetime, timedelta
from dataclasses import dataclass
import csv
from io import StringIO
# endregion
@dataclass
class Signal:
Number: int
Ticker: str
startDate: str
endDate: str
startPrice: float
endPrice: float
class StockTradingAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetCash(100000)
self.current_index = 0
self.active_stocks = {}
self.number_of_stocks = int(
self.GetParameter("number_of_stocks", "7")
)
self.holding_period = int(
self.GetParameter("holding_period", "7")
)
self.frequency = int(
self.GetParameter("frequency", "4")
)
self.dropbox_url =str(
# 5 stocks - FZR5_NOOTC_1W_1YR
# self.GetParameter("dropbox_url", "https://www.dropbox.com/scl/fi/07loovahjlaxmuujg41i7/FZR5_NOOTC_1W_1YR.csv?rlkey=cl7mm9h7usfashr7xjdd1iim3&st=n012ed20&dl=1")
# 7 stocks - VM1_NOOTC_1W_1YR
self.GetParameter("dropbox_url", "https://www.dropbox.com/scl/fi/323i4t3lq5u6524flyb76/VM1_NOOTC_1W_1YR.csv?rlkey=2jimy0z492xq6przr4540arr3&st=w0v3ky1s&dl=1")
# 3 stocks - BMZ_NOOTC_1W_1YR
# self.GetParameter("dropbox_url", "https://www.dropbox.com/scl/fi/ax36xcsx8bcf1zon6rnzz/BMZ_NOOTC_1W_1YR.csv?rlkey=4xhilujlygqm8dkpg3t9q25cl&st=yqlbitlx&dl=1")
)
self.signals = self.GetAllSignals(self.dropbox_url)
self.SetStartDate(self.signals[0].startDate - timedelta(days = 10))
self.SetEndDate(self.signals[len(self.signals) -1].endDate + timedelta(days = 10))
self.next_rotation_date = self.signals[0].startDate - timedelta(days = 1)
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(9, 45), self.CheckForRotation)
def GetAllSignals(self, dropbox_url) -> List[Signal]:
data = self.Download(dropbox_url)
signals= []
if data:
csv_data = StringIO(data)
reader = csv.DictReader(csv_data)
signals = [Signal(
Number=int(row['Number']),
Ticker=row['Ticker'],
startDate=datetime.strptime(row['Start Date'], "%m/%d/%Y"),
endDate=datetime.strptime(row['End Date'], "%m/%d/%Y"),
startPrice=float(row['Start Price']),
endPrice = float(row['End Price'])
) for row in reader]
return signals
def CheckForRotation(self):
if self.Time >= self.next_rotation_date:
self.RotateStocks()
self.next_rotation_date+=timedelta(days=self.holding_period)
def RotateStocks(self):
if self.current_index >= len(self.signals):
for ticker in list(self.active_stocks):
self.Schedule.On(self.DateRules.On(self.active_stocks[ticker]['endDate']), self.TimeRules.BeforeMarketClose(ticker, 1), self.ActionOnEnd(ticker, self.active_stocks[ticker]['endPrice']))
del self.active_stocks[ticker]
self.Debug("All signals processed")
return
# Extract the next batch of 3/7 stocks
next_stocks_data = self.signals[self.current_index:self.current_index + self.number_of_stocks]
self.current_index += self.number_of_stocks
next_tickers = {stock.Ticker for stock in next_stocks_data}
# Liquidate stocks not in the new list
for ticker in list(self.active_stocks):
if ticker not in next_tickers:
self.Schedule.On(self.DateRules.On(self.active_stocks[ticker]['endDate']), self.TimeRules.BeforeMarketClose(ticker, 1), self.ActionOnEnd(ticker,self.active_stocks[ticker]['endPrice']))
del self.active_stocks[ticker]
# Initialize or continue trading the new/continuing stocks
for signal in next_stocks_data:
if signal.Ticker not in self.active_stocks:
self.AddEquity(signal.Ticker, self.frequency)
# we buy a minute later than we sell to make sure there is a balance
self.Schedule.On(self.DateRules.On(signal.startDate), self.TimeRules.BeforeMarketClose(signal.Ticker, 0), self.ActionOnStart(signal))
self.active_stocks[signal.Ticker] = {
'endDate': signal.endDate,
'endPrice': signal.endPrice
}
# Buy function - execute open position
def ActionOnStart(self, signal):
def Action():
# if (self.active_stocks):
self.SetHoldings(signal.Ticker, 1.0 / len(self.active_stocks), False, f"Open ${signal.startPrice}")
self.Debug(f"Bought {signal.Ticker} on {signal.startDate}")
return Action
# Sell function
def ActionOnEnd(self, ticker, endPrice):
def Action():
self.Liquidate(ticker, f"Close ${endPrice}")
self.Debug(f"Sold {ticker}")
return Action
def OnData(self, data):
pass