| Overall Statistics |
|
Total Trades 64 Average Win 10.65% Average Loss -3.91% Compounding Annual Return 146.176% Drawdown 31.600% Expectancy 0.746 Net Profit 111.706% Sharpe Ratio 1.435 Probabilistic Sharpe Ratio 69.334% Loss Rate 53% Win Rate 47% Profit-Loss Ratio 2.73 Alpha 0.757 Beta -0.03 Annual Standard Deviation 0.524 Annual Variance 0.274 Information Ratio 1.072 Tracking Error 0.536 Treynor Ratio -24.825 Total Fees $2601.85 |
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 = false;
private const bool UseOptimization = false;
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.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;
/// <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, 10, 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.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;
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);
}
}
}
}