Overall Statistics |
Total Trades 90 Average Win 0.18% Average Loss -1.43% Compounding Annual Return -11.512% Drawdown 33.300% Expectancy -0.127 Net Profit -31.455% Sharpe Ratio -0.916 Loss Rate 22% Win Rate 78% Profit-Loss Ratio 0.12 Alpha 0 Beta 0 Annual Standard Deviation 0.123 Annual Variance 0.015 Information Ratio 0 Tracking Error 0 Treynor Ratio 0 Total Fees $111.15 |
/* * 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; using System.Linq; using System.Collections.Generic; using QuantConnect.Data.Market; using QuantConnect.Indicators; using QuantConnect.Orders; namespace QuantConnect.Algorithm.Examples { class MeanRevertSymbolData { public string Symbol; //public Momentum OneMonthPerformance { get; set; } public Momentum ThreeMonthPerformance { get; set; } public SimpleMovingAverage SMA200 { get; set; } public CommodityChannelIndex CCI5 { get; set; } // public decimal ObjectiveScore // { // get // { // // we weight the one month performance higher // decimal weight1 = 100; // decimal weight2 = 75; // // return (weight1 * OneMonthPerformance + weight2 * ThreeMonthPerformance) / (weight1 + weight2); // } // } } /// <summary> /// In this example we look at the canonical 15/30 day moving average cross. This algorithm /// will go long when the 15 crosses above the 30 and will liquidate when the 15 crosses /// back below the 30. /// </summary> public class CommodityChannelIndexAlgorithm : QCAlgorithm { //private const string Symbol = "SPY"; private DateTime previous; //private CommodityChannelIndex cci; //private SimpleMovingAverage sma; private SimpleMovingAverage[] ribbon; private Dictionary<string,int> Quantity = new Dictionary<string,int>(); private int LastDay; private const decimal _stopLimit = 1.001m; private const decimal _limitVal = 0.03m; private readonly HashSet<int> _immediateCancellations = new HashSet<int>(); private const decimal _maxPctPerSymbol = 0.1m; private decimal _minCash = 0m; private decimal _sumOrdersYetToBeFilled = 0m; // these are the symbols we trade List<string> Symbols = new List<string> { "UPRO", "UDOW", "EDC", "TNA", "YINN", "BIB", "CURE", "DRN", "RUSL", "SOXL", "TMF" }; List<MeanRevertSymbolData> SymbolData = new List<MeanRevertSymbolData>(); private readonly List<OrderTicket> _stopTickets = new List<OrderTicket>(); private readonly List<OrderTicket> _limitBuyTickets = new List<OrderTicket>(); /// <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() { // set up our analysis span SetStartDate(2009, 04, 16); SetEndDate(2015, 01, 01); SetCash(53000); //Set Strategy Cash _minCash = 0.05m*Portfolio.Cash; // request SPY data with minute resolution //AddSecurity(SecurityType.Equity, Symbol, Resolution.Daily); foreach (var symbol in Symbols) { // ideally we would use daily data AddSecurity(SecurityType.Equity, symbol, Resolution.Daily); //var oneMonthPerformance = MOM(symbol, 30, Resolution.Daily); var threeMonthPerformance = MOM(symbol, 90, Resolution.Daily); var cci = CCI(symbol, 5,MovingAverageType.Simple,Resolution.Daily); var sma = SMA(symbol, 200, Resolution.Daily); SymbolData.Add(new MeanRevertSymbolData { Symbol = symbol, // OneMonthPerformance = oneMonthPerformance, ThreeMonthPerformance = threeMonthPerformance, CCI5 = cci, SMA200 = sma }); Quantity [symbol] = 0; } // create a 30 day exponential moving average //slow = EMA(Symbol, 30, Resolution.Daily); int ribbonCount = 8; int ribbonInterval = 15; //ribbon = Enumerable.Range(0, ribbonCount).Select(x => SMA(Symbol, (x + 1)*ribbonInterval, Resolution.Daily)).ToArray(); } private int GetNumSymbols(decimal currentPrice) { var risk = 0.01m; if (Portfolio.Cash < _minCash) { Log ("Free margin, no money left"); return 0; } var min_value = Math.Min(Portfolio.Cash, Portfolio.TotalPortfolioValue); var this_risk = risk * min_value; var symbol_risk = 0.14m * currentPrice; var quantity = (int)(Math.Round(this_risk / (symbol_risk))); var totalPrice = quantity * currentPrice; if (totalPrice > _maxPctPerSymbol*min_value) { quantity = (int)(Math.Round(_maxPctPerSymbol*min_value/currentPrice)); } return quantity; } /// <summary> /// Update stop limit orders /// </summary> private void UpdateStops() { foreach(var ticket in _stopTickets) { Log(ticket.ToString()); ticket.Update(new UpdateOrderFields { LimitPrice = Securities[ticket.Symbol].Low - _limitVal, StopPrice = Securities[ticket.Symbol].Low * _stopLimit, Tag = "Change prices: " + Time }); Log("UPDATE2:: " + ticket.UpdateRequests.Last()); } } private void RemoveStop(string symbol) { for (var i = 0; i < _stopTickets.Count; i++) { if (_stopTickets [i].Symbol == symbol) _stopTickets.RemoveAt (i); } } /// <summary> /// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here. /// </summary> /// <param name="data">TradeBars IDictionary object with your stock data</param> public void OnData(TradeBars data) { // a couple things to notice in this method: // 1. We never need to 'update' our indicators with the data, the engine takes care of this for us // 2. We can use indicators directly in math expressions // 3. We can easily plot many indicators at the same time // wait for our slow ema to fully initialize foreach (var symboldata in SymbolData) { if (!symboldata.CCI5.IsReady) return; if (!symboldata.SMA200.IsReady) return; } if (Time.Day != LastDay) { // foreach (var symboldata in SymbolData) { // var holdings = Portfolio[symboldata.Symbol].Quantity; // // we only want to go long if we're currently short or flat // if (holdings > 0) { // // var stopPrice = Securities [symboldata.Symbol].Low * _stopLimit; // var limitPrice = Securities [symboldata.Symbol].Low - _limitVal; // // var newStopTicket = StopLimitOrder (symboldata.Symbol, Quantity [symboldata.Symbol], stopPrice, limitPrice); // _stopLimitTickets.Add (newStopTicket); // } // } UpdateStops (); } LastDay = Time.Day; foreach (var symboldata in SymbolData) { var diff = Securities[symboldata.Symbol].Price / symboldata.SMA200; if (diff < 0.01m) { Log("price under SMA 200, not buying."); } var holdings = Portfolio[symboldata.Symbol].Quantity; // we only want to go long if we're currently short or flat if (holdings <= 0) { if (symboldata.CCI5 <= -100) { var cash_left = Portfolio.Cash - _sumOrdersYetToBeFilled; int num = GetNumSymbols(Securities[symboldata.Symbol].Price); if (num > 0 && cash_left > _minCash) { Quantity[symboldata.Symbol] = num; Log("BUY, symbol = " + symboldata.Symbol + " cci = " + symboldata.CCI5 + " num = " + num + " >> " + Securities[symboldata.Symbol].Price); var limitPrice = Securities [symboldata.Symbol].Price * .999m; var newTicket = LimitOrder(symboldata.Symbol, num, limitPrice); _limitBuyTickets.Add (newTicket); _sumOrdersYetToBeFilled += (decimal)num * limitPrice; //UpdateTemporaryCashBalance (); //var newStopTicket = StopMarketOrder(symboldata.Symbol, -1*Quantity[symboldata.Symbol], stopPrice); //Log("BUY >> " + Securities[Symbol].Price); //SetHoldings(Symbol, 1.0); } } } if (holdings > 0 && symboldata.CCI5 > 100) { //var num = (int)(-0.5 * Quantity); var num = -1 * Quantity[symboldata.Symbol]; Log("SELL symbol = " + symboldata.Symbol + " cci = " + symboldata.CCI5 + " num = " + num + " >> " + Securities[symboldata.Symbol].Price); var newTicket = LimitOrder(symboldata.Symbol,num, Securities[symboldata.Symbol].Price * .999m); //var stopPrice = Securities[Symbol].Low * 1.001m; //var limitPrice = Securities[Symbol].Low - 0.03m; //var newStopTicket = StopLimitOrder(Symbol, Quantity, stopPrice,limitPrice); //Liquidate(Symbol); } } // only once per day if (previous.Date == data.Time.Date) return; // define a small tolerance on our checks to avoid bouncing const decimal tolerance = 0.00015m; // we only want to liquidate if we're currently long // if the fast is less than the slow we'll liquidate our long //Plot(Symbol, "Price", data[Symbol].Price); // easily plot indicators, the series name will be the name of the indicator //Plot(Symbol, cci, sma); //Plot("Ribbon", ribbon); previous = data.Time; } // private void UpdateTemporaryCashBalance() // { // _sumOrdersYetToBeFilled = 0; // foreach (var ticket in _limitBuyTickets) { // if (ticket.Status.IsOpen () && !ticket.Status.IsFill ()) { // _sumOrdersYetToBeFilled += ticket.Quantity * ticket.LimitPrice; // } // } // } public override void OnOrderEvent(OrderEvent orderEvent) { // if (_immediateCancellations.Contains(orderEvent.OrderId)) // { // _immediateCancellations.Remove(orderEvent.OrderId); // Transactions.CancelOrder(orderEvent.OrderId); // } if (orderEvent.Status == OrderStatus.Filled) { Log("FILLED:: " + Transactions.GetOrderById(orderEvent.OrderId) + " FILL PRICE:: " + orderEvent.FillPrice.SmartRounding()); if (orderEvent.FillQuantity > 0) { Log("BUY >> " + orderEvent.FillPrice.SmartRounding()); Log("Cash left: " + Portfolio.Cash); var stopPrice = Securities[orderEvent.Symbol].Low * _stopLimit; var limitPrice = Securities[orderEvent.Symbol].Low - _limitVal; var newStopTicket = StopLimitOrder(orderEvent.Symbol, Quantity[orderEvent.Symbol], stopPrice,limitPrice); _stopTickets.Add (newStopTicket); _sumOrdersYetToBeFilled -= orderEvent.FillPrice * orderEvent.FillQuantity; // UpdateTemporaryCashBalance (); if (Portfolio.Cash < _minCash) { foreach (var buyTicket in _limitBuyTickets) { if (buyTicket.Status.IsOpen () && !buyTicket.Status.IsFill ()) { buyTicket.Cancel (); } } } } else { Log("SELL >> " + orderEvent.FillPrice.SmartRounding()); Log("Cash left: " + Portfolio.Cash); RemoveStop(orderEvent.Symbol); } } else { Log(orderEvent.ToString()); //Log("TICKET:: " + _tickets.Last()); } } } }