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);
            }
        }
    }
}