| 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);
}
}
}