| Overall Statistics |
|
Total Trades 43 Average Win 49.73% Average Loss -4.99% Compounding Annual Return 304.381% Drawdown 37.800% Expectancy 3.697 Net Profit 1995.534% Sharpe Ratio 2.181 Loss Rate 57% Win Rate 43% Profit-Loss Ratio 9.96 Alpha 0.567 Beta 0.44 Annual Standard Deviation 0.497 Annual Variance 0.247 Information Ratio -0.167 Tracking Error 0.547 Treynor Ratio 2.465 Total Fees $0.00 |
import clr
clr.AddReference("System")
clr.AddReference("QuantConnect.Algorithm")
clr.AddReference("QuantConnect.Indicators")
clr.AddReference("QuantConnect.Common")
from System import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Indicators import *
from QuantConnect.Orders import *
import decimal as d
class MovingAverageAlgorithm(QCAlgorithm):
def __init__(self):
# Market parameters
self.previousTime = None
self.previousPrice = None
self.previousTrigger = None
self.buyTicket = None
self.sellTicket = None
self.stopTicket = None
self.stopMonitor = False
self.symbol = "BTCEUR"
self.base = "BTC"
self.quote = "EUR"
self.market = Market.GDAX
self.brokerage = BrokerageName.GDAX
self.resolution = Resolution.Daily
self.conversionRate = 1.23
# Strategy parameters
self.length_fast_ema = 5
self.length_slow_ema = 14
self.initial_stop_percent_delta = 0.1
self.follow_factor = 1
def Initialize(self):
'''Initialise algorithm with data, resolution, cash and start-end dates'''
# Add string correspondance to each Orders.OrderType:
self.orderTypeDict = {OrderType.Market: "Market", OrderType.Limit: "Limit",
OrderType.StopMarket: "StopMarket", OrderType.StopLimit: "StopLimit",
OrderType.MarketOnOpen: "MarketOnOpen", OrderType.MarketOnClose: "MarketOnClose"}
# Add string correspondance to each Orders.Direction:
self.orderDirectionDict = {OrderDirection.Buy: "Buy", OrderDirection.Sell: "Sell",
OrderDirection.Hold: "Hold"}
self.SetStartDate(2016,1,1) # Set Start Date
self.SetEndDate(2018,3,4) # Set End Date
self.Portfolio.SetCash(0) # Set USD to 0 since we only have EUR in our account
self.Portfolio.SetCash("EUR", 1000, self.conversionRate) # Set EUR strategy cash with static EURUSD conversion rate
self.AddCrypto(self.symbol, self.resolution, self.market) # symbol to trade
self.SetBrokerageModel(self.brokerage, AccountType.Cash) # crypto brokerage
# Indicators
self.ema_fast = self.EMA(self.symbol, self.length_fast_ema, self.resolution) # fast moving average
self.ema_slow = self.EMA(self.symbol, self.length_slow_ema, self.resolution) # slow moving average
# Benchmark is buy & hold of the traded security
self.SetBenchmark(self.symbol)
# Note - use single quotation marks: ' instead of double "
# Chart - Master Container for the Chart:
coinPlot = Chart('Strategy Equity')
# On the Trade Plotter Chart we want 3 series: trades and price:
coinPlot.AddSeries(Series('Benchmark', SeriesType.Line, 0))
coinPlot.AddSeries(Series('Fast EMA', SeriesType.Line, 0))
coinPlot.AddSeries(Series('Slow EMA', SeriesType.Line, 0))
def OnData(self, data):
'''OnData event is the primary entry point for your algorithm.
Each new data point will be pumped in here.
Arguments:
data: Slice object keyed by symbol containing the security data
'''
# 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 indicators to fully initialize
if not self.ema_slow.IsReady:
return
# Allow one trade maximum per day
if self.previousTime is not None and self.previousTime.date() == self.Time.date():
return
# Retrieve quote asset. CashBook is a dictionary of currencies (including crypto assets)
availableQuote = self.Portfolio.CashBook[self.quote].Amount
# Retrieve current holdings
holdings = self.Portfolio.CashBook[self.base].Amount
# Retrieve current price
currentPrice = self.Securities[self.symbol].Close
## UPDATE TRAILING STOP LIMIT ORDER ##
# Increase trailing stop limit if it exist and if necessary:
if self.stopMonitor:
if currentPrice > self.previousPrice:
ratio = currentPrice / self.previousPrice
adjustedRatio = (ratio + self.follow_factor) / (1 + self.follow_factor)
trigger = self.previousTrigger * d.Decimal(adjustedRatio)
self.previousTrigger = trigger
if self.stopTicket is not None:
self.stopTicket.cancel()
self.stopTicket = None
elif currentPrice <= self.previousTrigger:
# limit price is set below the trigger to maximise the chances of catching a price decrease
stopLimitPrice = self.previousTrigger * d.Decimal(0.99)
# Use round(price, precision) to meet brokerage price precision requirement
self.stopTicket = self.LimitOrder(self.symbol, -holdings, round(stopLimitPrice, 2))
# Define a small tolerance to avoid bouncing when comparing indicators:
tolerance = 0.00015
## BUY ORDER ##
# 1. Go long if we're currently short or flat
# Note that we cannot short crypto assets at the moment with GDAX API
# 2. If fast ema is greater than slow ema, then go long
# 3. Wait for price confirmation: price must exceed a recent high
if holdings <= 0:
if self.ema_fast.Current.Value > self.ema_slow.Current.Value * d.Decimal(1 + tolerance):
limitPrice = currentPrice * d.Decimal(1+0.001)
# use all cash on long, 0.999 is here to avoid buying power issues
quantity = (availableQuote / limitPrice) * d.Decimal(0.999)
lot = self.Securities[self.symbol].SymbolProperties.LotSize
roundedQuantity = round(quantity/lot-d.Decimal(0.5))*lot # -0.5 is present to round down the quantity
self.buyTicket = self.LimitOrder(self.symbol, roundedQuantity, round(limitPrice, 2))
## SELL ORDER ##
# 1. Liquidate if we're currently long
# 2. If fast ema is less than slow ema then liquidate the long
if holdings > 0 and self.stopTicket is None:
if self.ema_fast.Current.Value < self.ema_slow.Current.Value:
limitPrice = currentPrice * d.Decimal(1-0.001)
self.sellTicket = self.LimitOrder(self.symbol, -holdings, round(limitPrice, 2)) # sell entire position
# Store in memory the time and the price of the last execution
self.previousTime = self.Time
self.previousPrice = currentPrice
# Plot indicators and benchma
self.Plot('Strategy Equity', 'Benchmark', self.Securities[self.symbol].Price)
self.Plot('Strategy Equity', 'Fast EMA', self.ema_fast.Current.Value)
self.Plot('Strategy Equity', 'Slow EMA', self.ema_slow.Current.Value)
def OnOrderEvent(self, event):
# Handle filling of buy & sell & orders:
# Determine if order is the buy or the sell or the stop
order = self.Transactions.GetOrderById(event.OrderId)
## BUY ORDER ##
if self.buyTicket is not None:
self.Debug("Event detected: {0} {1}".format(self.orderTypeDict[order.Type], self.orderDirectionDict[order.Direction]))
self.Debug("{0}".format(event))
if event.OrderId == self.buyTicket.OrderId:
# If buy order is filled, monitor price to create a simulated stop limit order if needed
if self.buyTicket.Status == OrderStatus.Filled:
self.previousTrigger = d.Decimal(1 - self.initial_stop_percent_delta) * self.buyTicket.AverageFillPrice
self.buyTicket = None
self.stopMonitor = True
return
## SELL ORDER ##
if self.sellTicket is not None:
self.Debug("Event detected: {0} {1} ==> Normal Sell Order".format(self.orderTypeDict[order.Type], self.orderDirectionDict[order.Direction]))
self.Debug("{0}".format(event))
if event.OrderId == self.sellTicket.OrderId:
# If sell order is filled, cancel stop loss
if self.sellTicket.Status == OrderStatus.Filled:
self.sellTicket = None
self.stopMonitor = False
if self.stopTicket is not None:
self.stopTicket.Cancel()
self.stopTicket = None
return
## STOP ORDER ##
if self.stopTicket is not None:
self.Debug("Event detected: {0} {1} ==> Simulated Stop Limit Order".format(self.orderTypeDict[order.Type], self.orderDirectionDict[order.Direction]))
self.Debug("{0}".format(event))
if event.OrderId == self.stopTicket.OrderId:
# If stop order is filled, cancel the sell order, if any:
if self.stopTicket.Status == OrderStatus.Filled:
self.stopTicket = None
self.stopMonitor = False
if self.sellTicket is not None:
self.sellTicket.Cancel()
self.sellTicket = None
return