| Overall Statistics |
|
Total Trades 95 Average Win 7.29% Average Loss -4.38% Compounding Annual Return 6.180% Drawdown 39.200% Expectancy 0.417 Net Profit 89.483% Sharpe Ratio 0.346 Loss Rate 47% Win Rate 53% Profit-Loss Ratio 1.66 Alpha 0.068 Beta 0.016 Annual Standard Deviation 0.199 Annual Variance 0.039 Information Ratio 0.04 Tracking Error 0.266 Treynor Ratio 4.364 Total Fees $247.39 |
namespace QuantConnect.Rotation
{
using QuantConnect.Algorithm.CSharp;
/*
* QuantConnect University - Global Rotation by Michael Handschuh
*
* From a list of ETF's which look at the global markets; always select the
* best performing ETF assuming its momentum will continue.
*
* Symbols are ranked by an objective function.
*
*/
public class QCUGlobalRotation : QCAlgorithm
{
// we'll use this to tell us when the month has ended
DateTime LastRotationTime = DateTime.MinValue;
TimeSpan RotationInterval = TimeSpan.FromDays(30);
// these are the growth symbols we'll rotate through
List<string> GrowthSymbols = new List<string>
{
"MDY", // US S&P mid cap 400
"IEV", // iShares S&P europe 350
"EEM", // iShared MSCI emerging markets
"ILF", // iShares S&P latin america
"EPP" // iShared MSCI Pacific ex-Japan
};
// these are the safety symbols we go to when things are looking bad for growth
List<string> SafetySymbols = new List<string>
{
"EDV", // Vangaurd TSY 25yr+
"SHY" // Barclays Low Duration TSY
};
// we'll hold some computed data in these guys
List<SymbolData> SymbolData = new List<SymbolData>();
public override void Initialize()
{
Profile.Enabled = true;
SetCash(25000);
SetStartDate(2007, 1, 1);
foreach (var symbol in GrowthSymbols.Union(SafetySymbols))
{
// ideally we would use daily data
AddSecurity(SecurityType.Equity, symbol, Resolution.Minute);
var oneMonthPerformance = MOM(symbol, 30, Resolution.Daily);
var threeMonthPerformance = MOM(symbol, 90, Resolution.Daily);
SymbolData.Add(new SymbolData
{
Symbol = symbol,
OneMonthPerformance = oneMonthPerformance,
ThreeMonthPerformance = threeMonthPerformance
});
}
}
public override void OnEndOfAlgorithm()
{
Profile.PrintReport(this);
}
private void SetPositions(SymbolData bestGrowth)
{
Profile.Begin("SetPositions");
try
{
if (bestGrowth.ObjectiveScore > 0)
{
if (Portfolio[bestGrowth.Symbol].Quantity == 0)
{
Log("PREBUY>>LIQUIDATE>>");
Liquidate();
}
Log(">>BUY>>" + bestGrowth.Symbol + "@" + (100 * bestGrowth.OneMonthPerformance).ToString("00.00"));
decimal qty = Portfolio.Cash / Securities[bestGrowth.Symbol].Close;
MarketOrder(bestGrowth.Symbol, (int)qty);
}
else
{
// if no one has a good objective score then let's hold cash this month to be safe
Log(">>LIQUIDATE>>CASH");
Liquidate();
}
}
finally
{
Profile.End("SetPositions");
}
}
private bool first = true;
public void OnData(TradeBars data)
{
Profile.Begin("OnData");
try
{
// the first time we come through here we'll need to do some things such as allocation
// and initializing our symbol data
if (first)
{
first = false;
LastRotationTime = data.Time;
return;
}
var delta = data.Time.Subtract(LastRotationTime);
if (delta > RotationInterval)
{
LastRotationTime = data.Time;
// pick which one is best from growth and safety symbols
var orderedObjScores = SymbolData.OrderByDescending(x => x.ObjectiveScore).ToList();
foreach (var orderedObjScore in orderedObjScores)
{
Log(">>SCORE>>" + orderedObjScore.Symbol + ">>" + orderedObjScore.ObjectiveScore);
}
var bestGrowth = orderedObjScores.First();
SetPositions(bestGrowth);
}
}
catch (Exception ex)
{
Error("OnTradeBar: " + ex.Message + "\r\n\r\n" + ex.StackTrace);
}
finally
{
Profile.End("OnData");
}
}
}
class SymbolData
{
public string Symbol;
public Momentum OneMonthPerformance { get; set; }
public Momentum ThreeMonthPerformance { get; set; }
public decimal ObjectiveScore
{
get
{
Profile.Begin("ObjectiveScore");
try
{
// we weight the one month performance higher
decimal weight1 = 100;
decimal weight2 = 75;
return (weight1 * OneMonthPerformance + weight2 * ThreeMonthPerformance) / (weight1 + weight2);
}
finally
{
Profile.End("ObjectiveScore");
}
}
}
}
}using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// Instance version of Profiler.
/// Starts enabled by default.
/// </summary>
public sealed class Profiler : IDisposable
{
private readonly object _myLock = new object();
private struct MethodCall
{
public long StartTime;
public CallNode Node;
}
private class CallNode
{
public string Name;
public long TotalTime;
public long TotalCalls;
public CallNode Parent;
public readonly List<CallNode> Children = new List<CallNode>();
public long TotalTimeSelf
{
get { return TotalTime - Children.Sum(x => x.TotalTime); }
}
public List<CallNode> Siblings
{
get { return Parent != null ? Parent.Children : null; }
}
public double GetLocalInclusiveTimeFraction()
{
if (Parent == null)
return 1;
var totalCallTime = Parent.TotalTime;
if (totalCallTime == 0)
{
if (Parent.Parent != null)
return 0;
totalCallTime = Siblings.Sum(x => x.TotalTime);
if (totalCallTime == 0)
return 0;
}
var fraction = (double)TotalTime / totalCallTime;
return fraction;
}
public double GetInclusiveTimeFraction()
{
var fraction = GetLocalInclusiveTimeFraction();
var parent = Parent;
while (parent != null)
{
fraction *= parent.GetLocalInclusiveTimeFraction();
parent = parent.Parent;
}
return fraction;
}
public double GetLocalExclusiveTimeFraction()
{
if (Parent == null)
return 0;
var totalCallTime = Parent.TotalTime;
if (totalCallTime == 0)
{
if (Parent.Parent != null)
return 0;
totalCallTime = Siblings.Sum(x => x.TotalTime);
if (totalCallTime == 0)
return 0;
}
var fraction = (double)TotalTimeSelf / totalCallTime;
return fraction;
}
public double GetExclusiveTimeFraction()
{
var fraction = GetLocalExclusiveTimeFraction();
var parent = Parent;
while (parent != null)
{
fraction *= parent.GetLocalInclusiveTimeFraction();
parent = parent.Parent;
}
return fraction;
}
}
private readonly Stopwatch _stopwatch = new Stopwatch();
private readonly ThreadLocal<Stack<MethodCall>> _callstack = new ThreadLocal<Stack<MethodCall>>();
private readonly CallNode _root = new CallNode()
{
Name = "_root",
};
/// <summary>
/// Not thread safe, enable/disable only when you know single thread is using!
/// </summary>
public bool Enabled { get; set; }
public bool IsOpen()
{
lock (_myLock)
{
CheckCallstack();
return _callstack.Value.Count > 0;
}
}
public Profiler()
{
Enabled = true;
_stopwatch.Start();
}
private void CheckCallstack()
{
if (!_callstack.IsValueCreated)
_callstack.Value = new Stack<MethodCall>();
}
public void Begin(string method)
{
if (!Enabled)
return;
if (method == null || method == "")
throw new ArgumentException("method");
lock (_myLock)
{
CheckCallstack();
var stack = _callstack.Value;
var parent = stack.Count == 0 ? _root : stack.Peek().Node;
var node = parent.Children.Find(x => x.Name == method);
if (node == null)
{
node = new CallNode()
{
Name = method,
Parent = parent,
};
parent.Children.Add(node);
}
node.TotalCalls += 1;
var call = new MethodCall()
{
Node = node,
StartTime = _stopwatch.ElapsedTicks,
};
stack.Push(call);
}
}
public void End(string method = null)
{
if (!Enabled)
return;
lock (_myLock)
{
CheckCallstack();
var stack = _callstack.Value;
if (stack.Count == 0)
throw new InvalidOperationException("No corresponding entry call!");
var call = stack.Pop();
if (method != null && method != call.Node.Name)
{
throw new InvalidOperationException("Expected end of " + method + ", entry was as " + call.Node.Name);
}
var elapsedMs = _stopwatch.ElapsedTicks - call.StartTime;
call.Node.TotalTime += elapsedMs;
}
}
private void PrintReport(Action<string> lineEmitter, CallNode node, string indent)
{
var line = indent + node.Name + ": "
+ node.GetInclusiveTimeFraction().ToString("P2")
+ " ("
+ node.GetExclusiveTimeFraction().ToString("P2")
+ ")";
lineEmitter(line);
indent += " ";
foreach (var child in node.Children)
{
PrintReport(lineEmitter, child, indent);
}
}
public void PrintReport(Action<string> lineEmitter)
{
lock (_myLock)
{
lineEmitter("Stopwatch at " + _stopwatch.ElapsedMilliseconds + " ms and " + _stopwatch.ElapsedTicks + " ticks");
foreach (var node in _root.Children)
{
PrintReport(lineEmitter, node, "");
}
}
}
public void PrintReport(QCAlgorithm algo)
{
PrintReport(x => algo.Log(x));
}
public void Dispose()
{
_callstack.Dispose();
}
}
/// <summary>
/// Static single instance version of Profiler.
/// Starts disabled by default, set Enabled to true to use.
/// </summary>
public static class Profile
{
private static Profiler _profiler = new Profiler()
{
Enabled = false
};
/// <summary>
/// Not thread safe, enable/disable only when you know single thread is using!
/// </summary>
public static bool Enabled
{
get { return _profiler.Enabled; }
set { _profiler.Enabled = value; }
}
public static bool IsOpen()
{
return _profiler.IsOpen();
}
/// <summary>
/// Not thread safe, reset only when you know single thread is using!
/// </summary>
public static void Reset()
{
if (IsOpen())
throw new InvalidOperationException("Trying to reset while profiler callstack is open!");
bool enabled = _profiler.Enabled;
_profiler.Dispose();
_profiler = new Profiler();
_profiler.Enabled = enabled;
}
public static void Begin(string method)
{
_profiler.Begin(method);
}
public static void End(string method = null)
{
_profiler.End(method);
}
public static void PrintReport(Action<string> lineEmitter)
{
_profiler.PrintReport(lineEmitter);
}
public static void PrintReport(QCAlgorithm algo)
{
_profiler.PrintReport(algo);
}
}
}