Overall Statistics |
Total Trades 14 Average Win 1.98% Average Loss -3.80% Compounding Annual Return -59.917% Drawdown 19.400% Expectancy -0.565 Net Profit -14.380% Sharpe Ratio -1.755 Probabilistic Sharpe Ratio 9.150% Loss Rate 71% Win Rate 29% Profit-Loss Ratio 0.52 Alpha -0.569 Beta -0.069 Annual Standard Deviation 0.324 Annual Variance 0.105 Information Ratio -1.595 Tracking Error 0.357 Treynor Ratio 8.272 Total Fees $324.62 |
using System; using System.Collections; using System.Collections.Generic; using QuantConnect.Securities; using QuantConnect.Models; namespace QuantConnect { 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 { public partial class BubbleRider : QCAlgorithm { //Parameters private const bool UseCustomData = false; private const bool UseBrokerageModel = true; private const bool DebugEnabled = true; private const string BaseCurrency = "ETH"; private const string QuoteCurrency = "USD"; private const string MySymbol = BaseCurrency + QuoteCurrency; 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.Minute; //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; private TradeBuilder _tradeBuilder; /// <summary> /// Retrieves Quote Currency Balance (rounded) /// </summary> public decimal QuoteCurrencyBalance { get { return Math.Round(Portfolio.CashBook[QuoteCurrency].Amount, 2); } } /// <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, 07, 01); SetEndDate(2019, 08, 31); SetCash(10000); if (UseBrokerageModel) { SetBrokerageModel(BrokerageName.GDAX, AccountType.Cash); _mySecurity = AddCrypto(MySymbol, _resolution); BrokerageModel.GetFeeModel(_mySecurity); } else { _mySecurity = AddCrypto(MySymbol, _resolution); } } MyLogger = new Logger(this, DebugEnabled); 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.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."); _tradeBuilder = new TradeBuilder(FillGroupingMethod.FlatToFlat, FillMatchingMethod.FIFO); SetTradeBuilder(_tradeBuilder); } /// <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) return; 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) { //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] > _adxFilterLevel) { OpenPosition(); } } else if (_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 _tradeBuilder.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}%"); } // 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; namespace QuantConnect.Algorithm.CSharp { public class Logger { private QCAlgorithm QcAlgorithm; private bool DebugEnabled; private const string TimeFormat = "MM-dd-yyyy HH:mm:ss"; public Logger(QCAlgorithm 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) { if (DebugEnabled) { QcAlgorithm.Debug($"{CustomTimeFormat} DEBUG (CUSTOM) {message}"); } else { QcAlgorithm.Log($"{CustomTimeFormat} INFO (CUSTOM) {message}"); } } /// <summary> /// Log OHLC data /// </summary> /// <param name="tradeBar"></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> /// <param name="tradeBar"></param> /// <param name="parabolic"></param> /// <param name="avgDirectionalIndex"></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> /// <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) { if (DebugEnabled) { QcAlgorithm.Log($"{CustomTimeFormat} DEBUG (ORDER) Action Create |" + $" Id {orderEvent.OrderId} |" + $" Type Market |" + $" Status {orderEvent.Status} |" + $" Price {orderEvent.FillPrice} |" + $" Size {orderEvent.FillQuantity} |" + $" Direction {orderEvent.Direction}"); } else if (QcAlgorithm.LiveMode) { QcAlgorithm.Log($"{CustomTimeFormat} INFO (ORDER) Action Create |" + $" Id {orderEvent.OrderId} |" + $" Type Market |" + $" Status {orderEvent.Status} |" + $" Price {orderEvent.FillPrice} |" + $" Size {orderEvent.FillQuantity} |" + $" Direction {orderEvent.Direction}"); } } } /// <summary> /// Logs Info when a Position is Opened /// </summary> /// <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) { if (DebugEnabled) { QcAlgorithm.Debug($"{CustomTimeFormat} DEBUG (POSITION) Action Open |" + $" Direction Long |" + $" Price {price} |" + $" Quantity {quantity} |" + $" {quoteCurrency} Balance Before {previousQuoteBalance} |" + $" Stop Loss {stopLossPrice}"); } else if (QcAlgorithm.LiveMode) { QcAlgorithm.Debug($"{CustomTimeFormat} INFO (POSITION) Action Open |" + $" Direction Long |" + $" Price {price} |" + $" Quantity {quantity} |" + $" {quoteCurrency} Balance Before {previousQuoteBalance} |" + $" Stop Loss {stopLossPrice}"); } } /// <summary> /// Logs info about position /// </summary> /// <param name="action"></param> /// <param name="stopLoss"></param> public void InfoPositionUpdate(decimal stopLoss) { if (DebugEnabled) { QcAlgorithm.Debug($"{CustomTimeFormat} DEBUG (POSITION) Action Update |" + $" Stop Loss {stopLoss}"); } else if (QcAlgorithm.LiveMode) { QcAlgorithm.Debug($"{CustomTimeFormat} INFO (POSITION) Action Update |" + $" Stop Loss {stopLoss}"); } } /// <summary> /// /// </summary> /// <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) { if (DebugEnabled) { QcAlgorithm.Debug($"{CustomTimeFormat} DEBUG (POSITION) Action Close |" + $" Direction Long |" + $" Price {price} |" + $" Quantity {quantity} |" + $" {quoteCurrency} Balance After {previousQuoteBalance}"); } else if (QcAlgorithm.LiveMode) { QcAlgorithm.Debug($"{CustomTimeFormat} INFO (POSITION) Action Close |" + $" Direction Long |" + $" Price {price} |" + $" Quantity {quantity} |" + $" {quoteCurrency} Balance After {previousQuoteBalance}"); } } /// <summary> /// Log a custom message /// </summary> public void InfoCustom(String message) { if (DebugEnabled) { QcAlgorithm.Debug($"{CustomTimeFormat} DEBUG (CUSTOM) {message}"); } else { 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="ordersCount"></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) { QcAlgorithm.Log($"{CustomTimeFormat} ERROR in Number of Orders, total: {orders.Count()}."); if (orders.Count() > 0) { foreach (var c in orders) { 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: /// Balance (Quote Currency) | Balance (Base Currency) | Open Orders | Current Position | Position Size /// </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) { string protocol = DebugEnabled ? "DEBUG" : "INFO"; decimal positionSize = QcAlgorithm.Portfolio.CashBook[baseCurrency].Amount; QcAlgorithm.Debug($"{CustomTimeFormat} {protocol} (REPORT) Balance ({quoteCurrency}) {Math.Round(QcAlgorithm.Portfolio.CashBook[quoteCurrency].Amount, 2)} |" + $" Balance {baseCurrency} {positionSize} |" + $" Current Position {(positionSize > 0 ? "Long" : "None")} |" + $" Position Size {positionSize}"); } }); } } }
using QuantConnect.Orders; using System; using System.Collections.Generic; using System.Linq; namespace QuantConnect.Algorithm.CSharp { 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); } } } }