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