Overall Statistics |
Total Trades 6684 Average Win 0.03% Average Loss -0.03% Compounding Annual Return 23.146% Drawdown 6.600% Expectancy 0.182 Net Profit 20.574% Sharpe Ratio 1.472 Loss Rate 49% Win Rate 51% Profit-Loss Ratio 1.30 Alpha 0.181 Beta -0.033 Annual Standard Deviation 0.12 Annual Variance 0.014 Information Ratio 0.275 Tracking Error 0.135 Treynor Ratio -5.276 Total Fees $0.00 |
using QuantConnect.Data.Market; using QuantConnect.Orders; using QuantConnect.Orders.Fills; using QuantConnect.Securities; using System; using System.Linq; namespace QuantConnect.Algorithm.CSharp { public sealed class ImmediateOptimisticStopFillModel : ImmediateFillModel { /// <summary> /// Default limit order fill model in the base security class. /// </summary> /// <param name="asset">Security asset we're filling</param> /// <param name="order">Order packet to model</param> /// <returns>Order fill information detailing the average price and quantity filled.</returns> /// <seealso cref="StopMarketFill(Security, StopMarketOrder)"/> /// <seealso cref="MarketFill(Security, MarketOrder)"/> public override OrderEvent LimitFill(Security asset, LimitOrder order) { //Initialise; var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone); var fill = new OrderEvent(order, utcTime, 0); //If its cancelled don't need anymore checks: if (order.Status == OrderStatus.Canceled) return fill; //Get the range of prices in the last bar: var prices = GetPrices(asset, order.Direction); //-> Valid Live/Model Order: switch (order.Direction) { case OrderDirection.Buy: //Buy limit seeks lowest price if (prices.Low < order.LimitPrice) { //Set order fill: fill.Status = OrderStatus.Filled; // fill at the worse price this bar or the limit price, this allows far out of the money limits // to be executed properly fill.FillPrice = order.LimitPrice; } break; case OrderDirection.Sell: //Sell limit seeks highest price possible if (prices.High > order.LimitPrice) { fill.Status = OrderStatus.Filled; // fill at the worse price this bar or the limit price, this allows far out of the money limits // to be executed properly fill.FillPrice = order.LimitPrice; } break; } // assume the order completely filled if (fill.Status == OrderStatus.Filled) { fill.FillQuantity = order.Quantity; fill.OrderFee = asset.FeeModel.GetOrderFee(asset, order); } return fill; } /// <summary> /// Default stop fill model implementation in base class security. (Stop Market Order Type) /// </summary> /// <param name="asset">Security asset we're filling</param> /// <param name="order">Order packet to model</param> /// <returns>Order fill information detailing the average price and quantity filled.</returns> /// <seealso cref="MarketFill(Security, MarketOrder)"/> /// <seealso cref="SecurityTransactionModel.LimitFill"/> public override OrderEvent StopMarketFill(Security asset, StopMarketOrder order) { //Default order event to return. var utcTime = asset.LocalTime.ConvertToUtc(asset.Exchange.TimeZone); var fill = new OrderEvent(order, utcTime, 0); // make sure the exchange is open before filling if (!IsExchangeOpen(asset)) return fill; //If its cancelled don't need anymore checks: if (order.Status == OrderStatus.Canceled) return fill; //Get the range of prices in the last bar: var prices = GetPrices(asset, order.Direction); //Calculate the model slippage: e.g. 0.01c var slip = asset.SlippageModel.GetSlippageApproximation(asset, order); //Check if the Stop Order was filled: opposite to a limit order switch (order.Direction) { case OrderDirection.Sell: //-> 1.1 Sell Stop: If Price below setpoint, Sell: if (prices.Low < order.StopPrice) { fill.Status = OrderStatus.Filled; fill.FillPrice = order.StopPrice - slip; } break; case OrderDirection.Buy: //-> 1.2 Buy Stop: If Price Above Setpoint, Buy: if (prices.High > order.StopPrice) { fill.Status = OrderStatus.Filled; fill.FillPrice = order.StopPrice + slip; } break; } // assume the order completely filled if (fill.Status == OrderStatus.Filled) { fill.FillQuantity = order.Quantity; fill.OrderFee = asset.FeeModel.GetOrderFee(asset, order); } return fill; } /// <summary> /// Get the minimum and maximum price for this security in the last bar: /// </summary> /// <param name="asset">Security asset we're checking</param> /// <param name="direction">The order direction, decides whether to pick bid or ask</param> private Prices GetPrices(Security asset, OrderDirection direction) { var low = asset.Low; var high = asset.High; var open = asset.Open; var close = asset.Close; var current = asset.Price; if (direction == OrderDirection.Hold) { return new Prices(current, open, high, low, close); } // Only fill with data types we are subscribed to var subscriptionTypes = asset.Subscriptions.Select(x => x.Type).ToList(); // Tick var tick = asset.Cache.GetData<Tick>(); if (subscriptionTypes.Contains(typeof(Tick)) && tick != null) { var price = direction == OrderDirection.Sell ? tick.BidPrice : tick.AskPrice; if (price != 0m) { return new Prices(price, 0, 0, 0, 0); } // If the ask/bid spreads are not available for ticks, try the price price = tick.Price; if (price != 0m) { return new Prices(price, 0, 0, 0, 0); } } // Quote var quoteBar = asset.Cache.GetData<QuoteBar>(); if (subscriptionTypes.Contains(typeof(QuoteBar)) && quoteBar != null) { var bar = direction == OrderDirection.Sell ? quoteBar.Bid : quoteBar.Ask; if (bar != null) { return new Prices(bar); } } // Trade var tradeBar = asset.Cache.GetData<TradeBar>(); if (subscriptionTypes.Contains(typeof(TradeBar)) && tradeBar != null) { return new Prices(tradeBar); } return new Prices(current, open, high, low, close); } /// <summary> /// Determines if the exchange is open using the current time of the asset /// </summary> private static bool IsExchangeOpen(Security asset) { if (!asset.Exchange.DateTimeIsOpen(asset.LocalTime)) { // if we're not open at the current time exactly, check the bar size, this handle large sized bars (hours/days) var currentBar = asset.GetLastData(); if (asset.LocalTime.Date != currentBar.EndTime.Date || !asset.Exchange.IsOpenDuringBar(currentBar.Time, currentBar.EndTime, false)) { return false; } } return true; } private class Prices { public readonly decimal Current; public readonly decimal Open; public readonly decimal High; public readonly decimal Low; public readonly decimal Close; public Prices(IBar bar) : this(bar.Close, bar.Open, bar.High, bar.Low, bar.Close) { } public Prices(decimal current, decimal open, decimal high, decimal low, decimal close) { Current = current; Open = open == 0 ? current : open; High = high == 0 ? current : high; Low = low == 0 ? current : low; Close = close == 0 ? current : close; } } } }
using Accord.MachineLearning.VectorMachines; using Accord.MachineLearning.VectorMachines.Learning; using Accord.Statistics.Kernels.Sparse; using QuantConnect.Securities; using System; using System.Collections.Generic; using System.Linq; using System.Net; namespace QuantConnect.Algorithm.CSharp.Xilera { public partial class NewsAlgorithm : QCAlgorithm { private const string _tickers = @" ADAP AKTX AMRN AMFW AZN AV BCS BBL BP BTI BT CUK DEO GSK GWPH HSBC IHG LYG LXFR MTP MTFB NGG PSO PUK RELX RIO RBS SHPG SNN SMMT UL VOD WPPGY "; private const string _backtestLink = "https://www.dropbox.com/s/0c3rdc2b75byd0z/news.csv?dl=1"; private readonly Dictionary<DateTime, double[]> _backtestNews = new Dictionary<DateTime, double[]>(); private readonly List<string> _tokenTable = new List<string>(); private readonly Dictionary<string, int> _tokenLookup = new Dictionary<string, int>(); private readonly Dictionary<Symbol, Tradable> _tradables = new Dictionary<Symbol, Tradable>(); private static NewsAlgorithm _instance; public override void Initialize() { _instance = this; SetCash(100000); SetStartDate(2017, 1, 1); var tickers = _tickers.Split(new char[] { '\n', ' ' }, StringSplitOptions.RemoveEmptyEntries); foreach (var t in tickers) { var ticker = t.Trim(); if (ticker == "") continue; var tradable = new Tradable(ticker); _tradables[tradable.Symbol] = tradable; } Debug("Downloading backtest data"); DownloadBacktestData(); Debug("Training model"); foreach (var tradable in _tradables.Values) tradable.TrainModel(); var someSecurity = _tradables.Values.First().Security; Schedule.On(DateRules.EveryDay(someSecurity.Symbol), TimeRules.BeforeMarketClose(someSecurity.Symbol, 11), Rebalance); Debug("Init finished"); } private void DownloadBacktestData() { string csvData; using (var client = new WebClient()) { csvData = client.DownloadString(_backtestLink); } var tokens = new Dictionary<string, string[]>(); foreach (var line in csvData.Split('\n')) { var fields = line.Split(';'); if (fields.Length != 2) continue; var date = fields[0]; var bbc = fields[1]; var bbcTokens = TokenizeString(bbc); tokens[date] = bbcTokens; RegisterTokens(bbcTokens); } foreach (var kv in tokens) { _backtestNews[GetDateFromString(kv.Key)] = BuildSparseFeatureVector(kv.Value); } } private static DateTime GetDateFromString(string str) { var year = int.Parse(str.Substring(0, 4)); var month = int.Parse(str.Substring(4, 2)); var day = int.Parse(str.Substring(6, 2)); return new DateTime(year, month, day).Date; } private static string[] TokenizeString(string str) { str = str.ToLowerInvariant(); return str.Split(' ', '\n', ';', ',', '.', ':'); } private void RegisterTokens(string[] tokens) { foreach (var token in tokens) { if (!_tokenLookup.ContainsKey(token)) { int index = _tokenTable.Count; _tokenLookup[token] = index; _tokenTable.Add(token); } } } private double[] BuildSparseFeatureVector(string[] tokens) { var features = new List<double>(); var countedTokens = new Dictionary<string, int>(); foreach (var token in tokens) { int count; if (countedTokens.TryGetValue(token, out count)) { countedTokens[token] = 1 + count; } else { countedTokens[token] = 1; } } foreach (var kv in countedTokens) { int index = _tokenLookup[kv.Key]; features.Add(1 + index); features.Add(kv.Value); } return features.ToArray(); } private void Rebalance() { double sum = 0; foreach (var tradable in _tradables.Values) { sum += Math.Abs(tradable.PredictDirection()); } if (sum == 0) { Liquidate(); } else { foreach (var tradable in _tradables.Values) { var dir = tradable.PredictDirection(); SetHoldings(tradable.Symbol, dir / sum); } } } private class Tradable { public readonly Security Security; public Symbol Symbol { get { return Security.Symbol; } } private SupportVectorMachine<SparseLinear> _classifier; public Tradable(string ticker) { Security = _instance.AddEquity(ticker, Resolution.Minute, leverage: 2); Security.FeeModel = new ConstantFeeTransactionModel(0); } private void GetInstances(out double[][] features, out double[] labels) { var f = new List<double[]>(); var l = new List<double>(); var minDate = _instance._backtestNews.Keys.Min(); var bars = _instance.History(Symbol, minDate, _instance.UtcTime - TimeSpan.FromDays(5), Resolution.Daily); string currentDate = _instance.UtcTime.ToString("yyyyMMdd"); if (bars.Count() < 2) { _instance.Debug("Insufficient bars for " + Symbol.ToString()); features = new double[0][]; labels = new double[0]; return; } var prevBar = bars.First(); foreach (var bar in bars.Skip(1)) { double[] inputs; if (_instance._backtestNews.TryGetValue(prevBar.Time.Date, out inputs)) { var change = bar.Close / prevBar.Close - 1; int label = change >= 0 ? 1 : -1; f.Add(inputs); l.Add(label); } prevBar = bar; } features = f.ToArray(); labels = l.ToArray(); } public void TrainModel() { double[][] features; double[] labels; GetInstances(out features, out labels); if (labels.Length == 0) return; var smo = new SequentialMinimalOptimization<SparseLinear>(); smo.Complexity = 1000; _classifier = smo.Learn(features, labels); } public double PredictDirection() { if (_classifier == null) return 0; double[] inputs; if (!_instance._backtestNews.TryGetValue(_instance.Time.Date, out inputs)) return 0; return _classifier.Compute(inputs); } } } }