Overall Statistics |
Total Trades 1143 Average Win 3.60% Average Loss -3.65% Compounding Annual Return -6.237% Drawdown 98.200% Expectancy 0.042 Net Profit -36.316% Sharpe Ratio 0.464 Loss Rate 48% Win Rate 52% Profit-Loss Ratio 0.99 Alpha -0.576 Beta 51.005 Annual Standard Deviation 0.96 Annual Variance 0.922 Information Ratio 0.443 Tracking Error 0.96 Treynor Ratio 0.009 Total Fees $1419.49 |
/* Wenbo Zhnag 9/17/17 on https://www.quantopian.com/posts/ballistic-xiv-slash-vxx-my-best-long-xiv-slash-vxx-strategy-to-date Also read https://www.quantopian.com/posts/ballistic-xiv-slash-vxx-my-best-long-xiv-slash-vxx-strategy-to-date */ namespace QuantConnect { public partial class TestConsolidatedIndicator : QCAlgorithm { //Editable parameters int context_xivTradePercent = 100; int context_tltTradePercent = 100; int context_vxxTradePercent = 100; decimal context_StopLossPct = 0.25M; //Used for logging decimal context_StopPrice = 0; decimal context_BuyPrice = 0; decimal context_SellLossPrice = 0; decimal context_SellProfitPrice = 0; bool context_last_bar = false; bool context_sell = false; public bool context_buy = false; bool context_buyVXX = false; // FR#2: Delayed buy; // context.buyTLT = False; // FR#4: Improve buy/sell condition decimal context_rsi_last = -1; decimal context_newsl = 0; decimal rsi_current = 0 ; decimal rsi_previous = 0; public void BarDetails() { // return String.Format("{0} - {1}", pBar.Time, pBar.Price); if (Data["XIV"].Bars.Count > 0) { var bar = Data["XIV"].Bars[0]; LogIt(String.Format("{0} - {1}", bar.Time, bar.Price)); } if (Data["XIV"].Bars.Count > 1) { var bar = Data["XIV"].Bars[1]; LogIt(String.Format("{0} - {1}", bar.Time, bar.Price)); } //return ; } public void bar_open() { var vMethodName = "bar_open"; LogMethodStart(vMethodName); BarDetails(); //return; if ((context_buy == true) && (Transactions.GetOpenOrders().Count == 0)) { set_fixed_stop(); // TODO: Turm the below two If statements into a loop from dictionary, and the list is where symbol in PositionType.Overnight, PositionType.Base // Also verify that after Liquidate the there are no more positions held. // #FR#2: Delayed buy if (Portfolio[Symbols[PositionType.Overnight]].Quantity > 0) { Liquidate(Symbols[PositionType.Overnight]); LogIt("Sell " + Symbols[PositionType.Overnight]); } if (Portfolio[Symbols[PositionType.Base]].Quantity > 0) { Liquidate(Symbols[PositionType.Base]); LogIt("Sell " + Symbols[PositionType.Base] + " at " + Price(Symbols[PositionType.Base])); } // Fill a market order immediately (before moving to next line of code) var newTicket = MarketOrder(Symbols[PositionType.Inverse], context_xivTradePercent); LogIt("Buy XIV at " + context_BuyPrice); context_buy = false; context_sell = false; context_buyVXX = false; context_newsl = 0; } if ( (context_buyVXX == true) && (Portfolio[Symbols[PositionType.Base]].Quantity == 0) && (Transactions.GetOpenOrders().Count == 0) ) { var newTicket = MarketOrder(Symbols[PositionType.Base], context_vxxTradePercent); LogIt("Buy VXX at " + Price(Symbols[PositionType.Base])); context_buyVXX = false; } if ((context_sell == true) && (Portfolio[Symbols[PositionType.Inverse]].Quantity > 0)) { Liquidate(Symbols[PositionType.Inverse]); LogIt("Sell " + Symbols[PositionType.Inverse] + " at " + Price(Symbols[PositionType.Inverse])); context_sell = false; context_buy = false; } LogMethodEnd(vMethodName); } public void my_rebalance() { var vMethodName = "my_rebalance"; LogMethodStart(vMethodName); // Get RSI for every 2 hour period for the last 10 periods. rsi_current = Data[Symbols[PositionType.Inverse]].RSI_History[0].Value; rsi_previous = rsi_current; if (Data[Symbols[PositionType.Inverse]].RSI_History.Count > 1) { rsi_previous = Data[Symbols[PositionType.Inverse]].RSI_History[1].Value; if (context_rsi_last == -1) { context_rsi_last = Data[Symbols[PositionType.Inverse]].RSI_History[1].Value; } } var vxx_price = Price(Symbols[PositionType.Base]); var xiv_price = Price(Symbols[PositionType.Inverse]); var message = String.Format("Current XIV price {0:0.00}, VXX price {1:0.00} @ RSI_2 level {2}, previous {3}, last stored {4}" , xiv_price, vxx_price, rsi_current, rsi_previous, context_rsi_last); LogIt(message); //BUY RULE if ((context_buy == false) && (context_rsi_last < 70) && (rsi_current >= 70) && (Portfolio[Symbols[PositionType.Base]].Quantity == 0)) { context_buy = true; } //SELL RULE if ((context_rsi_last > 85) && (rsi_current <= 85) && (HaveNoHoldingsIn(PositionType.Inverse)) && IsTransactionDone()) { Liquidate(Symbols[PositionType.Inverse]); LogIt(String.Format("Sell {0} at {1:0.00}", Symbols[PositionType.Inverse],xiv_price) ); context_buy = false; } if ((context_buyVXX == false) && (rsi_current <= 30) && (HaveNoHoldingsIn(PositionType.Base))) { context_buyVXX = true; } //Buy VXX rule if ((context_buyVXX == false) && (context_rsi_last > 30) && (rsi_current <= 30) && (HaveNoHoldingsIn(PositionType.Base)) && (HaveNoHoldingsIn(PositionType.Inverse)) ) { context_buyVXX = true; } // Sell VXX rule if ((context_rsi_last < 15) && (rsi_current >= 15) && ( !HaveNoHoldingsIn(PositionType.Base))) { Liquidate(Symbols[PositionType.Base]); LogIt(String.Format("Sell {0} at {1:0.00}", Symbols[PositionType.Base], vxx_price) ); context_buyVXX = false; } // panic // if (Time.Hour > 12) { var high = -1M; var long_history = History(Symbols[PositionType.Inverse], 119, Resolution.Minute ); // var long_history = History(Symbols[PositionType.Inverse], TimeSpan.FromMinutes(119), Resolution.Minute ); // LogIt(long_history.Count()); if (long_history.Count() > 0) { high = long_history.Max(element => (element.High)); // 10M ; } /* TODO: Note this is the current implementation of context.last_bar. Will need to understand more before I figure this out properly */ if (Time.Hour >= 16) { high = History(Symbols[PositionType.Inverse], 29, Resolution.Minute).Max(element => (element.High)); } // TODO: I think the logic is, if current price is less than 10% of high --Check and confirm if (((high/xiv_price) - 1) > (decimal).1) { Liquidate(Symbols[PositionType.Inverse]); LogIt(String.Format("Panic Sell {0} at {1:0.00}", Symbols[PositionType.Inverse], xiv_price) ); context_sell = false; context_buy = false; } } context_rsi_last = rsi_current; LogMethodEnd(vMethodName); } public void set_fixed_stop() { var vMethodName = "set_fixed_stop"; LogMethodStart(vMethodName); // Only call this once when the stock is bought var price = Price(Symbols[PositionType.Inverse]); // data.current(Symbols[PositionType.Inverse], 'price'); // Quantopian code context_BuyPrice = price; context_SellLossPrice = Math.Max(context_StopPrice, price - (context_StopLossPct * price)); LogMethodEnd(vMethodName); } public void handle_data() { var vxx_price = Price(Symbols[PositionType.Base]); var xiv_price = Price(Symbols[PositionType.Inverse]); if (context_BuyPrice > 0) { if ((HaveHoldingsIn(PositionType.Inverse)) ) { // set break even if (((xiv_price / context_BuyPrice) >= (decimal)1.1) && ( context_BuyPrice > context_SellLossPrice)) { context_SellLossPrice = context_BuyPrice; LogIt(String.Format("New {0} stop loss price {1:0.00}", Symbols[PositionType.Inverse], context_SellLossPrice)); } } // 50% trailing stop // FR#1: Add profit take 50% if (((xiv_price / context_BuyPrice) >= (decimal)1.5) && ( context_BuyPrice > context_SellLossPrice)) { context_SellLossPrice = xiv_price; LogIt(String.Format("{0} take HUGE profit at {1:0.00}", Symbols[PositionType.Inverse], context_SellLossPrice)); } } // If we have a position check sell conditions if ((xiv_price <= context_SellLossPrice) && IsTransactionDone()) { Liquidate(Symbols[PositionType.Inverse]); LogIt(String.Format("Stop loss sell {0} at {1:0.00}", Symbols[PositionType.Inverse], xiv_price)); context_sell = false; context_buy = false; } } // TODO: Link OnData to Handle_Data and then testS } }
namespace QuantConnect { /// <summary> /// Cloned from https://github.com/QuantConnect/Lean/blob/master/Algorithm.CSharp/MultipleSymbolConsolidationAlgorithm.cs /// </summary> /* Things to do: 1. Add two or more symbols to be looked at. 2. Get the last 10 prices for each 2 hour period. This could be over multiple days, ignore weekend and holidays. 3. Warmup does not seem to be working 4. In the documentation I saw a line for RegisterIndicator. Is this not required? 5. How would I used Consolidators with long and short SMA. 6. How would I used Consolidators withe multiple indicators - SMA, RSI - Long and short for each 7. The "Bar" has two values Time and EndTime. The data provided is for which time? 8. Could the SMA be defined with symbolData.SMA = SMA(symbolData.Symbol, SimpleMovingAveragePeriod, ResolutionPeriod); 9. */ /* Errors: */ public partial class TestConsolidatedIndicator : QCAlgorithm { public struct PositionType { private PositionType(string value) { Value = value; } public string Value { get; set; } public static PositionType Base { get { return new PositionType("Base"); } } public static PositionType Inverse { get { return new PositionType("Inverse"); } } public static PositionType Overnight { get { return new PositionType("Overnight"); } } } private const int BarPeriodValue = 1; private const int MINUTES_IN_HOUR = 60; private const string SPY = "SPY"; /// <summary> /// This is the period of bars we'll be creating /// </summary> public readonly Resolution ResolutionPeriod = Resolution.Minute; /// <summary> /// This is the period of our sma SLOW indicators /// </summary> public readonly int SimpleMovingAveragePeriod_Small = 2; /// <summary> /// This is the period of our sma FAST indicators /// </summary> public readonly int SimpleMovingAveragePeriod_Large = 5; /// <summary> /// This is the number of consolidated bars we'll hold in symbol data for reference /// </summary> public readonly int RollingWindowSize = 5; /// <summary> /// Holds all of our data keyed by each symbol /// </summary> public readonly Dictionary<string, ConsolidatedIndicator> Data = new Dictionary<string, ConsolidatedIndicator>(); /// <summary> /// Contains all of our equity symbols /// </summary> public readonly Dictionary<PositionType, string> Symbols = new Dictionary<PositionType, string> { {PositionType.Base, "VXX" }, {PositionType.Inverse, "XIV" }, {PositionType.Overnight, "TLT" } }; /// <summary> /// This is the period of bars we'll be creating /// </summary> public TimeSpan GetBarPeriod() { switch (ResolutionPeriod) { case Resolution.Minute: return TimeSpan.FromMinutes(BarPeriodValue); case Resolution.Hour: return TimeSpan.FromHours(BarPeriodValue); case Resolution.Daily: return TimeSpan.FromDays(BarPeriodValue); default: throw new ArgumentOutOfRangeException(); } } /// <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() { SetStartDate(2011, 01, 5); // Set Start Date SetEndDate(2018, 1, 5); // Set End Date SetCash(100000); // Set Strategy Cash //SetWarmUp(RollingWindowSize + 2); // Happens after Initalize is done. AddEquity(SPY, ResolutionPeriod); foreach(var entry in Symbols) { var symbol = entry.Value; var equity = AddEquity(symbol, ResolutionPeriod); Debug(symbol); Data.Add(symbol, new ConsolidatedIndicator(equity.Symbol, GetBarPeriod(), RollingWindowSize)); } ScheduleMethods(); // loop through all our symbols and request data subscriptions and initialize indicators foreach (var kvp in Data) { // this is required since we're using closures below, for more information // see: http://stackoverflow.com/questions/14907987/access-to-foreach-variable-in-closure-warning var symbolData = kvp.Value; // define a consolidator to consolidate data for this symbol on the requested period var consolidator = symbolData.Symbol.SecurityType == SecurityType.Equity ? (IDataConsolidator)new TradeBarConsolidator(GetBarPeriod()) : (IDataConsolidator)new QuoteBarConsolidator(GetBarPeriod()); // define our indicator // symbolData.SMA_Small = new SimpleMovingAverage(CreateIndicatorName(symbolData.Symbol, "SMA" + SimpleMovingAveragePeriod_Small, ResolutionPeriod), SimpleMovingAveragePeriod_Small); // symbolData.SMA_Large = new SimpleMovingAverage(CreateIndicatorName(symbolData.Symbol, "SMA" + SimpleMovingAveragePeriod_Large, ResolutionPeriod), SimpleMovingAveragePeriod_Large); symbolData.RSI = new RelativeStrengthIndex(CreateIndicatorName(symbolData.Symbol, "RSI" + SimpleMovingAveragePeriod_Small, ResolutionPeriod), SimpleMovingAveragePeriod_Small, MovingAverageType.Simple); // wire up our consolidator to update the indicator consolidator.DataConsolidated += (sender, baseData) => { IReadOnlyList<string> TimesToCheck = new List<string>{ "9:39", "11:29", "1:29", "3:29", "4:00" }; if (!TimesToCheck.Contains(Time.ToString("h:mm"))) { return; } // 'bar' here is our newly consolidated data var bar = (IBaseDataBar)baseData; // LogIt(Time.ToString("u") + " " + bar); // we're also going to add this bar to our rolling window so we have access to it later symbolData.Bars.Add(bar); // update the indicator // symbolData.SMA_Small.Update(bar.Time, bar.Close); // symbolData.SMA_Large.Update(bar.Time, bar.Close); symbolData.RSI.Update(bar.Time, bar.Close); symbolData.RSI_History.Add(symbolData.RSI.Current); }; // consolidator.DataConsolidated += HandleConsolidatedData; // we need to add this consolidator so it gets auto updates SubscriptionManager.AddConsolidator(symbolData.Symbol, consolidator); } // Debug("Is the warmup done?"); } private void ScheduleMethods() { IReadOnlyList<int> MyRebalanceHours = new List<int>{ 2, 4, 6 }; foreach (var hour in MyRebalanceHours) { // LogIt(hour); Schedule.On(DateRules.EveryDay(SPY), TimeRules.AfterMarketOpen(SPY, (MINUTES_IN_HOUR * hour) + 2), my_rebalance); } Schedule.On(DateRules.EveryDay(SPY), TimeRules.AfterMarketOpen(SPY, 11), my_rebalance); Schedule.On(DateRules.EveryDay(SPY), TimeRules.BeforeMarketClose(SPY, 25), my_rebalance); IReadOnlyList<int> BarOpenHours = new List<int>{0, 2, 4, 6 }; foreach (var hour in BarOpenHours) { Schedule.On(DateRules.EveryDay(SPY), TimeRules.AfterMarketOpen(SPY, (MINUTES_IN_HOUR * hour) + 5), bar_open); } } public void scheduled_test() { Debug(Time.Hour + ":" + Time.Minute); } public void HandleConsolidatedData(object pSender, IBaseData pBaseData) { //if (IsWarmingUp) return; var vMethodName = "HandleConsolidatedData"; LogIt(vMethodName); //BarDetails(); if (Time.Hour >= 16) { // LogIt("End of Day: No more activity"); return; } // LogMethodStart(vMethodName); //SymbolDataDetails(vMethodName); foreach (var symbolData in Data.Values) { // this check proves that this symbol was JUST updated prior to this OnData function being called if (symbolData.IsReady && symbolData.WasJustUpdated(Time)) { var output = String.Format("{0} - RSI - Current: {1}; Last: {2}; Second Last: {3}", symbolData.Symbol, symbolData.RSI, symbolData.RSI_History[0].Value, symbolData.RSI_History[1].Value); LogIt(output); // LogMethodStart(vMethodName + " - " + symbolData.Symbol); /* LogIt("Current Period Value: " + symbolData.Bars[0].Value ); LogIt(symbolData.SMA_Small.ToString()); LogIt("SMA Small Current: " + symbolData.SMA_Small.Current ); LogIt("SMA Small Details: " + symbolData.SMA_Small.ToDetailedString()); LogIt(symbolData.SMA_Large.ToString()); LogIt("SMA Large Current: " + symbolData.SMA_Large.Current ); LogIt("SMA Large Details: " + symbolData.SMA_Large.ToDetailedString()); */ /* LogIt("RSI: " + symbolData.RSI); LogIt("RSI Current: " + symbolData.RSI_Lag[0]); LogIt("RSI Previous: " + symbolData.RSI_Lag[1]); */ /* foreach(var bar in symbolData.Bars) { var message = String.Format("{4} - Time: {0:ddd} {0} ; EndTime: {1:ddd} {1} ; Value: {2} ; Price: {3}", bar.Time, bar.EndTime, bar.Value, bar.Price, vMethodName); // Debug(bar.ToString()); LogIt(message); } */ } } } /// <summary> /// End of a trading day event handler. This method is called at the end of the algorithm day (or multiple times if trading multiple assets). /// </summary> /// <remarks>Method is called 10 minutes before closing to allow user to close out position.</remarks> public override void OnEndOfDay() { // LogMethodStart("OnEndOfDay"); int i = 0; // foreach (var kvp in Data.OrderBy(x => x.Value.Symbol)) foreach (var kvp in Data.Where(x => x.Value.Symbol == Symbols[PositionType.Inverse])) { // we have too many symbols to plot them all, so plot ever other if (kvp.Value.IsReady && ++i%2 == 0) { // Plot(kvp.Value.Symbol.ToString(), kvp.Value.SMA_Small); Plot(kvp.Value.Symbol.ToString(), kvp.Value.RSI); } } } private void SymbolDataDetails(string pMethodName) { // loop through each symbol in our structure foreach (var symbolData in Data.Values) { //if (symbolData.IsReady)// && symbolData.WasJustUpdated(Time)) { LogMethodStart(pMethodName + " - " + symbolData.Symbol); foreach(var bar in symbolData.Bars) { var message = String.Format("{4} - Time: {0:ddd} {0} ; EndTime: {1:ddd} {1} ; Value: {2} ; Price: {3}", bar.Time, bar.EndTime, bar.Value, bar.Price, pMethodName); // Debug(bar.ToString()); Debug(message); } } } } /// <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 override void OnData(Slice data) { // handle_data(); /* // loop through each symbol in our structure foreach (var symbolData in Data.Values) { // this check proves that this symbol was JUST updated prior to this OnData function being called if (symbolData.IsReady && symbolData.WasJustUpdated(Time)) { // If the position is currently held if((Portfolio[symbolData.Symbol].Quantity > 0)) { } } } */ } /* public bool AreSymbolsReady() { foreach (var symbolData in Data.Values) { yield return new System.Threading.WaitUntil(() => (symbolData.IsReady && symbolData.WasJustUpdated(Time))); // this check proves that this symbol was JUST updated prior to this OnData function being called // if (symbolData.IsReady && symbolData.WasJustUpdated(Time)) // { // } } return true; } */ } }
namespace QuantConnect { // // Make sure to change "BasicTemplateAlgorithm" to your algorithm class name, and that all // files use "public partial class" if you want to split up your algorithm namespace into multiple files. // //public partial class BasicTemplateAlgorithm : QCAlgorithm, IAlgorithm //{ // Extension functions can go here...(ones that need access to QCAlgorithm functions e.g. Debug, Log etc.) //} //public class Indicator //{ // ...or you can define whole new classes independent of the QuantConnect Context //} public partial class TestConsolidatedIndicator : QCAlgorithm, IAlgorithm { #region Log Method Start & End private void LogMethodStart(string pMethodName) { return; LogMethod(pMethodName); } private void LogMethodEnd(string pMethodName) { return; LogMethod(pMethodName, false); } private void LogMethod(string pMethodName, bool pIsStart = true) { var vState = pIsStart ? "Start" : "End"; var vMessage = String.Format("Method: {0} {1} - {2:MM/dd/yy H:mm:ss:fff}", pMethodName, vState, Time); LogIt(vMessage); } #endregion Log Method Start & End private void LogIt(int pMessage) { LogIt(pMessage.ToString()); } private void LogIt(string pMessage) { Debug(Time + " : " + pMessage); // Log(Time + " : " + pMessage); } private bool IsTransactionDone() { return (Transactions.GetOpenOrders().Count == 0); } private bool HaveNoHoldingsIn(PositionType pPositionType) { return !HaveHoldingsIn(pPositionType); } private bool HaveHoldingsIn(PositionType pPositionType) { return (Portfolio[Symbols[pPositionType]].Quantity > 0); } private decimal Price(String pSymbol) { return Securities[pSymbol].Price; // return Securities[pSymbol].GetLastData().Price; } } }
namespace QuantConnect { /// <summary> /// Contains data pertaining to a symbol in our algorithm /// </summary> public class ConsolidatedIndicator { /// <summary> /// This symbol the other data in this class is associated with /// </summary> public readonly Symbol Symbol; #region TradeBar properties /// <summary> /// A rolling window of data, data needs to be pumped into Bars by using Bars.Update( tradeBar ) and /// can be accessed like: /// mySymbolData.Bars[0] - most first recent piece of data /// mySymbolData.Bars[5] - the sixth most recent piece of data (zero based indexing) /// </summary> public readonly RollingWindow<IBaseDataBar> Bars; /// <summary> /// The period used when population the Bars rolling window. /// </summary> public readonly TimeSpan BarPeriod; #endregion TradeBar properties /// <summary> /// The simple moving average Small indicator for our symbol /// </summary> public SimpleMovingAverage SMA_Small; /// <summary> /// The simple moving average Large indicator for our symbol /// </summary> public SimpleMovingAverage SMA_Large; /// <summary> /// The Relative Strength Index indicator for our symbol /// </summary> public RelativeStrengthIndex RSI; public readonly RollingWindow<IndicatorDataPoint> RSI_History; // public readonly RollingWindow<RelativeStrengthIndex> RSI_History; /// <summary> /// Initializes a new instance of SymbolData /// </summary> public ConsolidatedIndicator(Symbol symbol, TimeSpan barPeriod, int windowSize) { Symbol = symbol; BarPeriod = barPeriod; Bars = new RollingWindow<IBaseDataBar>(windowSize); RSI_History = new RollingWindow<IndicatorDataPoint>(3); } /// <summary> /// Returns true if the most recent trade bar time matches the current time minus the bar's period, this /// indicates that update was just called on this instance /// </summary> /// <param name="current">The current algorithm time</param> /// <returns>True if this instance was just updated with new data, false otherwise</returns> public bool WasJustUpdated(DateTime current) { return Bars.Count > 0 && Bars[0].Time == current - BarPeriod; } /// <summary> /// Returns true if all the data in this instance is ready (indicators, rolling windows, ect...) /// </summary> public bool IsReady { get { return Bars.IsReady && SMA_Small.IsReady && SMA_Large.IsReady && RSI.IsReady; } } } }