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