Back

Help with Simple Crypto Strategies

Hi QC Community,

I'm getting frustrated & need some help. I have tried cloning many simple algorithms & changing the tickers to accept "BTCUSD" & I cant get a single one of them working without multiple errors one after another.

On another note, I am finding it very difficult to debug errors in QC, running the backtest just throws off huge error scripts but I cant even tell which line the issue might be in. You cant access cryptos in the notebook at the moment so cant use that for testing. Can anyone reccomend a good way for developing, testing & debugging these strategies in python? Should I stick to Jupyter notebooks or try get on Visual Studio?

Thanks

Strategy 1 - Simple Breakout

# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import clr
clr.AddReference("System")
clr.AddReference("QuantConnect.Algorithm")
clr.AddReference("QuantConnect.Indicators")
clr.AddReference("QuantConnect.Common")

from System import *
import numpy as np
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Indicators import *
import decimal as d

### <summary>
### In this example we look at the canonical 15/30 day moving average cross. This algorithm
### will go long when the 15 crosses above the 30 and will liquidate when the 15 crosses
### back below the 30.
### </summary>
### <meta name="tag" content="indicators" />
### <meta name="tag" content="indicator classes" />
### <meta name="tag" content="moving average cross" />
### <meta name="tag" content="strategy example" />
class ChannelsAlgorithm(QCAlgorithm):

def Initialize(self):
'''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''

self.SetStartDate(2016, 01, 01) #Set Start Date
self.SetEndDate(2017, 10, 19) #Set End Date
self.SetCash(10000) #Set Strategy Cash
# Find more symbols here: http://quantconnect.com/data
self.SetBrokerageModel(BrokerageName.GDAX)
self.AddCrypto("BTCUSD", Resolution.Hour)

# create a 15 day exponential moving average
# self.fast = self.EMA("BTCUSD", 15, Resolution.Daily);

# create a 30 day exponential moving average
# self.slow = self.EMA("BTCUSD", 70, Resolution.Daily);

# Create Channels
self.channel = self.DCH("BTCUSD",20,20,Resolution.Daily)

self.previous = None


def OnData(self, data):
'''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.'''
# a couple things to notice in this method:
# 1. We never need to 'update' our indicators with the data, the engine takes care of this for us
# 2. We can use indicators directly in math expressions
# 3. We can easily plot many indicators at the same time

# wait for our slow ema to fully initialize
# if not self.slow.IsReady:
# return

# only once per day
if self.previous is not None and self.previous.date() == self.Time.date():
return

# define a small tolerance on our checks to avoid bouncing
tolerance = 0.00015;

holdings = self.Portfolio["BTCUSD"].Quantity

# we only want to go long if we're currently short or flat
if holdings <= 0:
# if the fast is greater than the slow, we'll go long
if self.Securities["BTCUSD"].Price > self.channel.UpperBand:
self.Log("BUY >> {0}".format(self.Securities["BTCUSD"].Price))
self.SetHoldings("BTCUSD", 1.0)

# we only want to liquidate if we're currently long
# if the fast is less than the slow we'll liquidate our long
if holdings > 0 and self.Securities["BTCUSD"].Price < self.channel.LowerBand:
self.Log("SELL >> {0}".format(self.Securities["BTCUSD"].Price))
self.Liquidate("BTCUSD")

self.previous = self.Time

Strategy 2:

from datetime import datetime
import clr
clr.AddReference("System")
clr.AddReference("QuantConnect.Algorithm")
clr.AddReference("QuantConnect.Indicators")
clr.AddReference("QuantConnect.Common")

from System import *
import numpy as np
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Indicators import *
import decimal as d
from datetime import timedelta


class DualThrustAlgorithm(QCAlgorithm):

def Initialize(self):

'''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''

self.SetStartDate(2017,01,01)
self.SetEndDate(2017,8,30)
self.SetCash(100000)
equityt = self.AddSecurity(SecurityType.Equity, "SPY", Resolution.Hour)
# equity = self.AddSecurity(SecurityType.Crypto, "BTCUSD", Resolution.Hour)
equity = self.AddCrypto("BTCUSD", Resolution.Daily)
self.syls = equity.Symbol

# schedule an event to fire every trading day for a security
# the time rule here tells it to fire when market open
# self.syl = equity.Symbol
self.syl = "BTCUSD"
# self.Schedule.On(self.DateRules.EveryDay(self.syl),self.TimeRules.AfterMarketOpen(self.syl,5),Action(self.SetSignal))
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.Every(timedelta(minutes=10)),Action(self.SetSignal))
self.selltrig = None
self.buytrig = None
self.currentopen = None

def SetSignal(self):
history = self.History("BTCUSD", 4, Resolution.Daily)

k1 = 0.5
k2 = 0.5
self.high = []
self.low = []
self.close = []
for slice in history:
bar = slice[self.syl]
self.high.append(bar.High)
self.low.append(bar.Low)
self.close.append(bar.Close)
# Pull the open price on each trading day
self.currentopen = self.Portfolio[self.syl].Price

HH, HC, LC, LL = max(self.high), max(self.close), min(self.close), min(self.low)
if HH - LC >= HC - LL:
signalrange = HH - LC
else:
signalrange = HC - LL

self.selltrig = self.currentopen - decimal.Decimal(k1) * signalrange
self.buytrig = self.currentopen + decimal.Decimal(k2) * signalrange

def OnData(self,data):

'''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.'''

holdings = self.Portfolio[self.syl].Quantity

if self.Portfolio[self.syl].Price >= self.selltrig:
if holdings >= 0:
self.SetHoldings(self.syl, 0.8)
else:
self.Liquidate(self.syl)
self.SetHoldings(self.syl, 0.8)

elif self.Portfolio[self.syl].Price < self.selltrig:
if holdings >= 0:
self.Liquidate(self.syl)
self.SetHoldings(self.syl, -0.8)
else:
self.SetHoldings(self.syl, -0.8)

self.Log("open: "+ str(self.currentopen)+" buy: "+str(self.buytrig)+" sell: "+str(self.selltrig))
Update Backtest








I use self.Log(<str>) to leave bread crumbs when I am totally lost(I know pretty basic). After the backtest is completed, the Console gives a URL to view this log. Here is my fix after some playing:

I am unsure why the first strategy doing this, but the issue was the indicator values being returns. Normal float casting didn't work even though printing did, so I made them strings and then floats. Can a QC pro better explain that to us?

self.channel.<value>

to

foat(str(self.channel.<value>))

0


The second one was having trouble with indexing via: bars.<High,Low,etc>
I really like pandas and I see you just wanted the list of values? So I switch to the History call that returns a DataFrame(pandas) and then got the numpy array for each bar item as before. This made things slightly streamlined.

Please note, to prevent decimal error in trigger calcs I converted the current price and historical data to floats. This was for convenience so please consider if this change could impact your code.

0


Hi Aleksey

I am learning also, thank you for sharing. For Strategy 2, I added chart ploting for the Donchian Channels. Derek how would we plot the price in the same chart as the Donchian, so we can see it break out? I find I am a very visual learner and this would help me better optimize. Also, plotting trades in/out on the same plot would be cool. 

Aleksey, I would suggest backtesting over differing periods... I tried since the start of 2017 instead of 2016 and had really extreme drawdown. 

1


Sorry I am unsure how to add items to that nice PlotIndicator chart that gets shown outside the backtest like you see above, but we can add a chart using the followiing example.

0


Regarding the second strategy: sorry I left a mess of Logs in the code, that will waste daily log quota. 

Here is the Second Strategy run over a larger period with no debugging logs. I also added SetBrokerageModel to include fees, removed the sub to SPY in case it was impacting performance(did not see it being used), and added charts llike with previous post to balanace features between the two.

0


This is my currently (failing) shot at integrating that charting method with consolidators. Note OnMinuteData() and On15MinuteData(). Aleksey, let me know if I am hijacking this thread too much or if this is useful... Thank you.

# MultiIndicator v2 (Py)

from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Common")

from System import *
from QuantConnect import *
from QuantConnect.Data import *
from QuantConnect.Algorithm import *
from QuantConnect.Indicators import *
from datetime import datetime
import decimal as d
import numpy as np


class WarmupAlgorithm(QCAlgorithm):

def Initialize(self):

# define email address for buy/sell notifications
# please change prior to Live deploy
self.email_address = 'test@test.com'

self.SetStartDate(2017,11,1) #Set Start Date
self.SetEndDate(2017,11,2) #Set End Date
self.SetCash(3000) #Set Strategy Cash

# define crypto we want to trade on
# ETHUSD or LTCUSD or BTCUSD
self.target_crypto = "ETHUSD"

# Set brokerage to GDAX for cryptos
self.SetBrokerageModel(BrokerageName.GDAX, AccountType.Cash)

# Set crypto and time resolution
self.AddCrypto(self.target_crypto, Resolution.Minute)
self.SetBenchmark(self.target_crypto)

# Request warmup data
self.SetWarmup(2160)

# create consolidator for 1 minute
# even though base resolution is already Minute,
# manually consolidating at the same resolution
# ensures pattern can be the same for larger resolution bars.
consMinute = TradeBarConsolidator(1)
consMinute.DataConsolidated += self.OnMinuteData
self.SubscriptionManager.AddConsolidator(self.target_crypto, consMinute)

# create consolidator for 5 minute
cons15Minute = TradeBarConsolidator(5)
cons15Minute.DataConsolidated += self.On15MinuteData
self.SubscriptionManager.AddConsolidator(self.target_crypto, cons15Minute)

# Define EMAs at minute consolidated resolution
# Note we are at minute resolution
self.ema_minute_f = ExponentialMovingAverage("EMA_Min_f", 2)
self.ema_minute_m = ExponentialMovingAverage("EMA_Min_m", 15)
self.ema_minute_s = ExponentialMovingAverage("EMA_Min_s", 120)

# Define MOMs at minute consolidated resolution
# Note we are at 15 minute resoltuion
self.mom_15minute_f = self.MOM(self.target_crypto, 3)

sPlot = Chart('Strategy Equity')
sPlot.AddSeries(Series('EMA Crossover', SeriesType.Line, 2)) #Only for axis title override
sPlot.AddSeries(Series('ema_minute_f', SeriesType.Line, 2))
sPlot.AddSeries(Series('ema_minute_m', SeriesType.Line, 2))
sPlot.AddSeries(Series('ema_minute_s', SeriesType.Line, 2))
sPlot.AddSeries(Series('mom_15minute_f', SeriesType.Line, 3))
self.AddChart(sPlot)

# Initialize variables
self.holdings = 0
self.tolerance = .001
self.price = self.Securities[self.target_crypto].Price
self.price_last_buy = None
self.price_max = None
self.price_delta_buy = None
self.price_delta_max = None
self.first = True

def OnMinuteData(self, sender, bar):
self.ema_minute_f.Update(bar.EndTime, bar.Close)
self.ema_minute_m.Update(bar.EndTime, bar.Close)
self.ema_minute_s.Update(bar.EndTime, bar.Close)
#self.Debug(str(self.Time) + " > New Minute Bar!")
self.Plot("Strategy Equity", 'EMA 2min', self.ema_minute_f)
self.Plot("Strategy Equity", 'EMA 15min', self.ema_minute_m)
self.Plot("Strategy Equity", 'EMA 120min', self.ema_minute_s)

def On15MinuteData(self, sender, bar):
self.mom_15minute_f.Update(bar.EndTime, bar.Close)
#self.Debug(str(self.Time) + " > New Minute Bar!")
self.Plot("Strategy Equity", "Momentum", self.mom_15minute_f)

def OnData(self, data):
if self.first and not self.IsWarmingUp:
self.first = False
pass

0

Very close, but with a couple changes suggested:
Label mismatch between AddSeries and Plot! Also use ' vs " (I think it says that in docs?) and cast those indicators to a nice float as that was throwing the error I believe. You can see the changes I made to the plotting code in the attached backtest as well as the now working charts.

0


 & liquidgenius I made a pretty big error. The issue was NOT casting the indicator, but rather properly accessing its current value. Please be sure to take note of this as my algos above have wrong examples when plotting the various indicators:

So I was incorectly doing:
float(str(self.<Indicator>))

However, please be sure to use Current.Value to access your indicators correctly:

self.<Indicator>.Current.Value

0

Thanks Derek Tishler ,

Would you be able to help me reference an indicator specific to a particular security? Below is what I have in initialize:

        for asset in self.symbols:
            self.asset = self.<indicator>

Then calling it later on in OnData:

self.asset.Current.Value

Doesnt seem to work.

0

 for something like that, perhaps using a dictionary like:

# inside init
self.indicators = {}
for asset in self.symbols:
self.indicators[asset] = self.<Indicator>()

# later, getting values from dict 1-by-1
for asset in self.symbols:
self.Log( self.indicators[asset].Current.Value )
0

Update Backtest





0

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.


Loading...

This discussion is closed