| Overall Statistics |
|
Total Trades 0 Average Win 0% Average Loss 0% Compounding Annual Return 0% Drawdown 0% Expectancy 0 Net Profit 0% Sharpe 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 -1.043 Tracking Error 0.134 Treynor Ratio 0 Total Fees $0.00 Estimated Strategy Capacity $0 Lowest Capacity Asset |
f = False
if f:
from AlgorithmImports import *
from collections import deque
from typing import List
import configs as cfg
from goldencross import GoldenCross
from datetime import timedelta
class EnergeticBlueDonkey(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2021, 1, 1)
self.SetEndDate(2021, 1, 15)
self.SetCash(100000)
self.symbol = self.AddEquity('SPY', Resolution.Minute).Symbol
self.gc = GoldenCross(cfg.fast_sma_period, cfg.slow_sma_period)
option = self.AddOption(self.symbol)
option.SetFilter(-20, +20, timedelta(25), timedelta(35))
self.options : List[Symbol] = []
# makes it so we emulate Daily Resolution on Minute Resolution
# this is necessary since we are dealing with options, which only work on Minute or finer data
self.curr_day = -1
self.SetWarmUp(cfg.slow_sma_period + 3, Resolution.Daily)
def OnData(self, data:Slice):
if self.curr_day == self.Time.day:
return
self.curr_day = self.Time.day
self.ProcessOptions(data)
if not data.Bars.ContainsKey(self.symbol):
return
self.gc.Update(data[self.symbol])
if self.IsWarmingUp or not self.gc.IsReady:
return
self.Plots()
def ProcessOptions(self, data:Slice):
if cfg.debug: self.Debug('processing options')
self.options = []
for chain in data.OptionChains.Values:
chain: OptionChain = chain
contracts = [optionContract for optionContract in chain if cfg.option_filter(optionContract)]
underlying_price = self.Securities[self.symbol].Price
nearTheMoney = sorted(contracts, key=lambda contract: abs(contract.Strike - underlying_price))[:cfg.options_count]
self.options.extend([contract.Symbol for contract in nearTheMoney])
if cfg.debug: self.Debug(f'Found {len(self.options)} options')
def Plots(self):
if not cfg.debug:
return
self.Plot('GoldenCross', 'Value', self.gc.Value)
f = False
if f: from AlgorithmImports import *
# disable below if you want to reduce logging/plotting
debug = True
#BEGIN GoldenCross configurations
fast_sma_period = 5
slow_sma_period = 20
assert(fast_sma_period < slow_sma_period)
# entry condition after cross has formed
def entry_condition(curr_price:float, fast_sma:float, slow_sma:float)->bool:
'''
return True iff entry condition is met
'''
sma_avg = (fast_sma + slow_sma) / 2
# 4% within average of two SMAs
return abs(1-(curr_price / sma_avg)) < .04
#END GoldenCross configurations
#BEGIN Options configurations
options_count = 3 # how many of the top delta options
target_delta = .5
def option_filter(optionContract: OptionContract):
'''
return True iff contract is a call and Greek conditions are met
'''
if optionContract.Right != OptionRight.Call:
return False
elif optionContract.Greeks.Theta < -.05:
return False
elif (
optionContract.Greeks.Delta < target_delta * .9
or optionContract.Greeks.Delta > target_delta * 1.1
):
return False
return True
#END Options configurationsf = False
if f:
from AlgorithmImports import *
import configs as cfg
from collections import deque
class GoldenCross:
def __init__(self, fast_period:int, slow_period:int):
'''
GoldenCross indicator
.Value = 0 -> not golden cross or death cross
.Value = 1 -> golden cross formed, entry not
.Value = 2 -> entry formed after golden cross
'''
self.Value = 0
self.fast_sma = SimpleMovingAverage(fast_period)
self.slow_sma = SimpleMovingAverage(slow_period)
# fast sma - slow sma
self.sma_diffs = deque(maxlen=3)
def dq_rdy(self, vals:deque):
'''
returns True iff the deque is has maxlen elements
'''
return len(vals) == vals.maxlen
def Update(self, input:TradeBar):
'''
updates the Golden Cross indicator with a new bar of data
returns self.IsReady
'''
self.Time = input.EndTime
close = input.Close
self.fast_sma.Update(self.Time, close)
self.slow_sma.Update(self.Time, close)
if not self.slow_sma.IsReady:
# since the slow_sma takes more values, if its ready
# the fast_sma must be ready
return False
self.sma_diffs.append(
self.fast_sma.Current.Value - self.slow_sma.Current.Value
)
if not self.dq_rdy(self.sma_diffs):
return False
is_crossed = (
self.sma_diffs[2] > 0 and self.sma_diffs[1] < 0 and self.sma_diffs[0] < 0
) # if the fast just recently rises above the slow
is_death_crossed = (
self.sma_diffs[2] < 0 and self.sma_diffs[1] > 0 and self.sma_diffs[0] > 0
) # if the fast just recently dips above the slow
if is_death_crossed:
self.Value = 0
if self.Value <= 0 and is_crossed:
self.Value = 1
elif self.Value == 1 and cfg.entry_condition(close, self.fast_sma.Current.Value, self.slow_sma.Current.Value) :
self.Value = 2
return True
def Warmup(self):
pass
@property
def IsReady(self):
'''
returns True iff the indicator is ready to use
'''
return self.dq_rdy(self.sma_diffs)