Overall Statistics |
Total Trades 528 Average Win 1.88% Average Loss -0.83% Compounding Annual Return 14.217% Drawdown 40.800% Expectancy 0.110 Net Profit 20.756% Sharpe Ratio 0.424 Probabilistic Sharpe Ratio 19.815% Loss Rate 66% Win Rate 34% Profit-Loss Ratio 2.26 Alpha 0.174 Beta -0.108 Annual Standard Deviation 0.392 Annual Variance 0.153 Information Ratio 0.216 Tracking Error 0.414 Treynor Ratio -1.535 Total Fees $2843.55 |
using System; using System.Collections.Generic; using System.Linq; using QuantConnect.Data; using QuantConnect.Data.Consolidators; using QuantConnect.Data.Market; using QuantConnect.Indicators; using QuantConnect.Orders; using QuantConnect.Securities; namespace QuantConnect.Algorithm.CSharp.My_Strategies.BubbleRider.MultiCurrency { public class AssetHandler { private readonly BubbleRider _bot; public decimal Equity { get; private set; } private readonly Position _position; private readonly ParabolicStopAndReverse _parabolic; private readonly AverageDirectionalIndex _avgDirectionalIndex; private readonly DonchianChannel _donchian; //Collections private readonly RollingWindow<decimal> _histParabolic; private readonly RollingWindow<decimal> _histAvgDirectionalIndex; private readonly RollingWindow<decimal> _histDonchian; private readonly RollingWindow<decimal> _histOpeningValues; private readonly RollingWindow<decimal> _histHighValues; private readonly RollingWindow<decimal> _histLowValues; private readonly RollingWindow<TradeBar> _historicalBar; private readonly List<DateTime> _dateOfClosedTrades = new List<DateTime>(); public AssetCharter Chart { get; } public Security Security { get; } public SymbolDescription SymbolDetails { get; } public decimal StopLossLevel => _histDonchian[0]; /// <summary> /// Constructor /// </summary> /// <param name="bot"></param> /// <param name="symbolDetails"></param> /// <param name="equity"></param> public AssetHandler(BubbleRider bot, SymbolDescription symbolDetails, decimal equity) { _bot = bot; Equity = equity; SymbolDetails = symbolDetails; Security = symbolDetails.Security; _bot.BrokerageModel.GetFeeModel(Security); _position = new Position(); if (_bot.Resolution == Resolution.Tick) { TickConsolidator fourHourConsolidator = new TickConsolidator(_bot.BarTimeSpan); fourHourConsolidator.DataConsolidated += ConsolidatedDataHandler; _bot.SubscriptionManager.AddConsolidator(Security.Symbol, fourHourConsolidator); _parabolic = new ParabolicStopAndReverse(_bot.AfStart, _bot.AfIncrement, _bot.AfMax); _avgDirectionalIndex = new AverageDirectionalIndex(symbolDetails.SymbolName, _bot.AdxPeriodLevel); _donchian = new DonchianChannel("Donchian", _bot.DonchianPeriods); _bot.RegisterIndicator(symbolDetails.Security.Symbol, _parabolic, fourHourConsolidator); _bot.RegisterIndicator(symbolDetails.Security.Symbol, _avgDirectionalIndex, fourHourConsolidator); _bot.RegisterIndicator(symbolDetails.Security.Symbol, _donchian, fourHourConsolidator); } else { TradeBarConsolidator fourHourConsolidator = new TradeBarConsolidator(_bot.BarTimeSpan); fourHourConsolidator.DataConsolidated += ConsolidatedDataHandler; _bot.SubscriptionManager.AddConsolidator(Security.Symbol, fourHourConsolidator); _parabolic = new ParabolicStopAndReverse(_bot.AfStart, _bot.AfIncrement, _bot.AfMax); _avgDirectionalIndex = new AverageDirectionalIndex(symbolDetails.SymbolName, _bot.AdxPeriodLevel); _donchian = new DonchianChannel("Donchian", _bot.DonchianPeriods); _bot.RegisterIndicator(symbolDetails.Security.Symbol, _parabolic, fourHourConsolidator); _bot.RegisterIndicator(symbolDetails.Security.Symbol, _avgDirectionalIndex, fourHourConsolidator); _bot.RegisterIndicator(symbolDetails.Security.Symbol, _donchian, fourHourConsolidator); } //Create History for Indicator PSAR _histParabolic = new RollingWindow<decimal>(_bot.HistoryBars); _histAvgDirectionalIndex = new RollingWindow<decimal>(_bot.HistoryBars); _histDonchian = new RollingWindow<decimal>(_bot.HistoryBars); //Create History For OHLC Prices _historicalBar = new RollingWindow<TradeBar>(_bot.HistoryBars); _histOpeningValues = new RollingWindow<decimal>(_bot.HistoryBars); _histHighValues = new RollingWindow<decimal>(_bot.HistoryBars); _histLowValues = new RollingWindow<decimal>(_bot.HistoryBars); Chart = new AssetCharter(_bot, SymbolDetails.SymbolName); } public void OrderEvent(OrderEvent orderEvent) { if (!orderEvent.Symbol.ToString().Equals( SymbolDetails.SymbolName, StringComparison.InvariantCultureIgnoreCase )) return; if (orderEvent.Status != OrderStatus.Filled) return; if (orderEvent.Direction == OrderDirection.Sell) { _dateOfClosedTrades.Add(_bot.Time); _position.ClosePrice = orderEvent.FillPrice; _position.CloseTime = orderEvent.UtcTime; _position.Fees += orderEvent.OrderFee; Equity += (_position.ClosePrice - _position.OpenPrice) * _position.Quantity - orderEvent.OrderFee; //Console.WriteLine($"Closed on {orderEvent.Symbol} Equity {Equity} O: {_position.OpenPrice} C: {_position.ClosePrice} F: {orderEvent.OrderFee}"); } else { _position.Id = orderEvent.OrderId; _position.OpenTime = orderEvent.UtcTime; _position.OpenPrice = orderEvent.FillPrice; _position.Quantity = orderEvent.FillQuantity; _position.Fees = orderEvent.OrderFee; Equity -= orderEvent.OrderFee; //Console.WriteLine($"Open on {orderEvent.Symbol} Equity {Equity} O: {_position.OpenPrice} C: {_position.ClosePrice} F: {orderEvent.OrderFee}"); } if (orderEvent.FillPrice > 0) Chart.PlotTrade(orderEvent.Direction, orderEvent.FillPrice); } public void WarmupFinished() { //Must delete the data I filled from SetWarmUp _parabolic.Reset(); _avgDirectionalIndex.Reset(); _donchian.Reset(); if (!_bot.LiveMode) { return; } IEnumerable<TradeBar> history = _bot.History(Security.Symbol, TimeSpan.FromHours(_bot.BarTimeSpan.Hours * 200), Resolution.Minute ); IEnumerable<TradeBar> customTradeBarHistory = ConsolidateHistory( history, TimeSpan.FromHours(_bot.BarTimeSpan.Hours), Resolution.Minute ); IEnumerable<TradeBar> tradeBarHistory = customTradeBarHistory as TradeBar[] ?? customTradeBarHistory.ToArray(); foreach (TradeBar tradeBar in tradeBarHistory) { _parabolic.Update(tradeBar); _avgDirectionalIndex.Update(tradeBar); _donchian.Update(tradeBar); _historicalBar.Add(tradeBar); _histOpeningValues.Add(tradeBar.Open); _histHighValues.Add(tradeBar.High); _histLowValues.Add(tradeBar.Low); _histParabolic.Add(_parabolic.Current.Price); _histAvgDirectionalIndex.Add(_avgDirectionalIndex.Current.Price); _histDonchian.Add(_donchian.LowerBand.Current.Price); } if (_bot.LiveMode && tradeBarHistory.Last().Close > _parabolic.Current.Value && tradeBarHistory.Last().Close > _donchian.LowerBand.Current.Value && _avgDirectionalIndex.Current.Value > _bot.AdxFilterLevel) { if (_bot.Mode == TradingMode.PercentageOfFunds) _bot.PositionManager.OpenPosition(SymbolDetails); else _bot.PositionManager.OpenPosition(SymbolDetails, (Equity / tradeBarHistory.Last().Close) * 0.97m); } } /// <summary> /// Handles Four Hour (H4) bar events, everytime a new H4 bar is formed /// this method is called. /// </summary> /// <param name="sender"></param> /// <param name="bar"></param> private void ConsolidatedDataHandler(object sender, TradeBar bar) { if (!Security.IsTradable) return; if (!Security.Exchange.ExchangeOpen) { //MyLogger.ErrorExchangeClosed(); return; } if (!_parabolic.IsReady || !_avgDirectionalIndex.IsReady || !_donchian.IsReady || _bot.IsWarmingUp) { return; } _historicalBar.Add(bar); _histOpeningValues.Add(bar.Open); _histHighValues.Add(bar.High); _histLowValues.Add(bar.Low); _histParabolic.Add(_parabolic.Current.Price); _histAvgDirectionalIndex.Add(_avgDirectionalIndex.Current.Value); _histDonchian.Add(_donchian.LowerBand.Current.Value); Chart.PlotLevels(_historicalBar[0].Price, _histParabolic[0], _histDonchian[0]); if (_bot.Portfolio[SymbolDetails.SymbolName].Invested) return; //Open a position when the criteria meet: // - no previous trade within this signal // - price > PSAR // - price > ADX if (DateOfLastSignal > DateOfLastLongEntry && _historicalBar[0].Price > _histParabolic[0] && _historicalBar[0].Price > _histDonchian[0] && _histAvgDirectionalIndex[0] > _bot.AdxFilterLevel) { if (_bot.Mode == TradingMode.PercentageOfFunds) _bot.PositionManager.OpenPosition(SymbolDetails); else _bot.PositionManager.OpenPosition(SymbolDetails, (Equity / Security.BidPrice) * 0.97m); } } /// <summary> /// Transforms history from a frequency to another lower frequency, /// example: m1 to H4 /// </summary> /// <param name="history">History to transform</param> /// <param name="customTime">New bar time length for the new history</param> /// <param name="resolution">Original resolution of the current algorithm</param> /// <returns></returns> public IEnumerable<TradeBar> ConsolidateHistory( IEnumerable<TradeBar> history, TimeSpan customTime, Resolution resolution ) { if (resolution == Resolution.Minute) { TimeSpan minute = TimeSpan.FromMinutes(1); int totalMinutes = (int) (minute.TotalMinutes * history.Count()); int customTimeMinutes = (int) (customTime.TotalMinutes); int span = totalMinutes / customTimeMinutes; TradeBar[] consolidatedHistory = new TradeBar[span]; Symbol symbol = history.First().Symbol; for (int i = 0; i < span; i++) { decimal open = history.Skip(i * customTimeMinutes). Take(customTimeMinutes).First().Open; decimal high = history.Skip(i * customTimeMinutes). Take(customTimeMinutes).Max(item => item.High); decimal low = history.Skip(i * customTimeMinutes). Take(customTimeMinutes).Min(item => item.Low); decimal close = history.Skip(i * customTimeMinutes). Take(customTimeMinutes).Last().Close; DateTime time = history.Skip(i * customTimeMinutes). Take(customTimeMinutes).First().Time; decimal volume = history.Skip(i * customTimeMinutes). Take(customTimeMinutes).Sum(item => item.Volume); consolidatedHistory[i] = new TradeBar(time, symbol, open, high, low, close, volume, customTime); _historicalBar.Add(consolidatedHistory[i]); _histOpeningValues.Add(consolidatedHistory[i].Open); _histHighValues.Add(consolidatedHistory[i].High); _histLowValues.Add(consolidatedHistory[i].Low); } return consolidatedHistory; } else { //MyLogger.FatalHistoryConsolidator(); throw new Exception("History Consolidator was given a wrong resolution parameter."); } } public DateTime DateOfLastSignal { get { int i = 0; int maxBars = _historicalBar.Count; if (_historicalBar[i].Price <= _histParabolic[i]) return _historicalBar[i].Time; while (_historicalBar[i].Price > _histParabolic[i] && i < maxBars - 1) { i++; } return _historicalBar[i].Time; } } //Retrieves the start date of the last closed trade public DateTime DateOfLastLongEntry { get { if (!_dateOfClosedTrades.Any()) { return DateTime.MinValue; } else { return _dateOfClosedTrades.Max(); } } } /// <summary> /// Prints to Console the current holdings in the cashbook /// </summary> public void PrintCurrentHoldings() { //var holdings = Enumerable.Range(0, _bot.Assets.Count).Select(x => $"Symbol {_bot.Assets[x].SymbolDetails.SymbolName} {Math.Round(_bot.Portfolio[_bot.Assets[x].SymbolDetails.SymbolName].Quantity, 2)}").ToArray(); var cashBookHoldings = Enumerable.Range(0, _bot.Assets.Count).Select(x => $"Currency {_bot.Assets[x].SymbolDetails.BaseCurrency} {Math.Round(_bot.Portfolio.CashBook[_bot.Assets[x].SymbolDetails.BaseCurrency].Amount, 5)}").ToList(); cashBookHoldings.Add($"Currency USD {Math.Round(_bot.Portfolio.CashBook["USD"].Amount, 5)}"); Console.WriteLine(string.Join("-", cashBookHoldings.ToArray())); } /// <summary> /// Returns PnL of last closed trades for this symbol /// </summary> public decimal ClosedPnL => _bot.MyTradeBuilder.ClosedTrades .Where(x => x.Symbol.Equals(SymbolDetails.SymbolName)) .Select(x => x.ProfitLoss) .Sum(); /// <summary> /// Gives the returns in terms of percentage /// </summary> public decimal ClosedPercentReturn { get { decimal percentageReturn = 1m; foreach (var d in _bot.TradeBuilder.ClosedTrades.Where(x => x.Symbol == SymbolDetails.SymbolName)) { percentageReturn *= (d.ExitPrice / d.EntryPrice); //Console.WriteLine($"Symbol {SymbolDetails.SymbolName} PR {percentageReturn} Entry {d.EntryPrice} Exit {d.ExitPrice}"); } return Math.Round(percentageReturn * 100m - 100, 2); } } } }
using System.Collections.Generic; using System.Linq; using QuantConnect.Orders; namespace QuantConnect.Algorithm.CSharp.My_Strategies.BubbleRider.MultiCurrency { public class PositionManager { private readonly BubbleRider _bot; private readonly int _assetsCount; public decimal StopPrice; public PositionManager(BubbleRider bot, int assetsCount) { _bot = bot; _assetsCount = assetsCount; } public void OpenPosition(SymbolDescription symbol) { string tag = _bot.TagTrade; _bot.SetHoldings(symbol.Security.Symbol, ((1m / _assetsCount) * 0.97m), false, tag); } public void OpenPosition(SymbolDescription symbol, decimal quantity) { string tag = _bot.TagTrade; _bot.MarketOrder(symbol.Security.Symbol, quantity, false, tag); } /// <summary> /// Closes a Long Market Order /// </summary> public void ClosePosition(SymbolDescription symbol) { string tag = _bot.TagTrade + "Close"; _bot.Liquidate(symbol.Security.Symbol, tag); } } }
using QuantConnect.Securities; namespace QuantConnect.Algorithm.CSharp.My_Strategies.BubbleRider.MultiCurrency { public class SymbolDescription { public Security Security { get; } public string BaseCurrency { get; } public string QuoteCurrency { get; } public string SymbolName => Security.Symbol.Value; public SymbolDescription(Security security, string baseCurrency, string quoteCurrency) { Security = security; BaseCurrency = baseCurrency; QuoteCurrency = quoteCurrency; } } }
using System.Drawing; using QuantConnect.Orders; namespace QuantConnect.Algorithm.CSharp.My_Strategies.BubbleRider.MultiCurrency { public class AssetCharter { private readonly BubbleRider _bot; private readonly string _assetName; //Plotting Names private const string PriceSeriesName = "Price"; private const string BuySeriesName = "Buy"; private const string SellSeriesName = "Sell"; private const string ParabolicSeriesName = "PSAR"; private const string DonchianSeriesName = "Donchian"; private const string Unit = "$"; //Plotting Colors private readonly Color _priceColor = Color.Gray; private readonly Color _buyOrdersColor = Color.CornflowerBlue; private readonly Color _sellOrdersColor = Color.Red; private readonly Color _parabolicColor = Color.RosyBrown; private readonly Color _donchianColor = Color.MediumPurple; public AssetCharter(BubbleRider bot, string assetName) { _bot = bot; _assetName = assetName; Chart parabolicPlot = new Chart(assetName); parabolicPlot.AddSeries(new Series(PriceSeriesName, SeriesType.Line, Unit, _priceColor)); parabolicPlot.AddSeries(new Series(BuySeriesName, SeriesType.Scatter, Unit, _buyOrdersColor)); parabolicPlot.AddSeries(new Series(SellSeriesName, SeriesType.Scatter, Unit, _sellOrdersColor)); parabolicPlot.AddSeries(new Series(ParabolicSeriesName, SeriesType.Line, Unit, _parabolicColor)); parabolicPlot.AddSeries(new Series(DonchianSeriesName, SeriesType.Line, Unit, _donchianColor)); _bot.AddChart(parabolicPlot); } /// <summary> /// Plots Asset Price and Indicator values (Except ADX) /// </summary> /// <param name="price"></param> /// <param name="parabolic"></param> /// <param name="donchian"></param> public void PlotLevels(decimal price, decimal parabolic, decimal donchian) { _bot.Plot(_assetName, PriceSeriesName, price); _bot.Plot(_assetName, DonchianSeriesName, donchian); _bot.Plot(_assetName, ParabolicSeriesName, parabolic); } /// <summary> /// Plots Prices at which trades are made /// </summary> /// <param name="direction"></param> /// <param name="price"></param> public void PlotTrade(OrderDirection direction, decimal price) { _bot.Plot(_assetName, direction == OrderDirection.Buy ? BuySeriesName : SellSeriesName, price); } } }
using System; namespace QuantConnect.Algorithm.CSharp.My_Strategies.BubbleRider.MultiCurrency { public class Position { public int Id { get; set; } public decimal OpenPrice { get; set; } public decimal ClosePrice { get; set; } public decimal Quantity { get; set; } public DateTime OpenTime { get; set; } public DateTime CloseTime { get; set; } public decimal Fees { get; set; } } }
namespace QuantConnect.Algorithm.CSharp.My_Strategies.BubbleRider.MultiCurrency { public enum TradingMode { AllocatedQuantity, PercentageOfFunds } }
using QuantConnect.Brokerages; using QuantConnect.Data; using QuantConnect.Data.Consolidators; using QuantConnect.Data.Market; using QuantConnect.Indicators; using QuantConnect.Orders; using QuantConnect.Securities; using QuantConnect.Statistics; using System; using System.Collections.Generic; using System.Linq; using System.Globalization; namespace QuantConnect.Algorithm.CSharp.My_Strategies.BubbleRider.MultiCurrency { public class BubbleRider : QCAlgorithm { //Parameters public readonly decimal AdxFilterLevel = 0; public readonly int AdxPeriodLevel = 25; public readonly int DonchianPeriods = 15; public readonly decimal AfStart = 0.017m; public readonly decimal AfIncrement = 0.01m; public readonly decimal AfMax = 0.2m; public readonly int HistoryBars = 100; public TimeSpan BarTimeSpan => TimeSpan.FromHours(4); public readonly Resolution Resolution = Resolution.Minute; public readonly TradingMode Mode = TradingMode.AllocatedQuantity; //Others public List<AssetHandler> Assets = new List<AssetHandler>(); public PositionManager PositionManager; public TradeBuilder MyTradeBuilder; /// <summary> /// returns a string Epoch Tag for an Order /// </summary> public string TagTrade => ( Time.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) ).TotalSeconds.ToString(CultureInfo.InvariantCulture); /// <summary> /// Initialise the data and resolution required, as well as the cash and /// start-end dates for your algorithm. All algorithms must initialized. /// </summary> public override void Initialize() { SetStartDate(2018, 06, 01); SetEndDate(2019, 10, 31); SetCash(10000); SetBrokerageModel(BrokerageName.GDAX, AccountType.Cash); //Must add Assets here var assets = new Dictionary<string, string>() { {"BTC", "USD"}, {"ETH", "USD"}, {"BCH", "USD"}, {"XRP", "USD"}, {"LTC", "USD"} }; foreach (var asset in assets) { var security = AddCrypto(asset.Key + asset.Value, Resolution); var symbolDescription = new SymbolDescription(security, asset.Key, asset.Value); Assets.Add(new AssetHandler(this, symbolDescription, Portfolio.Cash / assets.Count)); } PositionManager = new PositionManager(this, assets.Count); //Must use SetWarmUp to 1 at least SetWarmup(1); MyTradeBuilder = new TradeBuilder(FillGroupingMethod.FlatToFlat, FillMatchingMethod.FIFO); SetTradeBuilder(MyTradeBuilder); } /// <summary> /// Called when the algorithm finishes warming up data /// (with SetWarmUp() method) /// </summary> public override void OnWarmupFinished() { foreach (var asset in Assets) { asset.WarmupFinished(); } } public override void OnData(Slice slice) { foreach (var asset in Assets) { if (!Portfolio[asset.Security.Symbol].Invested) continue; if (slice[asset.Security.Symbol].Price <= asset.StopLossLevel) PositionManager.ClosePosition(asset.SymbolDetails); } } /// <summary> /// Event handler for when the algorithm ends. /// </summary> public override void OnEndOfAlgorithm() { Liquidate(); foreach (var asset in Assets) { Console.WriteLine($"Symbol {asset.SymbolDetails.SymbolName} Equity {asset.Equity} PnL {asset.ClosedPnL} Returns: {asset.ClosedPercentReturn} %"); } } // Override the base class event handler for order events public override void OnOrderEvent(OrderEvent orderEvent) { foreach (var asset in Assets) { asset.OrderEvent(orderEvent); } } } }