| Overall Statistics |
|
Total Trades 74 Average Win 3.91% Average Loss -5.38% Compounding Annual Return 7.132% Drawdown 13.100% Expectancy 0.431 Net Profit 111.041% Sharpe Ratio 0.783 Loss Rate 17% Win Rate 83% Profit-Loss Ratio 0.73 Alpha 0.151 Beta -5.769 Annual Standard Deviation 0.074 Annual Variance 0.006 Information Ratio 0.566 Tracking Error 0.074 Treynor Ratio -0.01 Total Fees $0.00 |
import numpy as np
import decimal
### <summary>
### RSI algorithm the buys on overbought and sells on oversold but only with trend.
### </summary>
class TrendRSIAlgorithm(QCAlgorithm):
'''Trend following: if strong dailt RSI, up trend. If weak RSI: downtrend. Take entry on hourly overbought or oversold.'''
def Initialize(self):
'''Initializes cash and max trade size. Sets target currencies to run on.'''
# What do we want to trade?
self.currenciesToUse = {"EURUSD", "AUDUSD", "EURGBP", "USDNZD"}
self.SetStartDate(2007,10, 7) #Set Start Date
#self.SetEndDate(2016,6,11) #Set End Date
# Strategy cash management rules
self.SetCash(1000) # Set Strategy Cash
self.numlots = 4 # number of mini lots to trade (all positions added up)
# Total size of all trades added together:
self.maxTradeSize = self.numlots*10000 # 10000 = 1 mini lot
# Integer actually equals number of pips when priced this way....
self.profitTarget = self.numlots*40 # profit per mini
self.stopLoss = self.numlots*0 # stop per mini
self.secondTrade = self.numlots*20 # when to try to get out of trouble if no stop
self.criticalDeath = self.secondTrade*3 # when to abandon all hope and close it
# Strategy RSI values
self.overBought = 80
self.overSold = 20
self.uptrend = 65
self.downtrend = 35
self.SetBrokerageModel(BrokerageName.OandaBrokerage)
self.rsiSlow = {}
self.rsiFast = {}
self.inTrouble = {}
# Setup currencies
for cur in self.currenciesToUse:
# Add symbols to universe
self.Debug("Adding symbol: " + cur)
self.forex = self.AddForex(cur, Resolution.Hour, Market.Oanda)
self.rsiFast[cur] = self.RSI(cur, 30, MovingAverageType.Simple, Resolution.Hour)
self.rsiSlow[cur] = self.RSI(cur, 30, MovingAverageType.Simple, Resolution.Daily)
self.inTrouble[cur] = 0
# If we don't warmup, we are trading randomly right on day one
self.SetWarmUp(30, Resolution.Daily)
# Adjust so portfolio total stays within max trade size (not counting getting out of trouble)
self.tradesize = round(self.maxTradeSize / len(self.currenciesToUse))
self.profitTarget = round(self.profitTarget / len(self.currenciesToUse))
self.stopLoss = round(self.stopLoss / len(self.currenciesToUse))
self.secondTrade = round(self.secondTrade / len(self.currenciesToUse))
self.criticalDeath = round(self.criticalDeath / len(self.currenciesToUse))
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 stock data
'''
# Probably not necessary, but good to be safe ;)
if self.IsWarmingUp:
return
# Now loop through everyone we want to trade
for symbol in self.ActiveSecurities.Keys:
holdings = self.Portfolio[symbol.Value].Quantity
if holdings == 0:
# No current holdings, so initiate new position if appropriate
self.InitiatePosition(symbol)
else:
# We own it, so figure out what to do
profit = self.Portfolio[symbol.Value].UnrealizedProfit
# Take profit if we have hit profit target
if profit > self.profitTarget:
self.Liquidate(symbol.Value)
self.inTrouble[symbol.Value] = 0
continue # don't bother with anything else once we profit
if self.stopLoss == 0:
# No stop, we either die or take second trade
# Critical death is a last resort stop, so must check for it
if profit < -self.criticalDeath:
# Things have gone horribly wrong, just throw in the towel
self.Debug("ZOMG hit critical death: " + symbol.Value)
self.Liquidate(symbol.Value)
continue # required since this likely means RSI is extreme so we'd liquide then immediate take a 2nd trade
# Didn't stop out or take profit, so deal with 2nd trade situation
# Filter to let it move against us enough before adding to the position
if profit < -self.secondTrade:
self.HandleSecondTrade(symbol, holdings)
continue # for completeness or adding code later, not necessary here
else:
# We have a stop, so use it
# (we check every bar instead of setting stops to give us some room to come around)
if profit < -self.stopLoss:
# get out!
self.Liquidate(symbol.Value)
def InitiatePosition(self, symbol):
# Filter on Trend
if self.rsiSlow[symbol.Value].Current.Value > self.uptrend:
# Up trend so only take long
# Price below oversold RSI, so buy
if self.rsiFast[symbol.Value].Current.Value < self.overSold:
self.MarketOrder(symbol, self.tradesize)
return
if self.rsiSlow[symbol.Value].Current.Value < self.downtrend:
# Downtrend so only take short
# Price above overbought, so sell
if self.rsiFast[symbol.Value].Current.Value > self.overBought:
self.MarketOrder(symbol, -self.tradesize)
return
def HandleSecondTrade(self, symbol, holdings):
'''Essentially, we take a 2nd trade if the 1st has failed to profit
and we hit overbought or oversold again'''
# in trouble, try to get out
# inTrouble tracks whether we already added to the position
if self.inTrouble[symbol.Value] == 0:
# haven't initiated fix, so try to do better
if holdings < 0:
# We are short, so add short
if self.rsiFast[symbol.Value].Current.Value > self.overBought:
self.MarketOrder(symbol, -self.tradesize)
self.inTrouble[symbol.Value] = 1
else:
# We are long, so try to add long
if self.rsiFast[symbol.Value].Current.Value < self.overSold:
self.MarketOrder(symbol, self.tradesize)
self.inTrouble[symbol.Value] = 1