Overall Statistics
Total Trades
82
Average Win
9.90%
Average Loss
-3.78%
Compounding Annual Return
57.178%
Drawdown
36.400%
Expectancy
0.412
Net Profit
57.160%
Sharpe Ratio
0.87
Probabilistic Sharpe Ratio
43.581%
Loss Rate
61%
Win Rate
39%
Profit-Loss Ratio
2.62
Alpha
0.433
Beta
-0.015
Annual Standard Deviation
0.494
Annual Variance
0.244
Information Ratio
0.471
Tracking Error
0.505
Treynor Ratio
-29.339
Total Fees
$3415.83
using System;
using System.Collections;
using System.Collections.Generic;
using QuantConnect.Securities;
using QuantConnect.Models;

namespace QuantConnect.Algorithm.CSharp.My_Strategies.BubbleRider
{
    public class Bitcoin : TradeBar
    {
        //Set the defaults:
        public decimal VolumeBTC = 0;
        //public decimal VolumeUSD = 0;
        //public decimal WeightedPrice = 0;

        /// <summary>
        /// 1. DEFAULT CONSTRUCTOR: Custom data types need a default constructor.
        /// We search for a default constructor so please provide one here. It won't be used for data, just to generate the "Factory".
        /// </summary>
        public Bitcoin()
        {
            this.Symbol = "BTCUSD";
            // this is the missing secret sauce
            // tradebar sets this to TradeBar which causes the data to get piped elsewhere
            this.DataType = MarketDataType.Base;
        }

        /// <summary>
        /// 2. RETURN THE STRING URL SOURCE LOCATION FOR YOUR DATA:
        /// This is a powerful and dynamic select source file method. If you have a large dataset, 10+mb we recommend you break it into smaller files. E.g. One zip per year.
        /// We can accept raw text or ZIP files. We read the file extension to determine if it is a zip file.
        /// </summary>
        /// <param name="config">Subscription data, symbol name, data type</param>
        /// <param name="date">Current date we're requesting. This allows you to break up the data source into daily files.</param>
        /// <param name="datafeed">Datafeed type: Backtesting or the Live data broker who will provide live data. You can specify a different source for live trading! </param>
        /// <returns>string URL end point.</returns>
        public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
        {
        	if (isLiveMode)
        	{
        		throw new NotImplementedException("Define a REST endpoint for live data.");
                //return new SubscriptionDataSource("https://www.bitstamp.net/api/ticker/", SubscriptionTransportMedium.Rest);
        	}
        	
        	return new SubscriptionDataSource("https://s3.us-east-2.amazonaws.com/fulldata.bitstampedited/bitstamp2018_edited.csv", SubscriptionTransportMedium.RemoteFile);
        }

        /// <summary>
        /// 3. READER METHOD: Read 1 line from data source and convert it into Object.
        /// Each line of the CSV File is presented in here. The backend downloads your file, loads it into memory and then line by line
        /// feeds it into your algorithm
        /// </summary>
        /// <param name="line">string line from the data source file submitted above</param>
        /// <param name="config">Subscription data, symbol name, data type</param>
        /// <param name="date">Current date we're requesting. This allows you to break up the data source into daily files.</param>
        /// <param name="datafeed">Datafeed type - Backtesting or LiveTrading</param>
        /// <returns>New Bitcoin Object which extends BaseData.</returns>
        public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
        {
            //New Bitcoin object
            Bitcoin coin = new Bitcoin();

            try
            {
                string[] data = line.Split(',');
                coin.Time = DateTime.Parse(data[0]+" "+data[1], CultureInfo.InvariantCulture);
                coin.Open = Convert.ToDecimal(data[2], CultureInfo.InvariantCulture);
                coin.High = Convert.ToDecimal(data[3], CultureInfo.InvariantCulture);
                coin.Low = Convert.ToDecimal(data[4], CultureInfo.InvariantCulture);
                coin.Close = Convert.ToDecimal(data[5], CultureInfo.InvariantCulture);
                coin.VolumeBTC = Convert.ToDecimal(data[6], CultureInfo.InvariantCulture);
                coin.Symbol = config.Symbol;
                coin.Value = coin.Close;
            }
            catch { /* Do nothing, skip first title row */ }

            return coin;
        }
    }
}
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.Drawing;

namespace QuantConnect.Algorithm.CSharp.My_Strategies.BubbleRider
{
    public partial class BubbleRider : QCAlgorithm
    {
        //Parameters
        private const bool UseCustomData = false;
        private const bool UseBrokerageModel = true;
        private const bool DebugEnabled = false;
        private const bool UseOptimization = false;
        private const string BaseCurrency = "ETH";
        private const string QuoteCurrency = "USD";
        private const decimal _adxFilterLevel = 0;
        private const int AdxPeriodLevel = 25;
        private const int DonchianPeriods = 15;
        private const decimal AfStart = 0.017m;
        private const decimal AfIncrement = 0.01m;
        private const decimal AfMax = 0.2m;
        private const int HistoryBars = 100;
        private readonly TimeSpan _barTimeSpan = TimeSpan.FromHours(4);
        private readonly Resolution _resolution = Resolution.Tick;
        private PortfolioStatistics p;

        //Plotting Names
        private const string PriceAndIndicatorsName = "Price + Indicators";
        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 AdxChartName = "Plot ADX";
        private const string AdxSeriesName = "ADX";
        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;
        private readonly Color AdxColor = Color.CornflowerBlue;

        //Indicators
        private ParabolicStopAndReverse _parabolic;
        private AverageDirectionalIndex _avgDirectionalIndex;
        private DonchianChannel _donchian;

        //Collections
        private RollingWindow<decimal> _histParabolic;
        private RollingWindow<decimal> _histAvgDirectionalIndex;
        private RollingWindow<decimal> _histDonchian;
        private RollingWindow<decimal> _histOpeningValues;
        private RollingWindow<decimal> _histHighValues;
        private RollingWindow<decimal> _histLowValues;
        private RollingWindow<TradeBar> _historicalBar;
        private readonly List<DateTime> _dateOfClosedTrades = new List<DateTime>();

        //Others
        public Logger MyLogger;
        private Security _mySecurity;
        public TradeBuilder CustomTradeBuilder;

        public readonly string MySymbol  = BaseCurrency + QuoteCurrency;

        /// <summary>
        /// Retrieves Quote Currency Balance (rounded)
        /// </summary>
        public decimal QuoteCurrencyBalance
        {
            get
            {
                return Math.Round(Portfolio.CashBook[QuoteCurrency].Amount, 2);
            }
        }

        /// <summary>
        /// Retrieves Start Balance
        /// </summary>
        public decimal StartBalance { get; private set; }

        /// <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()
        {
            if (UseCustomData)
            {
                _mySecurity = AddData<Bitcoin>(MySymbol, _resolution);
                SetStartDate(2014, 01, 15);
                SetEndDate(2017, 12, 31);
                SetCash(10000);
            }
            else
            {
                SetStartDate(2019, 01, 01);
                SetEndDate(2019, 12, 31);
                SetCash(10000);

                if (UseBrokerageModel)
                {
                    SetBrokerageModel(BrokerageName.GDAX, AccountType.Cash);
                    _mySecurity = AddCrypto(MySymbol, _resolution);
                    BrokerageModel.GetFeeModel(_mySecurity);
                }
                else
                {
                    _mySecurity = AddCrypto(MySymbol, _resolution);
                }
            }

            StartBalance = Portfolio.Cash;
            MyLogger = new Logger(this, DebugEnabled);
            p = new PortfolioStatistics();

            if (_resolution == Resolution.Tick)
            {
                TickConsolidator fourHourConsolidator = new TickConsolidator(_barTimeSpan);
                fourHourConsolidator.DataConsolidated += ConsolidatedDataHandler;
                SubscriptionManager.AddConsolidator(_mySecurity.Symbol, fourHourConsolidator);

                _parabolic = new ParabolicStopAndReverse(AfStart, AfIncrement, AfMax);
                _avgDirectionalIndex = new AverageDirectionalIndex(MySymbol, AdxPeriodLevel);
                _donchian = new DonchianChannel("Donchian", DonchianPeriods);

                RegisterIndicator(MySymbol, _parabolic, fourHourConsolidator);
                RegisterIndicator(MySymbol, _avgDirectionalIndex, fourHourConsolidator);
                RegisterIndicator(MySymbol, _donchian, fourHourConsolidator);
            }
            else
            {
                TradeBarConsolidator fourHourConsolidator = new TradeBarConsolidator(_barTimeSpan);
                fourHourConsolidator.DataConsolidated += ConsolidatedDataHandler;
                SubscriptionManager.AddConsolidator(_mySecurity.Symbol, fourHourConsolidator);

                _parabolic = new ParabolicStopAndReverse(AfStart, AfIncrement, AfMax);
                _avgDirectionalIndex = new AverageDirectionalIndex(MySymbol, AdxPeriodLevel);
                _donchian = new DonchianChannel("Donchian", DonchianPeriods);

                RegisterIndicator(MySymbol, _parabolic, fourHourConsolidator);
                RegisterIndicator(MySymbol, _avgDirectionalIndex, fourHourConsolidator);
                RegisterIndicator(MySymbol, _donchian, fourHourConsolidator);
            }

            //Create History for Indicator PSAR
            _histParabolic = new RollingWindow<decimal>(HistoryBars);
            _histAvgDirectionalIndex = new RollingWindow<decimal>(HistoryBars);
            _histDonchian = new RollingWindow<decimal>(HistoryBars);

            //Create History For OHLC Prices
            _historicalBar = new RollingWindow<TradeBar>(HistoryBars);
            _histOpeningValues = new RollingWindow<decimal>(HistoryBars);
            _histHighValues = new RollingWindow<decimal>(HistoryBars);
            _histLowValues = new RollingWindow<decimal>(HistoryBars);

            //Must use SetWarmUp to 1 at least
            SetWarmup(1);
            //--Charting and Log
            Chart parabolicPlot = new Chart(PriceAndIndicatorsName);
            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));

            Chart plotAvgDirectionalIndex = new Chart(AdxChartName);
            plotAvgDirectionalIndex.AddSeries(new Series(AdxSeriesName, SeriesType.Line, Unit, AdxColor));
            AddChart(parabolicPlot);
            AddChart(plotAvgDirectionalIndex);

            //--For Logging
            MyLogger.ScheduleReport(MySymbol, BaseCurrency, QuoteCurrency);
            MyLogger.InfoSystem();
            MyLogger.InfoSettings($"SETTINGS: " +
                                  $"Use Custom Data: {UseCustomData} | " +
                                  $"Use Brokerage Model: {UseBrokerageModel} | " +
                                  $"Debug: {DebugEnabled} | " +
                                  $"ADX Filter Level: {_adxFilterLevel} | " +
                                  $"ADX Period Level: {AdxPeriodLevel} | " +
                                  $"Symbol: {MySymbol} | " +
                                  $"History Bars: {HistoryBars} | " +
                                  $"Resolution: {_resolution}");


            MyLogger.InfoCustom("Initialize Finished.");
            CustomTradeBuilder = new TradeBuilder(FillGroupingMethod.FlatToFlat, FillMatchingMethod.FIFO);
            SetTradeBuilder(CustomTradeBuilder);
        }

        /// <summary>
        /// Called when the algorithm finishes warming up data
        /// (with SetWarmUp() method)
        /// </summary>
        public override void OnWarmupFinished()
        {
            //Must delete the data I filled from SetWarmUp
            _parabolic.Reset();
            _avgDirectionalIndex.Reset();
            _donchian.Reset();

            if (!LiveMode)
            {
                return;
            }

            IEnumerable<TradeBar> history = History(_mySecurity.Symbol,
                TimeSpan.FromHours(_barTimeSpan.Hours * 200), Resolution.Minute
            );

            IEnumerable<TradeBar> customTradeBarHistory = ConsolidateHistory(
                history,
                TimeSpan.FromHours(_barTimeSpan.Hours),
                Resolution.Minute
            );

            foreach (TradeBar tradeBar in customTradeBarHistory)
            {
                _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);

                MyLogger.InfoBar(tradeBar, true);
                MyLogger.InfoIndicator(tradeBar, _parabolic, _avgDirectionalIndex, _donchian, true);
            }

            if (LiveMode
                && customTradeBarHistory.Last().Close > _parabolic.Current.Value
                && customTradeBarHistory.Last().Close > _donchian.LowerBand.Current.Value
                && _avgDirectionalIndex.Current.Value > _adxFilterLevel)
            {
                OpenPosition();
            }

            MyLogger.InfoCustom("OnWarmupFinished is complete.");
            base.OnWarmupFinished();
        }

        /// <summary>
        /// Event handler for everytime new custom data is processed (UseCustomData == true)
        /// </summary>
        /// <param name="data"></param>
        public void OnData(Bitcoin data)
        {

        }

        public void OnData(Slice slice)
        {
            if (!Portfolio.Invested)
            {
                if (_historicalBar.Count == 0 ||
                    _histParabolic.Count == 0 ||
                    _histAvgDirectionalIndex.Count == 0 ||
                    _histDonchian.Count == 0)
                    return;

                //Open a position when the criteria meet:
                // - no previous trade within this signal
                // - price > PSAR
                // - price > ADX
                if (DateOfLastSignal > DateOfLastLongEntry
                    && slice.Values[0].Price > _histParabolic[0]
                    && slice.Values[0].Price > _histDonchian[0]
                    && _histAvgDirectionalIndex[0] > _adxFilterLevel)
                {
                    OpenPosition();
                }
            }
            else
            {
                if (slice.Values[0].Price <= _histDonchian[0])
                    ClosePosition();
            }
        }

        /// <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 (!_mySecurity.IsTradable)
            {
                MyLogger.ErrorSymbolNotTradable(MySymbol);
                return;
            }
            if (!_mySecurity.Exchange.ExchangeOpen)
            {
                MyLogger.ErrorExchangeClosed();
                return;
            }
            if (!_parabolic.IsReady || !_avgDirectionalIndex.IsReady || !_donchian.IsReady || 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);

            Plot(PriceAndIndicatorsName, PriceSeriesName, _historicalBar[0].Price);
            Plot(PriceAndIndicatorsName, ParabolicSeriesName, _histParabolic[0]);
            Plot(PriceAndIndicatorsName, DonchianSeriesName, _histDonchian[0]);
            Plot(AdxChartName, AdxSeriesName, _histAvgDirectionalIndex[0]);

            MyLogger.InfoBar(bar);
            MyLogger.InfoIndicator(bar, _parabolic, _avgDirectionalIndex, _donchian);

            if (Portfolio.Invested && _histDonchian[0] > _histDonchian[1])
            {
                UpdatePosition();
            }
        }

        /// <summary>
        /// Event handler for when the algorithm ends.
        /// </summary>
        public override void OnEndOfAlgorithm()
        {
            Liquidate(MySymbol);

            decimal peak = 10000m;
            decimal balance = peak;
            decimal valley = 0m;
            decimal balanceDrawdown = 0m;

            foreach (var d in CustomTradeBuilder.ClosedTrades)
            {
                balance += d.ProfitLoss - d.TotalFees;

                if (balance > peak)
                {
                    peak = balance;
                    valley = peak;
                }
                else
                {
                    valley = balance;
                }

                if ((peak - valley) / peak > balanceDrawdown)
                    balanceDrawdown = (peak - valley) / peak;
            }

            Console.WriteLine($"Balance Drawdown % (From Risk Framework): {balanceDrawdown * 100}%");

            if (!UseOptimization)
                return;

            TradeStatistics t = new TradeStatistics(CustomTradeBuilder.ClosedTrades);

            var newValue = 10000m + t.TotalProfitLoss - t.TotalFees;
            var originalValue = 10000m;
            var percentIncrease = Math.Round(((newValue - originalValue) / originalValue) * 100, 2);
            //Percent increase = [(new value - original value)/original value] * 100
            Console.WriteLine($"{percentIncrease}," + //return
                              $"{t.TotalNumberOfTrades * 2}," +
                              $"{0}," + //Relative Drawdown
                              $"{Math.Round(balanceDrawdown * 100, 2)}," +
                              $"{p.Expectancy}," + //expectancy
                              $"{0}," +
                              $"{Math.Round(t.WinRate * 100, 2)}," +
                              $"0," +
                              $"{Math.Round(t.LossRate * 100, 2)}," +
                              $"0," + //PnL Ratio
                              $"0," + //Sharpe Ratio
                              $"{p.Alpha}," + //alpha
                              $"{p.Beta}," + //beta
                              $"{p.InformationRatio}," + //information ratio
                              $"{p.TreynorRatio}"); //treynor ratio
        }

        // Override the base class event handler for order events
        public override void OnOrderEvent(OrderEvent orderEvent)
        {
            if (orderEvent.Status == OrderStatus.Invalid)
            {
                MyLogger.ErrorOnOrderEvent(orderEvent);
            }

            if (orderEvent.Direction == OrderDirection.Sell)
            {
                _dateOfClosedTrades.Add(Time);
            }

            if (orderEvent.FillPrice > 0)
            {
                if (orderEvent.Direction == OrderDirection.Sell)
                {
                    Plot(PriceAndIndicatorsName, SellSeriesName, orderEvent.FillPrice);
                }
                else
                {
                    Plot(PriceAndIndicatorsName, BuySeriesName, orderEvent.FillPrice);
                }
            }

            MyLogger.InfoOrderEvent(orderEvent, QuoteCurrencyBalance, QuoteCurrency);
        }

        /// <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.");
            }
        }

        //Retrieves the start-date of the current bull signal
        public DateTime DateOfLastSignal
        {
            get
            {
                int i = 0;
                int maxBars = _historicalBar.Count;

                if (_historicalBar[i].Price > _histParabolic[i])
                {
                    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.Count() == 0)
                {
                    return DateTime.MinValue;
                }
                else
                {
                    return _dateOfClosedTrades.Max();
                }
            }
        }

        /// <summary>
        /// returns a string Epoch Tag for an Order
        /// </summary>
        public string TagTrade
        {
            get
            {
                return (
                    Time.ToUniversalTime()
                    - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)
                ).TotalSeconds.ToString();
            }
        }
    }
}
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
using QuantConnect.Orders;
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Statistics;

namespace QuantConnect.Algorithm.CSharp.My_Strategies.BubbleRider
{
    public class Logger
    {
        private BubbleRider QcAlgorithm;
        private bool DebugEnabled;
        private const string TimeFormat = "MM-dd-yyyy HH:mm:ss";
        private decimal _lastBalanceBeforeTrading;
        private int _id = 1;

        public Logger(BubbleRider qCAlgorithm, bool debugEnabled)
        {
            this.QcAlgorithm = qCAlgorithm;
            this.DebugEnabled = debugEnabled;
        }

        public string CustomTimeFormat
        {
            get
            {
                if (QcAlgorithm.LiveMode)
                {
                    return $"[{DateTime.Now.ToString(TimeFormat)}]";
                }
                else
                {
                    return $"[{QcAlgorithm.Time.ToString(TimeFormat)}]";
                }
            }
        }

        /// <summary>
        /// Logs Initial BBR Settings
        /// </summary>
        public void InfoSettings(string message)
        {
            QcAlgorithm.Log($"{CustomTimeFormat} INFO (CUSTOM) {message}");
        }

        /// <summary>
        /// [07-24-2018 16:03:06] INFO (SYSTEM) Status Initialized | Symbol USDBTC | Spread 3000 | Balance (BIT) 2000000 | Leverage 200 | Min Lot 0.01 | Max Lot 1
        /// </summary>
        public void InfoSystem()
        {
            QcAlgorithm.Log($"{CustomTimeFormat} INFO (SYSTEM) Status Initialized | " +
                            $"Symbol {QcAlgorithm.MySymbol} | " +
                            $"Spread {QcAlgorithm.Securities[QcAlgorithm.MySymbol].AskPrice - QcAlgorithm.Securities[QcAlgorithm.MySymbol].BidPrice} | " +
                            $"Balance ({QcAlgorithm.AccountCurrency}) {QcAlgorithm.Portfolio.Cash} | " +
                            $"Leverage {QcAlgorithm.Securities[QcAlgorithm.MySymbol].Leverage}");
        }

        /// <summary>
        /// Logs bar data
        /// </summary>
        /// <example>
        /// [07-24-2018 16:03:06] INFO (BAR) OPEN 7725.52 | HIGH 7809 | LOW 7628| CLOSE 7746.63
        /// </example>
        /// <param name="tradeBar"></param>
        /// <param name="isWarmUp"></param>
        public void InfoBar(TradeBar tradeBar, bool isWarmUp = false)
        {
            if (DebugEnabled)
            {
                string time = QcAlgorithm.LiveMode
                    ? CustomTimeFormat
                    : "[" + tradeBar.Time.ToUniversalTime().ToString(TimeFormat) + "]";

                QcAlgorithm.Debug($"{time} DEBUG (BAR) OPEN {tradeBar.Open} |" +
                                $" HIGH {tradeBar.High} |" +
                                $" LOW {tradeBar.Low} |" +
                                $" CLOSE {tradeBar.Close}");
            }
            else if (QcAlgorithm.LiveMode)
            {
                string time = isWarmUp
                    ? "[" + tradeBar.Time.ToUniversalTime().ToString(TimeFormat) + "]"
                    : CustomTimeFormat;

                QcAlgorithm.Log($"{time} INFO (BAR) OPEN {tradeBar.Open} |" +
                $" HIGH {tradeBar.High} |" +
                $" LOW {tradeBar.Low} |" +
                $" CLOSE {tradeBar.Close}");
            }
        }

        /// <summary>
        /// Log indicator data
        /// </summary>
        /// <example>
        /// [07-24-2018 16:03:06] INFO (INDICATOR) PSAR 7346.31 | ADX 52.00
        /// </example>
        /// <param name="tradeBar"></param>
        /// <param name="parabolic"></param>
        /// <param name="avgDirectionalIndex"></param>
        /// <param name="donchian"></param>
        /// <param name="isWarmUp"></param>
        public void InfoIndicator(
            TradeBar tradeBar,
            ParabolicStopAndReverse parabolic,
            AverageDirectionalIndex avgDirectionalIndex,
            DonchianChannel donchian,
            bool isWarmUp = false
        )
        {
            if (DebugEnabled)
            {
                string time = QcAlgorithm.LiveMode
                    ? CustomTimeFormat
                    : "[" + tradeBar.Time.ToUniversalTime().ToString(TimeFormat) + "]";

                QcAlgorithm.Debug($"{time} DEBUG (INDICATOR) PSAR {Math.Round(parabolic.Current.Price, 2)} | " +
                    $"ADX {Math.Round(avgDirectionalIndex.Current.Price, 2)} | " +
                    $"Donchian {Math.Round(donchian.LowerBand.Current.Price, 2)}");
            }
            else if (QcAlgorithm.LiveMode)
            {
                string time = isWarmUp
                    ? "[" + tradeBar.Time.ToUniversalTime().ToString(TimeFormat) + "]"
                    : CustomTimeFormat;

                QcAlgorithm.Log($"{time} INFO (INDICATOR) PSAR {Math.Round(parabolic.Current.Price, 2)} | " +
                    $"ADX {Math.Round(avgDirectionalIndex.Current.Price, 2)} | " +
                    $"Donchian {Math.Round(donchian.LowerBand.Current.Price, 2)}");
            }
        }

        /// <summary>
        /// Log position data on order events
        /// </summary>
        /// <example>
        /// [07-24-2018 18:03:06] INFO (ORDER) Action Create | Id 6 | Position Id 3 | Type Stop Market | Status Submitted | Price 7550.20 | Size 12.85790468 | Direction Sell
        /// [07-24-2018 18:03:06] INFO (ORDER) Action Filled | Id 6 | Position Id 3 | Type Stop Market | Status Filled | Price 7560.41 | Size 0.85790468 | Direction Sell
        /// [07-24-2018 18:03:06] INFO (ORDER) Action Cancel | Id 6 | Position Id 3 | Type Stop Market | Status Canceled
        /// </example>
        /// <param name="orderEvent"></param>
        /// <param name="quoteBalance">Quote Balance</param>
        /// <param name="quoteCurrency">Quote Currency i.e. USD in BTCUSD</param>
        public void InfoOrderEvent(
            OrderEvent orderEvent,
            decimal quoteBalance,
            string quoteCurrency
        )
        {
            if (orderEvent.FillPrice > 0 && orderEvent.Status == OrderStatus.Filled)
            {
                QcAlgorithm.Log($"{CustomTimeFormat} INFO (ORDER) Action Create |" +
                                $" Id {orderEvent.OrderId} |" +
                                $" Position Id {_id} |" + 
                                $" Type Market |" +
                                $" Status {orderEvent.Status} |" +
                                $" Price {orderEvent.FillPrice} |" +
                                $" Size {orderEvent.FillQuantity} |" +
                                $" Direction {orderEvent.Direction}");
            }
        }

        /// <summary>
        /// Logs info about position opening
        /// </summary>
        /// <example>
        /// [07-24-2018 16:03:06] INFO (POSITION) Action Open | ID 3 | Direction Long | Price: 7753.985 | Quantity 12.85790468 | USD Balance before 99700.90 | Stop Loss 7500.21 | Take Low Profit 7800.61 | Take Profit 7900.23
        /// </example>
        /// <param name="price"></param>
        /// <param name="quantity"></param>
        /// <param name="quoteCurrency"></param>
        /// <param name="previousQuoteBalance"></param>
        /// <param name="stopLossPrice"></param>
        public void InfoPositionOpen(decimal price,
            decimal quantity,
            string quoteCurrency,
            decimal previousQuoteBalance,
            decimal stopLossPrice)
        {
            _lastBalanceBeforeTrading = previousQuoteBalance;

            QcAlgorithm.Debug($"{CustomTimeFormat} INFO (POSITION) Action Open |" +
                              $" ID {_id} |" +
                              $" Direction Long |" +
                              $" Price {price} |" +
                              $" Quantity {quantity} |" +
                              $" {quoteCurrency} Balance Before {previousQuoteBalance} |" +
                              $" Stop Loss {stopLossPrice}");
        }

        /// <summary>
        /// Logs info about position update
        /// </summary>
        /// <example>
        /// [07-24-2018 18:03:06] INFO (POSITION) Action Update | ID 3 | Stop Loss 7505.21
        /// </example>
        /// <param name="stopLoss"></param>
        public void InfoPositionUpdate(decimal stopLoss)
        {
            QcAlgorithm.Debug($"{CustomTimeFormat} INFO (POSITION) Action Update |" +
                              $" ID {_id} |" +
                              $" Stop Loss {stopLoss}");
        }

        /// <summary>
        /// Logs info about position close
        /// </summary>
        /// <example>
        /// [07-24-2018 18:03:06] INFO (POSITION) Action Close | ID 3 | Direction Long | Price: 7793.985 | Quantity 12.85790468 | USD Balance after 99700.90
        /// </example>
        /// <param name="price"></param>
        /// <param name="quantity"></param>
        /// <param name="quoteCurrency"></param>
        /// <param name="previousQuoteBalance"></param>
        public void InfoPositionClose(
            decimal price,
            decimal quantity,
            string quoteCurrency,
            decimal previousQuoteBalance)
        {
            QcAlgorithm.Debug($"{CustomTimeFormat} INFO (POSITION) Action Close |" +
                              $" ID {_id++} |" +
                              $" Direction Long |" +
                              $" Price {price} |" +
                              $" Quantity {quantity} |" +
                              $" {quoteCurrency} Balance After {previousQuoteBalance}");
        }

        /// <summary>
        /// Log a custom message
        /// </summary>
        public void InfoCustom(String message)
        {
            QcAlgorithm.Log($"{CustomTimeFormat} INFO (CUSTOM) {message}");
        }

        /// <summary>
        /// Log an error if there was a failure canceling an order
        /// </summary>
        /// <param name="response"></param>
        public void ErrorCancelingOrder(OrderResponse response)
        {
            QcAlgorithm.Log($"{CustomTimeFormat} ERROR Canceling Order {response.OrderId}. " +
                $"Error: {response.ErrorMessage}, " +
                $"Code: {response.ErrorCode}");
        }

        /// <summary>
        /// Log an error if th exchange is closed
        /// </summary>
        public void ErrorExchangeClosed()
        {
            QcAlgorithm.Log($"{CustomTimeFormat} ERROR GDAX is Closed.");
        }

        /// <summary>
        /// Log an error on orders mismatch
        /// </summary>
        /// <param name="ordersCanceled"></param>
        public void ErrorInNumberOfOrdersCanceled(List<OrderTicket> ordersCanceled)
        {
            QcAlgorithm.Log($"{CustomTimeFormat} ERROR in Number of Orders, total: {ordersCanceled.Count}.");

            if (ordersCanceled.Count > 0)
            {
                foreach (var c in ordersCanceled)
                {
                    QcAlgorithm.Log($"{CustomTimeFormat} ERROR Order " +
                        $"Id: {c.OrderId} " +
                        $"Time {c.Time} " +
                        $"Order Closed {c.OrderClosed} " +
                        $"Quantity {c.Quantity} " +
                        $"Status {c.Status}");
                }
            }
        }

        /// <summary>
        /// Log an error on orders mismatch
        /// </summary>
        /// <param name="orders"></param>
        public void ErrorInNumberOfOrders(IEnumerable<Order> orders)
        {
            IEnumerable<Order> enumerableOrders = orders as Order[] ?? orders.ToArray();
            QcAlgorithm.Log($"{CustomTimeFormat} ERROR in Number of Orders, total: {enumerableOrders.Count()}.");

            if (!enumerableOrders.Any())
                return;

            foreach (var c in enumerableOrders)
            {
                QcAlgorithm.Log($"{CustomTimeFormat} ERROR Order " +
                                $"Id {c.Id} " +
                                $"Time {c.Time} " +
                                $"Quantity {c.Quantity} " +
                                $"Status {c.Status}");
            }
        }

        /// <summary>
        /// Log an error if the order event status is invalid
        /// </summary>
        /// <param name="orderEvent"></param>
        public void ErrorOnOrderEvent(OrderEvent orderEvent)
        {
            QcAlgorithm.Log($"{CustomTimeFormat} ERROR Order {orderEvent.OrderId} " +
                $"Invalid. Message: {orderEvent.Message}. " +
                $"Status: {orderEvent.Status}.");
        }

        /// <summary>
        /// Log an error if the position failed to open
        /// </summary>
        /// <param name="orderStatus"></param>
        public void ErrorOnPositionOpen(OrderStatus orderStatus)
        {
            QcAlgorithm.Log($"{CustomTimeFormat} ERROR Action Open. " +
                $"Status {orderStatus}");
        }

        /// <summary>
        /// Log an error if the position failed to open
        /// </summary>
        /// <param name="orderStatus"></param>
        public void ErrorOnPositionClose(OrderStatus orderStatus)
        {
            QcAlgorithm.Log($"{CustomTimeFormat} ERROR Action Close. " +
                            $"Status {orderStatus}");
        }

        /// <summary>
        /// Log an error if stop loss send fails
        /// </summary>
        /// <param name="response"></param>
        /// <param name="status"></param>
        public void ErrorStopLossSend(OrderResponse response, OrderStatus status)
        {
            QcAlgorithm.Log($"{CustomTimeFormat} ERROR Send Stop Loss, " +
                $"message: {response.ErrorMessage}. " +
                $"Code: {response.ErrorCode}. " +
                $"Status: {status} ");
        }

        /// <summary>
        /// Log an error if stop loss update fails
        /// </summary>
        /// <param name="response"></param>
        /// <param name="status"></param>
        public void ErrorStopLossUpdate(OrderResponse response, OrderStatus status)
        {
            QcAlgorithm.Log($"{CustomTimeFormat} ERROR Update Stop Loss," +
                $" message: {response.ErrorMessage}. " +
                $"Code: {response.ErrorCode}. " +
                $"Status: {status}");
        }

        /// <summary>
        /// Log an error if the trading symbol is not tradable
        /// </summary>
        public void ErrorSymbolNotTradable(string symbol)
        {
            QcAlgorithm.Log($"{CustomTimeFormat} ERROR {symbol} Not Tradable.");
        }

        /// <summary>
        /// Log a FATAL message
        /// </summary>
        public void FatalHistoryConsolidator()
        {
            QcAlgorithm.Error($"{CustomTimeFormat} FATAL History Consolidator was given a wrong resolution parameter.");
        }

        /// <summary>
        /// Reports every 6 hours the following data:
        /// </summary>
        /// <param name="mySymbol">Trading Symbol</param>
        /// <param name="baseCurrency">Base Currency i.e. BTC in BTCUSD </param>
        /// <param name="quoteCurrency">Quote Currency i.e. USD in BTCUSD </param>
        public void ScheduleReport(string mySymbol, string baseCurrency, string quoteCurrency)
        {
            QcAlgorithm.Schedule.On(QcAlgorithm.DateRules.EveryDay(mySymbol), QcAlgorithm.TimeRules.Every(TimeSpan.FromHours(6)), () =>
            {
                if (!DebugEnabled && !QcAlgorithm.LiveMode)
                    return;

                string protocol = DebugEnabled ? "DEBUG" : "INFO";
                decimal positionSize = QcAlgorithm.Portfolio.CashBook[baseCurrency].Amount;
                decimal fromStartProfitAndLossPercentage = Math.Round(((QcAlgorithm.Portfolio.TotalPortfolioValue - QcAlgorithm.StartBalance) / QcAlgorithm.StartBalance) * 100, 2);

                decimal currentTradeProfitAndLossPercentage = _lastBalanceBeforeTrading != 0
                    ? Math.Round(((_lastBalanceBeforeTrading + QcAlgorithm.Portfolio[mySymbol].UnrealizedProfit) / _lastBalanceBeforeTrading - 1) * 100, 2)
                    : 0;

                //[07 - 24 - 2018 18:03:06] INFO(REPORT) Portfolio Value[(USD) 15000] CashBook[(USD) 1000 (ETH) 8000000] //For QuantConnect
                //[07 - 24 - 2018 18:03:06] INFO(REPORT) From Start PnL[(USD) 51.0(%) 3.15] | Unrealized PnL[(USD) 10.0(%) 0.61]
                //[07 - 24 - 2018 18:03:06] INFO(REPORT) Pending Orders[(↑) Size: 0.12 | Quantity: 2] [(↓) Size: 0.00 | Quantity: 0]
                //[07-24-2018 18:03:06] INFO(REPORT) Positions[(↑) Size: 0.10 | Quantity: 1][(↓) Size: 0.20 | Quantity: 4]

                QcAlgorithm.Debug($"{CustomTimeFormat} {protocol} (REPORT) " +
                                  $"Portfolio Value [{quoteCurrency} {Math.Round(QcAlgorithm.Portfolio.TotalPortfolioValue, 2)}] " +
                                  $"CashBook [({quoteCurrency}) {Math.Round(QcAlgorithm.Portfolio.CashBook[quoteCurrency].Amount, 2)} ({baseCurrency}) {Math.Round(QcAlgorithm.Portfolio.CashBook[baseCurrency].Amount, 2)}]");

                QcAlgorithm.Debug($"{CustomTimeFormat} {protocol} (REPORT) " +
                                  $"From Start PnL [(USD) {Math.Round(QcAlgorithm.Portfolio.TotalPortfolioValue - QcAlgorithm.StartBalance, 2)} (%) {fromStartProfitAndLossPercentage}] | " +
                                  $"Unrealized PnL [(USD) {Math.Round(QcAlgorithm.Portfolio[mySymbol].UnrealizedProfit, 2)} (%) {currentTradeProfitAndLossPercentage}]");

                QcAlgorithm.Debug($"{CustomTimeFormat} {protocol} (REPORT) Positions [(↑) Size: {positionSize} | Quantity: {(positionSize > 0 ? 1 : 0)}]");
            });
        }
    }
}
using QuantConnect.Orders;
using System;
using System.Collections.Generic;
using System.Linq;

namespace QuantConnect.Algorithm.CSharp.My_Strategies.BubbleRider
{
    public partial class BubbleRider
    {
        public decimal StopPrice;
        /// <summary>
        /// Sends a Long Market Order with a Stop Loss at the Parabolic SAR value
        /// </summary>
        public void OpenPosition()
        {
            decimal quoteBalanceBeforeTrade = QuoteCurrencyBalance;
            string tag = TagTrade;
            SetHoldings(MySymbol, 1m, false, tag);

            IEnumerable<Order> orders = Transactions.GetOrders(
                item => item.Symbol == MySymbol &&
                item.Tag.Contains(tag)
            );

            if (orders.Count() == 1)
            {
                Order order = orders.First();
                int id = order.Id;

                if (LiveMode)
                {
                    Transactions.WaitForOrder(id);
                }

                if (order.Status == OrderStatus.Filled)
                {
                    StopPrice = Math.Round(_histDonchian[0], 2);
                    decimal quantity = Portfolio.CashBook[BaseCurrency].Amount;

                    MyLogger.InfoPositionOpen(order.Price, quantity, QuoteCurrency, quoteBalanceBeforeTrade, StopPrice);
                }
                else
                {
                    MyLogger.ErrorOnPositionOpen(order.Status);
                }
            }
            else
            {
                MyLogger.ErrorInNumberOfOrders(orders);
            }
        }

        /// <summary>
        /// Updates a Stop Loss Order to the Donchian Lower Band Value
        /// </summary>
        public void UpdatePosition()
        {
            StopPrice = Math.Round(_histDonchian[0], 2);

            MyLogger.InfoPositionUpdate(StopPrice);
        }

        /// <summary>
        /// Closes a Long Market Order
        /// </summary>
        public void ClosePosition()
        {
            string tag = TagTrade + "Close";
            Liquidate(_mySecurity.Symbol, tag);

            IEnumerable<Order> orders = Transactions.GetOrders(
                item => item.Symbol == MySymbol &&
                        item.Tag.Contains(tag)
            );

            if (orders.Count() == 1)
            {
                Order order = orders.First();
                int id = order.Id;

                if (LiveMode)
                {
                    Transactions.WaitForOrder(id);
                }

                if (order.Status == OrderStatus.Filled)
                {
                    MyLogger.InfoPositionClose(order.Price, order.Quantity, QuoteCurrency, QuoteCurrencyBalance);
                }
                else
                {
                    MyLogger.ErrorOnPositionClose(order.Status);
                }
            }
            else
            {
                MyLogger.ErrorInNumberOfOrders(orders);
            }
        }
    }
}