Overall Statistics |
Total Trades 58 Average Win 0.09% Average Loss -0.23% Compounding Annual Return -12.201% Drawdown 2.600% Expectancy -0.379 Net Profit -2.487% Sharpe Ratio -4.238 Loss Rate 55% Win Rate 45% Profit-Loss Ratio 0.38 Alpha -0.104 Beta -0.015 Annual Standard Deviation 0.024 Annual Variance 0.001 Information Ratio 0.626 Tracking Error 0.096 Treynor Ratio 6.985 Total Fees $185.65 |
/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System.Collections.Generic; using QuantConnect.Data.Market; using QuantConnect.Orders; using QuantConnect.Securities; using QuantConnect.Data.UniverseSelection; using QuantConnect.Algorithm; using QuantConnect.Algorithm.CSharp; using System.Linq; using System; namespace QuantConnect { public partial class QuantFramework : QCAlgorithm { //Cap the investment maximum size ($). public decimal maxTradeValueSizePerAsset = 5000; private Resolution _dataResolution = Resolution.Minute; private Dictionary<Symbol, TradingAsset> _tradingAssets = new Dictionary<Symbol, TradingAsset>(); BackTestParameters backTestParameters; UniverseSetup universeSetup; public override void Initialize() { bool isLocal = false; backTestParameters = new BackTestParameters(this, _dataResolution); universeSetup = new UniverseSetup(this, IncludeTradingAssets); if (isLocal) { backTestParameters.setUpLocal(); universeSetup.setUpLocal(); } else { backTestParameters.setUp(); universeSetup.setUp(); } } IEnumerable<Symbol> IncludeTradingAssets() { return _tradingAssets.Values.Where(x => x.OpenOrders).Select(x => x.Symbol); } /// <summary> /// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here. /// </summary> public void OnData(TradeBars data) { if (IsWarmingUp) return; foreach (var bar in data.Values) { try { if (_tradingAssets.ContainsKey(bar.Symbol)) { _tradingAssets[bar.Symbol].Scan(bar); } } catch (Exception exp) { Log(string.Format("Exception {0}\n{1}", exp, exp.StackTrace)); } } } public override void OnSecuritiesChanged(SecurityChanges changes) { foreach (Security s in changes.RemovedSecurities) { if (_tradingAssets.ContainsKey(s.Symbol)) { if (!_tradingAssets[s.Symbol].OpenOrders) { _tradingAssets.Remove(s.Symbol); } } } foreach (Security s in changes.AddedSecurities) { if (_tradingAssets.ContainsKey(s.Symbol)) { continue; } _tradingAssets.Add( s.Symbol, new TradingAsset(this, s.Symbol, new BounceSignal(this, s.Symbol), maxTradeValueSizePerAsset)); } } public override void OnOrderEvent(OrderEvent orderEvent) { _tradingAssets[orderEvent.Symbol].OnOrderEvent(orderEvent); } } }
using System; using QuantConnect.Data.Market; using QuantConnect.Indicators; namespace QuantConnect.Algorithm.CSharp { public class BounceSignal : TradeSignal { int atrPeriod = 22; decimal buyDiscount = 0.97m; decimal sellPremium = 1.03m; decimal stopPercentage = 0.02m; decimal minimumAtrPercentage = 0.01m; decimal adjustStopPrice = 0.01m; AverageTrueRange _atr; public BounceSignal(QCAlgorithm algorithm, Symbol symbol) : base(algorithm, symbol) { _atr = algorithm.ATR(symbol, atrPeriod, MovingAverageType.Exponential, Resolution.Minute); } private decimal stopPrice(decimal lastPrice) { return lastPrice - _atr / 2.0m; } public override void Scan(TradeBar bar) { if (!_atr.IsReady) return; if (TradeProfile == null) { if (_atr / bar.Close > minimumAtrPercentage) { var buyPrice = bar.Close * buyDiscount; EntrySignal = new Signal(SignalType.Long, buyPrice, stopPrice(buyPrice)); } } else if (TradeProfile.OpenTicket.Status == Orders.OrderStatus.Filled) { //ExitSignal = new Signal(SignalType.Exit, _atr * sellPremium, 0); var fillPrice = TradeProfile.OpenTicket.AverageFillPrice; var lastStopPrice = Math.Max(EntrySignal.StopPrice, ExitSignal.StopPrice); var newStopPrice = stopPrice(bar.Close); if (newStopPrice > (1 + adjustStopPrice) * lastStopPrice) { ExitSignal = new Signal(SignalType.AdjustStop, 0, newStopPrice); } else { ExitSignal = new Signal(SignalType.NoSignal, 0, lastStopPrice); } } } } }
using System; using System.Collections.Generic; namespace QuantConnect.Algorithm.CSharp { public class AssetType { string _etf = "UVXY,XIV,NUGT,DUST,JNUG,JDUST,LABU,LABD,GUSH,DRIP,TVIX,GASL,GASX,DWTI,UWTI,DGAZ,UGAZ,UBIO,ZBIO,BRZU,RUSS,SCO,UCO,RUSL,ERY,ERX,BIOL,SVXY,VXX,SILJ,BIB,BIS,VIXY,SOXL,VIIX,SOXS,BZQ,USLV,SLVP,DSLV,GDXJ,GLDX"; public string[] _forex = { "EURUSD", "GBPUSD", "USDCAD", "AUDUSD", "USDCHF" }; public string[] Forex { get { return _forex; } } HashSet<string> _etfSet; QCAlgorithm _algorithm; public AssetType(QCAlgorithm algorithm) { _algorithm = algorithm; _etfSet = new HashSet<string>(_etf.Split(new string[1] { "," }, StringSplitOptions.RemoveEmptyEntries)); } public bool isVolatileEtf(Symbol symbol) { return _etfSet.Contains(symbol.Value); } } }
using QuantConnect.Data.Market; using QuantConnect.Orders; namespace QuantConnect.Algorithm.CSharp { public enum SignalType { Long = 1, Short = -1, Exit = 2, AdjustStop = 3, NoSignal = 0 } public struct Signal { public Signal(SignalType signalType, decimal buyPrice, decimal stopPrice) : this() { SignalType = signalType; BuyPrice = buyPrice; StopPrice = stopPrice; } public SignalType SignalType { get; set; } public decimal BuyPrice { get; set; } public decimal StopPrice { get; set; } } public abstract class TradeSignal { public Symbol Symbol { get; private set; } public QCAlgorithm Algorithm { get; private set; } public Signal EntrySignal { get; set; } public Signal ExitSignal { get; set; } public TradeProfile TradeProfile { get; set; } public OrderDirection EntryDirection { get { if (EntrySignal.SignalType == SignalType.Long) { return OrderDirection.Buy; } if (EntrySignal.SignalType == SignalType.Short) { return OrderDirection.Sell; } return OrderDirection.Hold; } } public TradeSignal(QCAlgorithm algorithm, Symbol symbol) { Algorithm = algorithm; Symbol = symbol; EntrySignal = new Signal(SignalType.NoSignal, 0, 0); ExitSignal = new Signal(SignalType.NoSignal, 0, 0); TradeProfile = null; } public abstract void Scan(TradeBar bar); } }
using System; using QuantConnect.Data.Market; using QuantConnect.Orders; namespace QuantConnect.Algorithm.CSharp { public class TradeProfile { private decimal minimumPurchase = 500m; //Ticket tracking the open order public OrderTicket OpenTicket, StopTicket, ExitTicket; //Keeps track of the current price and the direction of the trade public Symbol Symbol { get; private set; } TradeBar _lastBar; Signal _entrySignal; public Signal EntrySignal { get { return _entrySignal; } set { _entrySignal = value; } } private readonly int _tradeQuantity; public int Quantity { get { if (EntrySignal.SignalType == SignalType.Long) { return _tradeQuantity; } if (EntrySignal.SignalType == SignalType.Short) { return -1 * _tradeQuantity; } return 0; } } public TradeProfile(Symbol symbol, TradeBar lastBar, Signal entrySignal, decimal purchasingPower) { Symbol = symbol; _lastBar = lastBar; EntrySignal = entrySignal; _tradeQuantity = 0; if (purchasingPower > minimumPurchase) { _tradeQuantity = (int)(purchasingPower / entrySignal.BuyPrice); } } internal void Update(TradeBar bar) { if (bar.Symbol == Symbol) { _lastBar = bar; } } public bool isOpen() { if (OpenTicket != null && OpenTicket.Status == OrderStatus.Filled) { return !isClosed(); } return false; } public bool isClosed() { if (OpenTicket == null || OpenTicket.Status != OrderStatus.Filled) { return false; } if (ExitTicket != null && ExitTicket.Status == OrderStatus.Filled) { return true; } if (StopTicket != null && StopTicket.Status == OrderStatus.Filled) { return true; } return false; } internal void AdjustStopPrice(decimal stopPrice) { if (StopTicket != null) { var updateOrderFields = new UpdateOrderFields(); updateOrderFields.StopPrice = stopPrice; StopTicket.Update(updateOrderFields); } else { _entrySignal.StopPrice = stopPrice; } } } }
using System.Collections.Generic; using System.Linq; using QuantConnect.Data.Market; using QuantConnect.Orders; using System; using System.Diagnostics.Contracts; namespace QuantConnect.Algorithm.CSharp { class TradingAsset { //public IExitSignal ExitSignal; public TradeSignal _tradeSignal; private readonly QCAlgorithm _algorithm; Object orderLock = new Object(); TradeProfile _tradeProfile; public Symbol Symbol { get; private set; } decimal _maxTradeValue; decimal _lastTradePrice; decimal _lastTradeMarginPercent = 0.25m; // 50 % public bool OpenOrders { get { return _tradeProfile != null; } } public TradingAsset(QCAlgorithm algorithm, Symbol symbol, TradeSignal tradeSignal, decimal maxTradeValue) { Symbol = symbol; _tradeSignal = tradeSignal; _algorithm = algorithm; _maxTradeValue = maxTradeValue; _tradeProfile = null; _lastTradePrice = -1; } public void Scan(TradeBar bar) { _tradeSignal.Scan(bar); if (_tradeProfile != null) { _tradeProfile.Update(bar); } ScanEntrySignal(bar); ScanExitSignal(bar); } decimal buyingPower(OrderDirection direction) { return Math.Min(_maxTradeValue, _algorithm.Portfolio.GetBuyingPower(Symbol, direction)); } void ScanEntrySignal(TradeBar bar) { if (_tradeProfile != null) return; // debouncing to make sure trade don't happen in same price ranges if (_lastTradePrice != -1) { var high = _lastTradePrice * (1 + _lastTradeMarginPercent); var low = _lastTradePrice * (1 - _lastTradeMarginPercent); if (!(bar.Close > high || bar.Close < low)) { return; } } if (_tradeSignal.EntrySignal.SignalType == SignalType.Long || _tradeSignal.EntrySignal.SignalType == SignalType.Short) { //Creates a new trade profile once it enters a trade _tradeProfile = new TradeProfile(Symbol, bar, _tradeSignal.EntrySignal, buyingPower(_tradeSignal.EntryDirection)); if (_tradeProfile.Quantity != 0) { lock (orderLock) { _tradeProfile.OpenTicket = _algorithm.LimitOrder(Symbol, _tradeProfile.Quantity, _tradeSignal.EntrySignal.BuyPrice); if (_tradeProfile.OpenTicket.Status != OrderStatus.Invalid) { _tradeSignal.TradeProfile = _tradeProfile; } else { _tradeProfile = null; } } } } } void ScanExitSignal(TradeBar bar) { if (_tradeProfile == null) return; var exitSignal = _tradeSignal.ExitSignal; if (exitSignal.SignalType != SignalType.NoSignal) { Contract.Ensures(exitSignal.SignalType == SignalType.Exit || exitSignal.SignalType == SignalType.AdjustStop); if (_tradeProfile.isOpen()) { if (exitSignal.SignalType == SignalType.AdjustStop) { _tradeProfile.AdjustStopPrice(exitSignal.StopPrice); _algorithm.Log(string.Format("Adjusting Stop Order {0} {1}", Symbol, exitSignal.StopPrice)); } else { var exitPrice = bar.Close; if (exitSignal.BuyPrice > 0) { exitPrice = exitSignal.BuyPrice; } _algorithm.Log(string.Format("Exit Ticket {0} ", Symbol)); if (_tradeProfile.ExitTicket != null) { var updateOrderFields = new UpdateOrderFields(); updateOrderFields.LimitPrice = bar.Close; _tradeProfile.ExitTicket.Update(updateOrderFields); } else { _tradeProfile.ExitTicket = _algorithm.LimitOrder(bar.Symbol, -1 * (int)_tradeProfile.OpenTicket.QuantityFilled, bar.Close); } } } } } public void OnOrderEvent(OrderEvent orderEvent) { // This can be good way to avoid lots of loop but for now this is not working for me lock (orderLock) { if (orderEvent.Status == OrderStatus.Filled) { _lastTradePrice = orderEvent.FillPrice; if (orderEvent.OrderId == _tradeProfile.OpenTicket.OrderId) { createStopOrder(_tradeProfile); } else { if (_tradeProfile.StopTicket != null && _tradeProfile.StopTicket.OrderId == orderEvent.OrderId) { if (_tradeProfile.ExitTicket != null) { _tradeProfile.ExitTicket.Cancel(); } _tradeProfile = null; return; } if (_tradeProfile.ExitTicket != null && _tradeProfile.ExitTicket.OrderId == orderEvent.OrderId) { if (_tradeProfile.StopTicket != null) { _tradeProfile.StopTicket.Cancel(); } _tradeProfile = null; return; } } } } } void createStopOrder(TradeProfile profile) { if (profile.StopTicket != null) return; //_algorithm.Log(string.Format("Creating Stop Order for {0}", profile.OpenTicket.OrderId)); var quantity = -1 * (int)profile.OpenTicket.QuantityFilled; var stopPrice = profile.EntrySignal.StopPrice; profile.StopTicket = _algorithm.StopMarketOrder(Symbol, quantity, stopPrice); } } }
using System; using QuantConnect.Brokerages; namespace QuantConnect.Algorithm.CSharp { public class BackTestParameters { QCAlgorithm _algorithm; Resolution _resolution; TimeSpan warmUpTime = TimeSpan.FromDays(5); public BackTestParameters(QCAlgorithm algorithm, Resolution resolution) { _algorithm = algorithm; _resolution = resolution; } public void setUp() { _algorithm.SetWarmUp(warmUpTime); _algorithm.SetStartDate(2016, 9, 1); _algorithm.SetEndDate(2016, 11, 7); _algorithm.SetCash(25000); _algorithm.UniverseSettings.Resolution = _resolution; _algorithm.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin); } public void setUpLocal() { decimal cash = 25000; _algorithm.SetWarmUp(warmUpTime); _algorithm.SetStartDate(2015, 06, 01); //Set Start Date _algorithm.SetEndDate(2016, 05, 01); //Set End Date _algorithm.SetCash(cash); _algorithm.SetBenchmark(delegate (DateTime dateTime) { return cash; }); _algorithm.UniverseSettings.Resolution = Resolution.Minute; _algorithm.SetBrokerageModel(BrokerageName.FxcmBrokerage, AccountType.Margin); } } }
using System; using System.Collections.Generic; using System.Linq; using QuantConnect.Data.UniverseSelection; using QuantConnect.Securities.Forex; namespace QuantConnect.Algorithm.CSharp { public class UniverseSetup { static int NumberOfSymbols = 100; decimal minDollarVolume = 500000; decimal maxDollarVolume = 10000000; QCAlgorithm _algorithm; AssetType _assetType; Func<IEnumerable<Symbol>> _includeTradingAssets; public UniverseSetup(QCAlgorithm algorithm, Func<IEnumerable<Symbol>> includeTradingAssets) { _algorithm = algorithm; _assetType = new AssetType(_algorithm); _includeTradingAssets = includeTradingAssets; } public void setUpLocal() { foreach (var security in _algorithm.Securities.Values) { _algorithm.RemoveSecurity(security.Symbol); } foreach (string symbol in _assetType.Forex) { _algorithm.AddSecurity(SecurityType.Forex, symbol, Resolution.Minute); } } public void setUp() { _algorithm.AddUniverse(CoarseSelectionFunction); } // sort the data by daily dollar volume and take the top 'NumberOfSymbols' public IEnumerable<Symbol> CoarseSelectionFunction(IEnumerable<CoarseFundamental> coarse) { var filterVolatileEtfs = coarse.Where(x => !_assetType.isVolatileEtf(x.Symbol)); var dollarVolumeFilter = filterVolatileEtfs.Where( x => x.DollarVolume > minDollarVolume && x.DollarVolume < maxDollarVolume); // sort descending by daily dollar volume var sortedByDollarVolume = dollarVolumeFilter.OrderByDescending(x => x.DollarVolume); // take the top entries from our sorted collection var topN = sortedByDollarVolume.Take(NumberOfSymbols).Select(x => x.Symbol); if (_includeTradingAssets != null) { topN = topN.Union(_includeTradingAssets.Invoke()); } return topN; } } }