Overall Statistics |
Total Trades 413 Average Win 1.32% Average Loss -0.60% Compounding Annual Return 2.956% Drawdown 25.900% Expectancy 0.235 Net Profit 22.631% Sharpe Ratio 0.28 Loss Rate 61% Win Rate 39% Profit-Loss Ratio 2.19 Alpha 0.014 Beta 0.237 Annual Standard Deviation 0.105 Annual Variance 0.011 Information Ratio -0.195 Tracking Error 0.181 Treynor Ratio 0.124 Total Fees $979.08 |
/* * 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 QuantConnect.Data; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Indicators; using QuantConnect.Data.Custom; using QuantConnect.Orders; using QuantConnect.Securities; using QuantConnect.Data.Consolidators; namespace QuantConnect.Algorithm.CSharp { /* * Paul McKinney: CSV Guided Portfolio Balance * */ public class BasicTemplateAlgorithm : QCAlgorithm { // algorithm settings // default history & universe resolution const Resolution _algo_resolution = Resolution.Daily; // setup the algorithm indicators public const int _longMovingAverage = 120; public const int _shortMovingAverage = 10; // portfolio handling variables public const decimal _startingcash = 200000m; public const decimal _minimumcash = 200m; public const decimal _positionweight = 0.086m; // setup buy and sell limit ratios public const decimal _minpricedrop = 0.99m; public const decimal _maxpricerise = 1.01m; // backtesting ranges public DateTime _startdate = new DateTime(2007, 11, 23); public DateTime _enddate = new DateTime(2014, 11, 23); // public DateTime _startdate = DateTime.Now.Date.AddDays(-60); // public DateTime _enddate = DateTime.Now.Date.AddDays(-0); // end algorithm settings // setup dictionary to hold indicators for all securities private readonly ConcurrentDictionary<Symbol, SelectionData> _keyindicators = new ConcurrentDictionary<Symbol, SelectionData>(); // we don't want to over order for the amount of cash we have, so track orders in a day private decimal _todaysorders = 0m; // messages defaults for email notifications private string _notification_message = ""; private string _notification_email = "myemail@myaddress.com"; private string _notification_subject = "QuantConnect Algo Message"; // keeps indicators updated for each symbol private class SelectionData { // moving averages and momentum tracking public ExponentialMovingAverage LongMA; public ExponentialMovingAverage ShortMA; private decimal _momentum; public decimal Momentum { get { return _momentum ; } } public decimal longMA { get { return LongMA.Current.Value; } } public decimal shortMA { get { return ShortMA.Current.Value; } } // constructure for dictionary item that sets up indicators public SelectionData() { LongMA = new ExponentialMovingAverage(_longMovingAverage); ShortMA = new ExponentialMovingAverage(_shortMovingAverage); } // this update must be called for each value to be fed through all your indicators // you want to be careful not over call Update or go backwards in time and call it with // a previous date. // it get's very trick with live enviroments because events are always being fired through OnData methods public bool Update(TradeBar tbar) { // if enough date has been passed to an indicator to perform calculations it is ready // each indicator will differ depending upon the amount of history you are calculating across bool isreadyshort = false; bool isreadylong = false; // call all the base indicators being tracked isreadyshort = ShortMA.Update(tbar.Time, tbar.Close); isreadylong = LongMA.Update(tbar.Time, tbar.Close); // if the other indicators are ready then I can calculate momentum if (isreadylong && isreadyshort && LongMA.Current.Value > 0m) { _momentum = ShortMA.Current.Value / LongMA.Current.Value; } else { _momentum = 0; } return isreadyshort && isreadylong; } } // Initialize the data and resolution you require for your strategy: // this method is call automatically every day. I belive it is called at midnight public override void Initialize() { // for backtesting setup the date range SetStartDate(_startdate); SetEndDate(_enddate); // don't do any margin stuff SetBrokerageModel(Brokerages.BrokerageName.InteractiveBrokersBrokerage, AccountType.Cash); //Cash allocation SetCash(_startingcash); UniverseSettings.Resolution = _algo_resolution; AddSecurity(SecurityType.Equity, "IJH", _algo_resolution); // iShares Core S&P Mid-Cap AddSecurity(SecurityType.Equity, "IJR", _algo_resolution); // iShares Core S&P Small-Cap AddSecurity(SecurityType.Equity, "JKD", _algo_resolution); // iShares Morningstar Larg-Cap AddSecurity(SecurityType.Equity, "IXG", _algo_resolution); // iShares Global Financials AddSecurity(SecurityType.Equity, "IWO", _algo_resolution); // iShares Russell 2000 Growth Index AddSecurity(SecurityType.Equity, "FREL", _algo_resolution); // Fidelity® MSCI Real Estate ETF AddSecurity(SecurityType.Equity, "GLD", _algo_resolution); // Gold // Schedule an event to fire after market open // I only want to buy and sell once a day right after the market opens // I'm not sure if I'll have issue with this on holidays. I haven't tested that yet Schedule.On(DateRules.Every(DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday), TimeRules.At(9, 35), () => { Log("PerformTrades: Fired at: " + Time.ToString("D")); PerformTrades(); }); } // lets look through our stock that we own and see if we are ready to sell any of them void SellLoosers() { foreach (QuantConnect.Symbol holding in Portfolio.Keys) { if (Portfolio[holding].HoldStock) // sell only if we hold it, I belive I've seen instances where a stock has 0 shares in Portfolio { // pull up the stock from our dictionary SelectionData sd = _keyindicators[holding]; // if it is moving downward put in an order to sell if ( sd.shortMA < sd.longMA ) { // using our limit ratio calculate an order for the day to sell the stock // if for some reason it jumps too low and never comes back up it will get cancelled limiting our losses LimitOrder(holding, -Portfolio[holding].Quantity, Securities[holding].Price * _minpricedrop); // this line builds up the email message so we can check our email at work and discover our trades ;) _notification_message += string.Format(" Sell {0} Quantity {1} at {2:C3}\r", holding.Value, Portfolio[holding].Quantity, Portfolio[holding].Price); } } } } // go through a list of securities and look what to buy void ReallocateClassification(ref IEnumerable<Security> securitylist) { // calculate how much of an investment to make for any position // I don't want to buy any more than _positionweight of a stock // if it grows or shrinks, I don't adjust the position I hold decimal positionsize = _positionweight * (Portfolio.TotalHoldingsValue + Portfolio.Cash); foreach (Security sec in securitylist) { // pickup the current indicators from the dicitonary // only look to buy a stock if we don't already hold it SelectionData sd = _keyindicators[sec.Symbol]; if (!Portfolio[sec.Symbol].HoldStock) { if ( sd.shortMA > sd.longMA ) { // based on how much cash in the porfolio subtrack any open or filled orders for the day // If Portfolio.Cash gets updated, I might miss an order because I don't subtract filled orders form _todaysorders // I also subtrack a _minimumcahs to make sure I keep a little in the account if (Portfolio.Cash - _minimumcash - _todaysorders > positionsize) { // based on position size figure out how many orders to buy // then add the order dollar amount to _todaysorders int shares = (int)(positionsize / sec.Price); _todaysorders += positionsize; // put in the order setting up the limit based on the ratio LimitOrder(sec.Symbol, shares, sec.Price * _maxpricerise); // add more order information to our email message _notification_message += string.Format(" Buy {0} at {1:C3}\r", sec.Symbol.Value, sec.Price); } } } } } public void PerformTrades() { // these types of debug messages can be annoying in backtesting // but it's very helpful when you are trying to figure out what is happening // during live trading Debug(" Checking for buy and sell..."); // here is where I reset my email message information // I only want 1 email a day to tell me what I ordered _notification_subject = string.Format("QuantConnect Report: {0} ", Time.ToString("D")); _notification_message = ""; // reset the amount spent on today's order _todaysorders = 0.0m; // go check what to sell SellLoosers(); // when searching for what to buy I want a list that prioritizes by momentum // the query below will include information found in the indicators dictionary IEnumerable<Security> securitylist = from s in Securities.Values let mpcs = _keyindicators.GetOrAdd(s.Symbol, sym => new SelectionData()) where mpcs.Momentum > 0.0m orderby mpcs.Momentum descending select s; // using the sorted list of stock trade bars go see what to buy AND DON'T SPENT TOO MUCH MONEY // ... I'm not made out of margin you know!!! ReallocateClassification(ref securitylist); // if orders were placed then send the email message that was constructed if (_notification_message != "") { _notification_message = "\rThe Following Orders Were Created: \r\r" + _notification_message; Notify.Email(_notification_email, _notification_subject, _notification_message); Debug(_notification_message); } } // this is probably the most challanging event method for me to understand // it get's called for a variaty of reasons, but I'm not 100% sure I understand all of them // when I do get an event I want to make sure I know what the event is for and I process it correctly // right now I care about 2 things the daily update to my indicators which fires at about midnight // the second is when anything happens to one of the stocks I'm tracking public override void OnData(Slice slice) { // update all of the indicators foreach (TradeBar s in slice.Bars.Values) { // I only want to have daily indicators if (Securities[s.Symbol].Resolution == Resolution.Daily) { SelectionData sc; // look for the key in the dictionary and update it's indicators sc = _keyindicators.GetOrAdd(s.Symbol, sym => new SelectionData()); sc.Update(s); } } // if anything happened to the stocks I'm following string stock_event = ""; if (slice.Splits.Count > 0) foreach (Split sp in slice.Splits.Values) stock_event += string.Format("Date: {0} Stock: {1} Split: {2:N3} At: {3:C3} \r", sp.Time.ToString("d"), sp.Symbol.Value, sp.SplitFactor, sp.Price); if (slice.Delistings.Count > 0) foreach (Delisting dl in slice.Delistings.Values) stock_event += string.Format("Date: {0} Stock: {1} Delisted At: {2:C3} \r", dl.Time.ToString("d"), dl.Symbol.Value, dl.Price); if (slice.Dividends.Count > 0) foreach (Dividend dv in slice.Dividends.Values) stock_event += string.Format("Date: {0} Stock: {1} Dividend: {2:C3} \r", dv.Time.ToString("d"), dv.Symbol.Value, dv.Value); if (slice.SymbolChangedEvents.Count > 0) foreach (SymbolChangedEvent sc in slice.SymbolChangedEvents.Values) stock_event += string.Format("Date: {0} Old Symbol: {1} New Symbol: {2} \r", sc.Time.ToString("d"), sc.OldSymbol, sc.NewSymbol); if (stock_event != "") { if (LiveMode) { stock_event = "Stock Event(s) \r" + stock_event; Notify.Email(_notification_email, _notification_subject, stock_event); } } } // each day the initialize function is called // this method responds to the securities added or removed from the list we are keeping indicators for // when you start the algorithm, this method will bring all the indicators up todate public override void OnSecuritiesChanged(SecurityChanges changes) { // each time our securities change we'll be notified here // if a security was removed, take it from our dictionary // only if we don't hold it in the portfolio foreach (var sec in changes.RemovedSecurities) { // if we don't hold it then remove it from the list // if it is not in our list of desired securities if (!Portfolio[sec.Symbol].HoldStock) { SelectionData sc; _keyindicators.TryRemove(sec.Symbol, out sc); } } // add the new securities to our list // make sure we get all the history with our default resolution // go through all the indicators and upate them with the history data foreach (var sec in changes.AddedSecurities) { SelectionData sc; sc = _keyindicators.GetOrAdd(sec.Symbol, sym => new SelectionData()); IEnumerable<TradeBar> history = History(sec.Symbol, _longMovingAverage, _algo_resolution); foreach (TradeBar tb in history) { sc.Update(tb); } } } // I like to get an email when my orders are filled, cancelled, or partially filled public override void OnOrderEvent(OrderEvent orderEvent) { string order_message = string.Format("Order: {0} {1} Sym: {2} Price: {3:C3} Qty: {4} Fee: {5:C3}, {6:C3}", orderEvent.UtcTime.ToString("F"), orderEvent.Status, orderEvent.Symbol.Value, orderEvent.FillPrice, orderEvent.FillQuantity, orderEvent.OrderFee, orderEvent.Message); Notify.Email(_notification_email, _notification_subject, order_message); } } }