| Overall Statistics |
|
Total Orders 92 Average Win 0% Average Loss 0.00% Compounding Annual Return -0.870% Drawdown 0.100% Expectancy -1 Start Equity 100000 End Equity 99924.25 Net Profit -0.076% Sharpe Ratio -117.981 Sortino Ratio -123.779 Probabilistic Sharpe Ratio 0% Loss Rate 100% Win Rate 0% Profit-Loss Ratio 0 Alpha -0.052 Beta 0.001 Annual Standard Deviation 0 Annual Variance 0 Information Ratio -5.958 Tracking Error 0.136 Treynor Ratio -56.221 Total Fees $92.00 Estimated Strategy Capacity $25000000.00 Lowest Capacity Asset TSLA UNU3P8Y3WFAD Portfolio Turnover 0.40% |
# region imports
from AlgorithmImports import *
import numpy as np
# endregion
#
# Get last 30 bars, categorize current volume and body as Ultra, high, avg, low.
# Get support and ressistance levels from past 30 bars.
# Test it on HO future first.
# Add ability to test for group of symbols, with different resolutions.
# Train on last one month optimize parameters(n).
# in V2, explore exit strategies.
#
class VolumeSpreadAnalysisStrategy(QCAlgorithm):
def Initialize(self):
self.set_start_date(2023, 1, 1)
self.set_end_date(2023,2, 1)
self.set_cash(100000)
self.symbols = [self.add_equity(s, Resolution.MINUTE).Symbol for s in ["TSLA"]]
self.history_window = 30
self.data_history = {symbol: [] for symbol in self.symbols}
self.exit_data = {}
for symbol in self.symbols:
self.schedule.on(self.date_rules.every_day(symbol), self.time_rules.before_market_close(symbol, 10), self._before_market_close)
def _before_market_close(self) -> None:
self.liquidate(tag="EOD close")
def OnData(self, data: Slice):
if not self.IsWithinTradingHours():
return
for symbol in self.symbols:
if not data.bars.contains_key(symbol):
continue
bar = data.bars[symbol]
history = self.data_history[symbol]
# Calculate bar body
body = abs(bar.close - bar.open)
# Append new data point to history
history.append({
'volume': bar.volume,
'body': body
})
# Keep only last 30 bars
if len(history) > self.history_window:
history.pop(0)
# Ensure enough history
if len(history) < self.history_window:
continue
# Calculate support and resistance.
# Get historical volume and body arrays
volume_list = [h['volume'] for h in history[:-1]] # exclude current
body_list = [h['body'] for h in history[:-1]]
# Calculate thresholds
volume_thresholds = self.GetBuckets(volume_list)
body_thresholds = self.GetBuckets(body_list)
# Classify current volume and body
volume_bucket = self.Classify(bar.volume, volume_thresholds)
body_bucket = self.Classify(body, body_thresholds)
# Trading logic: ULTRA volume and ULTRA body
if volume_bucket == "ULTRA" and body_bucket == "ULTRA":
# Do not buy again if we already has holdings.
if self.portfolio[symbol].invested:
continue
self.debug(f"{self.time} - SELL {symbol} - Volume: {bar.volume} ({volume_bucket}), Body: {body:.2f} ({body_bucket})")
buy_price = bar.close
# Buy 1 stock of that symbol.
self.market_order(symbol, 1, tag=f"Buy order price={buy_price}");
self.exit_data[symbol] = {
"buy_price" : buy_price,
"tp_price" : buy_price + 1,
"stop_loss" : buy_price * 0.9
}
for invested_symbol in self.exit_data:
tp_price = self.exit_data[invested_symbol]['tp_price']
stop_loss = self.exit_data[invested_symbol]['stop_loss']
bar = data.bars[invested_symbol]
if bar.close > tp_price:
self.liquidate(invested_symbol, tag="take profit")
elif bar.close < stop_loss:
self.liquidate(invested_symbol, tag="stop loss")
def IsWithinTradingHours(self):
return self.time.hour >= 10 and self.time.hour < 15
def GetBuckets(self, values):
q25 = np.percentile(values, 25)
q50 = np.percentile(values, 50)
q75 = np.percentile(values, 99)
return {
"LOW": (float('-inf'), q25),
"NORMAL": (q25, q50),
"HIGH": (q50, q75),
"ULTRA": (q75, float('inf'))
}
def Classify(self, value, thresholds):
for bucket, (low, high) in thresholds.items():
if low <= value < high:
return bucket
return "ULTRA" # fallback in edge case