| Overall Statistics |
|
Total Trades 967 Average Win 0.75% Average Loss -0.43% Compounding Annual Return -97.073% Drawdown 48.900% Expectancy -0.149 Net Profit -26.626% Sharpe Ratio -1.86 Loss Rate 69% Win Rate 31% Profit-Loss Ratio 1.74 Alpha -0.819 Beta -82.198 Annual Standard Deviation 1.113 Annual Variance 1.24 Information Ratio -1.874 Tracking Error 1.113 Treynor Ratio 0.025 Total Fees $0.00 |
/*
* 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.
*/
using System;
using System.Collections.Generic;
using QuantConnect.Data;
using QuantConnect.Orders;
namespace QuantConnect.Algorithm.CSharp
{
public class BasicTemplateForexAlgorithm : QCAlgorithm
{
// Initialize variables
private DateTime startTime = DateTime.Now;
private DateTime startDate = new DateTime(2018, 12, 1);
private DateTime endDate = new DateTime(2019, 1, 1);
private decimal portfolioAmount = 5000;
public int maxLotSize = 100000;
private static decimal maxLeverage = 50m; // Maximum Leverage.
private decimal leverageBuffer = 0.25m; // Percentage of Leverage left unused.
private decimal maxRiskPercent = 0.02m;
readonly string[] NYPairs = new string[] {
"AUDCAD",
"AUDCHF",
"AUDNZD",
"AUDUSD",
"CADCHF",
"EURAUD",
"EURCAD",
"EURCHF",
"EURGBP",
"EURNZD",
"EURUSD",
"GBPAUD",
"GBPCAD",
"GBPCHF",
"GBPNZD",
"GBPUSD",
"NZDCAD",
"NZDCHF",
"NZDUSD",
"USDCAD",
"USDCHF"
};
readonly string[] YPairs = new string[] {
"AUDJPY",
"CADJPY",
"CHFJPY",
"EURJPY",
"GBPJPY",
"NZDJPY",
"USDJPY"
};
/// This is the period of bars that will be created
public readonly TimeSpan FourHrHA = TimeSpan.FromMinutes(240);
public readonly TimeSpan HalfHrHA = TimeSpan.FromMinutes(30);
public readonly decimal BrickSizeNY = 0.001m;
public readonly decimal BrickSizeY = 0.1m;
// Period of the indicators
public readonly int ExpMovAvePd = 20;
public readonly int fastMAPd = 15;
public readonly int slowMAPd = 26;
public readonly int signalPd = 3;
// Direction indicators for 4Hr, 30Min and EMA (1 up, -1 down)
public int FourHrDir = 0;
public int HalfHrDir = 0;
public int EMADir = 0;
public int RenkoDir = 0;
public int TradeDir = 0;
/// This is the number of consolidated bars we'll hold in symbol data for reference
public readonly int FourHrRollWindSize = 2;
public readonly int HalfHrRollWindSize = 8;
public readonly int RenkoRollWindSize = 5;
/// Holds all of our data keyed by each symbol
public Dictionary<string, SymbolData> Data = new Dictionary<string, SymbolData>();
/// Holds all of our data keyed by each symbol
public Dictionary<string, OrderData> orders = new Dictionary<string, OrderData>();
// // Holds all Ticket data keyed by Order Id
public Dictionary<int, string> Orders = new Dictionary<int, string>();
/// Initialize the data, resolution cash, start and end dates required. All algorithms must initialized.
public override void Initialize()
{
SetStartDate(startDate); //Set Start Date
SetEndDate(endDate); //Set End Date
SetCash(portfolioAmount); //Set Strategy Cash
// set the brokerage model
SetBrokerageModel(BrokerageName.OandaBrokerage);
// initialize our forex data
foreach (var symbol in NYPairs)
{
var forex = AddForex(symbol,Resolution.Second);
Data.Add(symbol, new SymbolData(symbol, FourHrRollWindSize, HalfHrRollWindSize, BrickSizeNY, RenkoRollWindSize));
}
/* foreach (var symbol in YPairs)
{
var forex = AddForex(symbol,Resolution.Second);
Data.Add(symbol, new SymbolData(symbol, FourHrRollWindSize, HalfHrRollWindSize, BrickSizeY, RenkoRollWindSize));
}
*/
// loop through all our symbols and request data subscriptions and initialize indicatora
foreach(var kvp in Data)
{
var symbolData = kvp.Value;
// Add the Symbol to data subscriptions
// Define conslidators for 4Hr, 30Min and Renko
var FourHrcons = new QuoteBarConsolidator(FourHrHA);
var HalfHrcons = new QuoteBarConsolidator(HalfHrHA);
var Renkocons = new RenkoConsolidator(symbolData.RenkoBrick);
// Define and register indicators
symbolData.MACD = new MovingAverageConvergenceDivergence(symbolData.Symbol,fastMAPd,slowMAPd,signalPd,MovingAverageType.Exponential);
symbolData.FourHrHA = new HeikinAshi(symbolData.Symbol);
symbolData.HalfHrHA = new HeikinAshi(symbolData.Symbol);
symbolData.EMA = new ExponentialMovingAverage(symbolData.Symbol,ExpMovAvePd).Of(symbolData.HalfHrHA);
// RegisterIndicator(symbolData.Symbol, symbolData.FourHrHA, FourHrcons);
// RegisterIndicator(symbolData.Symbol, symbolData.HalfHrHA, HalfHrcons);
// RegisterIndicator(symbolData.Symbol, symbolData.EMA, HalfHrcons);
RegisterIndicator(symbolData.Symbol, symbolData.MACD, Renkocons);
// Wire up consolidators to update indicators and rolling windows
FourHrcons.DataConsolidated += (sender, consolidated) =>
{
// Debug(Time.ToString("u") + " Open price: " + consolidated.Open + " Close Price: " + consolidated.Close);
symbolData.FourHrHA.Update(consolidated);
//Add new Bar to history
symbolData.FourHrBars.Add(symbolData.FourHrHA);
if(symbolData.FourHrHA.Open>symbolData.FourHrHA.Close)
{
FourHrDir = -1; // Close Lower, Red Candle
// Debug(Time.ToString("o") + " 4Hr Red");
}
else if(symbolData.FourHrHA.Open<symbolData.FourHrHA.Close)
{
FourHrDir = 1; // Close Higher, Green Candle
// Debug(Time.ToString("o") + " 4Hr Green");
}
};
HalfHrcons.DataConsolidated += (sender, consolidated) =>
{
// Update the 30min HA bar
symbolData.HalfHrHA.Update(consolidated);
// Debug(Time.ToString("u") + " Open price: " + symbolData.HalfHrHA.Open + " Close Price: " + symbolData.HalfHrHA.Close);
//Add new Bar to history
symbolData.HalfHrBars.Add(symbolData.HalfHrHA);
var tradeBar = new TradeBar(symbolData.HalfHrHA.Current.EndTime, symbolData.Symbol, symbolData.HalfHrHA.Open, symbolData.HalfHrHA.High, symbolData.HalfHrHA.Low, symbolData.HalfHrHA.Close, 0);
if(symbolData.HalfHrHA.Open>symbolData.HalfHrHA.Close)
{
HalfHrDir = -1; // Close Lower, Red Candle
// Debug(Time.ToString("o") + " 30 Min Red");
}
else if(symbolData.HalfHrHA.Open<symbolData.HalfHrHA.Close)
{
HalfHrDir = 1; // Close Higher, Green Candle
// Debug(Time.ToString("o") + " 30 Min Green");
}
// Update 30Min EMA
symbolData.EMA.Update(tradeBar.Time, tradeBar.Close);
// Add new EMA value to history
symbolData.EMAVals.Add(symbolData.EMA);
if(symbolData.EMAVals.Count > 1)
{
if(symbolData.EMAVals[1]>symbolData.EMAVals[0])
{
EMADir = -1; // EMA slope Down
// Debug("30 Min EMA Slope Down. Last EMA " + symbolData.EMAVals[1].ToString() + " Current EMA " +symbolData.EMAVals[0].ToString());
}
else if(symbolData.EMAVals[1]<symbolData.EMAVals[0])
{
EMADir = 1; // EMA slope Up
// Debug("30 Min EMA Slope up. Last EMA " + symbolData.EMAVals[1].ToString() + " Current EMA " +symbolData.EMAVals[0].ToString());
}
else
{
EMADir = 0; // EMA slope flat
// Debug(" 30 Min EMA Slope flat. Last EMA " + symbolData.EMAVals[1].ToString() + " Current EMA " +symbolData.EMAVals[0].ToString());
}
}
};
Renkocons.DataConsolidated += OnRenkoClose;
// we need to add this consolidator so it gets auto updates
SubscriptionManager.AddConsolidator(symbolData.Symbol, FourHrcons);
SubscriptionManager.AddConsolidator(symbolData.Symbol, HalfHrcons);
SubscriptionManager.AddConsolidator(symbolData.Symbol, Renkocons);
}
}
/// <summary>
/// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
/// </summary>
public void OnData(QuoteBar data)
{
}
public override void OnOrderEvent(OrderEvent orderEvent)
{
}
/// <summary>
/// OnRenkoClose event what happens when each Renko Bar is done.
/// </summary>
public void OnRenkoClose(object sender, RenkoBar bar)
{
var symbolData = Data[bar.Symbol];
int Lots=0;
int OrderTicket;
//Debug("4Hr Direction: " + FourHrDir + " 30Min Direction: " + HalfHrDir + " EMA Direction: " + EMADir
// + " Renko Brick Info: Open " + bar.Open + " Close " + bar.Close + " MACD Histogram " + symbolData.MACD.Histogram);
// Add new Renko bar to history
symbolData.RenkoBars.Add(bar);
// We are in a long position and we have a Red Brick
if(TradeDir == 1 && bar.Open > bar.Close)
{
Liquidate(bar.Symbol); // Liquidate Long Trade
TradeDir = 0; // No Trade Direction
//Debug("Renko Brick Info: Open " + bar.Open + " Close " + bar.Close + " MACD Histogram " + symbolData.MACD.Histogram);
}
// We are in a short position and we have a Green Brick
if(TradeDir == -1 && bar.Open < bar.Close)
{
Liquidate(bar.Symbol); // Liquidate Short Trade
TradeDir = 0; // No Trade Direction
//Debug("Renko Brick Info: Open " + bar.Open + " Close " + bar.Close + " MACD Histogram " + symbolData.MACD.Histogram);
}
if(FourHrDir == 1 && HalfHrDir == 1 && EMADir == 1 &&
bar.Open < bar.Close && symbolData.MACD.Histogram > 0 && TradeDir == 0)
{
//Enter long trade
Lots = CalculateLotSize(bar.Symbol);
OrderTicket=MarketOrder(bar.Symbol, Lots);
TradeDir = 1;
//Debug("4Hr Direction: " + FourHrDir + " 30Min Direction: " + HalfHrDir + " EMA Direction: " + EMADir
// + " Renko Brick Info: Open " + bar.Open + " Close " + bar.Close + " MACD Histogram " + symbolData.MACD.Histogram);
}
if(FourHrDir == -1 && HalfHrDir == -1 && EMADir == -1 &&
bar.Open > bar.Close && symbolData.MACD.Histogram < 0 && TradeDir == 0)
{
//Enter short trade
Lots = -CalculateLotSize(bar.Symbol);
OrderTicket=MarketOrder(bar.Symbol, Lots);
TradeDir = -1;
//Debug("4Hr Direction: " + FourHrDir + " 30Min Direction: " + HalfHrDir + " EMA Direction: " + EMADir
// + " Renko Brick Info: Open " + bar.Open + " Close " + bar.Close + " MACD Histogram " + symbolData.MACD.Histogram);
}
// Debug(Time.ToString("u") + " Open price: " + bar.Open + " Close Price: " + bar.Close);
}
/// <summary>
/// CalculateLotSize determines lot size based on risk tolerance.
/// </summary>
public int CalculateLotSize(string symbol)
{
decimal ShareSize = (maxLeverage * maxRiskPercent); // Risk percentage size calculation for lot size calc
int quantity = (int) (Math.Floor(CalculateOrderQuantity(symbol, ShareSize)/1000))*1000;
// Debug("Number of Shares " + quantity);
return Math.Min(quantity,maxLotSize);
}
}
}namespace QuantConnect {
/// <summary>
/// Contains data pertaining to a symbol in our algorithm
/// </summary>
public class OrderData
{
/// <summary>
/// The Entry, Stop Loss and Target
/// </summary>
public decimal FirstEntry;
public decimal SecondEntry;
public decimal StopLoss;
public decimal Target;
/// <summary>
/// The Entry, Stop and Target order numbers
/// </summary>
public OrderTicket FirstEntryTicket = null;
public OrderTicket SecondEntryTicket = null;
public OrderTicket StopLossTicket = null;
public OrderTicket TargetTicket = null;
//Share Size - based on portfolio size. Used to Calculate LotSize
public decimal ShareSize;
/// <summary>
/// The lot size for the setup
/// </summary>
public int FirstLotSize;
public int SecondLotSize;
/// <summary>
/// Initializes a new instance of OrderData
/// </summary>
public OrderData(decimal shareSize)
{
ShareSize = shareSize;
}
public OrderData(decimal firstEntry, decimal secondEntry, decimal stopLoss, decimal target)
{
FirstEntry = firstEntry;
SecondEntry = secondEntry;
StopLoss = stopLoss;
Target = target;
}
}
}namespace QuantConnect {
/// <summary>
/// Contains data pertaining to a symbol in our algorithm
/// </summary>
public class SymbolData
{
/// This symbol the other data in this class is associated with
public readonly Symbol Symbol;
/// A rolling window of data, data needs to be pumped into Bars by using Bars.Update( tradeBar ) and
/// can be accessed like:
/// mySymbolData.Bars[0] - most first recent piece of data
/// mySymbolData.Bars[5] - the sixth most recent piece of data (zero based indexing)
public readonly RollingWindow<HeikinAshi> FourHrBars;
public readonly RollingWindow<HeikinAshi> HalfHrBars;
public readonly RollingWindow<RenkoBar> RenkoBars;
public readonly RollingWindow<decimal> EMAVals;
/// The period used when population the Bars rolling window.
public readonly TimeSpan FourHrPeriod;
public readonly TimeSpan HalfHrPeriod;
public readonly decimal RenkoBrick;
/// The indicators for our symbol
public ExponentialMovingAverage EMA;
public MovingAverageConvergenceDivergence MACD;
public HeikinAshi FourHrHA;
public HeikinAshi HalfHrHA;
// Order information
public OrderData Long;
public OrderData Short;
/// <summary>
/// Initializes a new instance of SymbolData
/// </summary>
public SymbolData(Symbol symbol, int windowSize1, int windowSize2, decimal renkoSize, int windowSize3)
{
Symbol = symbol;
RenkoBrick = renkoSize;
FourHrBars = new RollingWindow<HeikinAshi>(windowSize1);
HalfHrBars = new RollingWindow<HeikinAshi>(windowSize2);
EMAVals = new RollingWindow<decimal>(windowSize2);
RenkoBars = new RollingWindow<RenkoBar>(windowSize3);
}
/// <summary>
/// Returns true if all the data in this instance is ready (indicators, rolling windows, ect...)
/// </summary>
public bool FourHrIsReady
{
get { return FourHrBars.IsReady; }
}
/// <summary>
/// Returns true if all the data in this instance is ready (indicators, rolling windows, ect...)
/// </summary>
public bool HalfHrIsReady
{
get { return HalfHrBars.IsReady; }
}
/// <summary>
/// Returns true if all the data in this instance is ready (indicators, rolling windows, ect...)
/// </summary>
public bool RenkoIsReady
{
get { return RenkoBars.IsReady; }
}
}
}using System;
using System.Collections;
using System.Collections.Generic;
using QuantConnect.Securities;
using QuantConnect.Models;
namespace QuantConnect
{
/*
* TimeSpanConsolidator Helper Routine: Assemble generic timespan bar lengths: e.g. 10 minutes:
*
* 1. Setup the new Consolidator class with the timespan period:
* var _consolidator = new Consolidator(TimeSpan.FromMinutes(10));
*
* 2. Add in the data with the update routine. It will return true when bar ready
* if (_consolidator.Update(data["MSFT"])) { UseBar }
*/
public class Consolidator
{
public TradeBar resultBar;
private TradeBar workingBar;
private DateTime start;
private TimeSpan period;
//Result:
public TradeBar Bar
{
get
{
return resultBar;
}
}
//Constructor: Set the period we'd like to scan
public Consolidator(TimeSpan span)
{
this.period = span;
this.resultBar = new TradeBar();
this.workingBar = new TradeBar(new DateTime(), "", Decimal.Zero, Decimal.MinValue, Decimal.MaxValue, 0, 0);
}
//Submit this bar, return true if we've started a new one.
public bool Update(TradeBar newBar)
{
//Intialize:
if (start == new DateTime())
{
start = newBar.Time;
}
//While we're less than end date, keep adding to this bar:
if (newBar.Time < (start + period))
{
//Building bar:
AddToBar(newBar);
return false;
}
else
{
//Completed bar: start new one:
resultBar = workingBar;
//Create a new bar:
workingBar = new TradeBar(newBar.Time, newBar.Symbol, Decimal.Zero, Decimal.MinValue, Decimal.MaxValue, 0, 0);
//Start of this bar:
start = newBar.Time;
AddToBar(newBar);
return true;
}
}
//Add to a tradebar
private void AddToBar(TradeBar newBar)
{
//Add this data to working bar:
if (workingBar.Time == new DateTime()) workingBar.Time = newBar.Time;
if (workingBar.Symbol == "") workingBar.Symbol = newBar.Symbol;
if (workingBar.Open == Decimal.Zero) workingBar.Open = newBar.Open;
if (newBar.High > workingBar.High) workingBar.High = newBar.High;
if (newBar.Low < workingBar.Low) workingBar.Low = newBar.Low;
workingBar.Close = newBar.Close;
workingBar.Volume = newBar.Volume;
}
}
}