/* * 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 System; using System.Linq; using QuantConnect.Brokerages; using QuantConnect.Data; using QuantConnect.Data.Market; using QuantConnect.Indicators; using QuantConnect.Orders; using QuantConnect.Securities; namespace QuantConnect.Algorithm.CSharp { /// /// /// QCU: Opening Breakout Algorithm /// /// In this algorithm we attempt to provide a working algorithm that /// addresses many of the primary algorithm concerns. These concerns /// are: /// /// 1. Signal Generation. /// This algorithm aims to generate signals for an opening /// breakout move before 10am. Signals are generated by /// producing the opening five minute bar, and then trading /// in the direction of the breakout from that bar. /// /// 2. Position Sizing. /// Positions are sized using recently the average true range. /// The higher the recently movement, the smaller position. /// This helps to reduce the risk of losing a lot on a single /// transaction. /// /// 3. Active Stop Loss. /// Stop losses are maintained at a fixed global percentage to /// limit maximum losses per day, while also a trailing stop /// loss is implemented using the parabolic stop and reverse /// in order to gauge exit points. /// /// /// /// public class OpeningBreakoutAlgorithm : QCAlgorithm { #pragma warning disable 00162 // File contains unreachable code when EnableOrderUpdateLogging is false // the equity symbol we're trading private const string symbol = "SPY"; // plotting and logging control private const bool EnablePlotting = true; private const bool EnableOrderUpdateLogging = false; private const int PricePlotFrequencyInSeconds = 15; // risk control private const decimal MaximumLeverage = 4; private const decimal GlobalStopLossPercent = 0.001m; private const decimal PercentProfitStartPsarTrailingStop = 0.0003m; private const decimal MaximumPorfolioRiskPercentPerPosition = .0025m; // entrance criteria private const int OpeningSpanInMinutes = 3; private const decimal BreakoutThresholdPercent = 0.00005m; private const decimal AtrVolatilityThresholdPercent = 0.00275m; private const decimal StdVolatilityThresholdPercent = 0.005m; // this is the security we're trading private Security _security; // define our indicators used for trading decisions private AverageTrueRange ATR14; private StandardDeviation STD14; private AverageDirectionalIndex ADX14; private ParabolicStopAndReverse PSARMin; // smoothed values private ExponentialMovingAverage _smoothedSTD14; private ExponentialMovingAverage _smoothedATR14; // working variable to control our algorithm // this flag is used to run some code only once after the algorithm is warmed up private bool FinishedWarmup; // this is used to record the last time we closed a position private DateTime LastExitTime; // this is our opening n minute bar private TradeBar OpeningBarRange; // this is the ticket from our market order (entrance) private OrderTicket MarketTicket; // this is the ticket from our stop loss order (exit) private OrderTicket StopLossTicket; // this flag is used to indicate we've switched from a global, non changing // stop loss to a dynamic trailing stop using the PSAR private bool EnablePsarTrailingStop; /// /// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized. /// public override void Initialize() { // initialize algorithm level parameters SetStartDate(2013, 10, 07); SetEndDate(2013, 10, 11); //SetStartDate(2014, 01, 01); //SetEndDate(2014, 06, 01); SetCash(100000); // leverage tradier $1 traders SetBrokerageModel(BrokerageName.TradierBrokerage); // request high resolution equity data AddSecurity(SecurityType.Equity, symbol, Resolution.Second); // save off our security so we can reference it quickly later _security = Securities[symbol]; // Set our max leverage _security.SetLeverage(MaximumLeverage); // define our longer term indicators ADX14 = ADX(symbol, 28, Resolution.Hour); STD14 = STD(symbol, 14, Resolution.Daily); ATR14 = ATR(symbol, 14, resolution: Resolution.Daily); PSARMin = new ParabolicStopAndReverse(symbol, afStart: 0.0001m, afIncrement: 0.0001m); // smooth our ATR over a week, we'll use this to determine if recent volatilty warrants entrance var oneWeekInMarketHours = (int)(5*6.5); _smoothedATR14 = new ExponentialMovingAverage("Smoothed_" + ATR14.Name, oneWeekInMarketHours).Of(ATR14); // smooth our STD over a week as well _smoothedSTD14 = new ExponentialMovingAverage("Smoothed_"+STD14.Name, oneWeekInMarketHours).Of(STD14); // initialize our charts var chart = new Chart(symbol); chart.AddSeries(new Series(ADX14.Name, SeriesType.Line, 0)); chart.AddSeries(new Series("Enter", SeriesType.Scatter, 0)); chart.AddSeries(new Series("Exit", SeriesType.Scatter, 0)); chart.AddSeries(new Series(PSARMin.Name, SeriesType.Scatter, 0)); AddChart(chart); var history = History(symbol, 20, Resolution.Daily); foreach (var bar in history) { ADX14.Update(bar); ATR14.Update(bar); STD14.Update(bar.EndTime, bar.Close); } // schedule an event to run every day at five minutes after our symbol's market open Schedule.Event("MarketOpenSpan") .EveryDay(symbol) .AfterMarketOpen(symbol, minutesAfterOpen: OpeningSpanInMinutes) .Run(MarketOpeningSpanHandler); Schedule.Event("MarketOpen") .EveryDay(symbol) .AfterMarketOpen(symbol, minutesAfterOpen: -1) .Run(() => PSARMin.Reset()); } /// /// This function is scheduled to be run every day at the specified number of minutes after market open /// public void MarketOpeningSpanHandler() { // request the last n minutes of data in minute bars, we're going to // define the opening rang var history = History(symbol, OpeningSpanInMinutes, Resolution.Minute); // this is our bar size var openingSpan = TimeSpan.FromMinutes(OpeningSpanInMinutes); // we only care about the high and low here OpeningBarRange = new TradeBar { // time values Time = Time - openingSpan, EndTime = Time, Period = openingSpan, // high and low High = _security.Close, Low = _security.Close }; // aggregate the high/low for the opening range foreach (var tradeBar in history) { OpeningBarRange.Low = Math.Min(OpeningBarRange.Low, tradeBar.Low); OpeningBarRange.High = Math.Max(OpeningBarRange.High, tradeBar.High); } // widen the bar when looking for breakouts OpeningBarRange.Low *= 1 - BreakoutThresholdPercent; OpeningBarRange.High *= 1 + BreakoutThresholdPercent; Log("---------" + Time.Date + "---------"); Log("OpeningBarRange: Low: " + OpeningBarRange.Low.SmartRounding() + " High: " + OpeningBarRange.High.SmartRounding()); } /// /// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here. /// /// Slice object keyed by symbol containing the stock data public override void OnData(Slice data) { // we don't need to run any of this during our warmup phase if (IsWarmingUp) return; // when we're done warming up, register our indicators to start plotting if (!IsWarmingUp && !FinishedWarmup) { // this is a run once flag for when we're finished warmup FinishedWarmup = true; // plot our hourly indicators automatically, wait for them to ready PlotIndicator("ADX", ADX14); PlotIndicator("ADX", ADX14.NegativeDirectionalIndex, ADX14.PositiveDirectionalIndex); PlotIndicator("ATR", true, ATR14); PlotIndicator("STD", true, STD14); PlotIndicator("ATR", true, _smoothedATR14); } // update our PSAR PSARMin.Update((TradeBar) _security.GetLastData()); // plot price until an hour after we close so we can see our execution skillz if (ShouldPlot) { // we can plot price more often if we want Plot(symbol, "Price", _security.Close); // only plot psar on the minute if (PSARMin.IsReady) { Plot(symbol, PSARMin); } } // first wait for our opening range bar to be set to today if (OpeningBarRange == null || OpeningBarRange.EndTime.Date != Time.Date || OpeningBarRange.EndTime == Time) return; // we only trade max once per day, so if we've already exited the stop loss, bail if (StopLossTicket != null && StopLossTicket.Status == OrderStatus.Filled) { // null these out to signal that we're done trading for the day OpeningBarRange = null; StopLossTicket = null; return; } // now that we have our opening bar, test to see if we're already in a position if (!_security.Invested) { ScanForEntrance(); } else { // if we haven't exited yet then manage our stop loss, this controls our exit point if (_security.Invested) { ManageStopLoss(); } else if (StopLossTicket != null && StopLossTicket.Status.IsOpen()) { StopLossTicket.Cancel(); } } } /// /// Scans for a breakout from the opening range bar /// private void ScanForEntrance() { // scan for entrances, we only want to do this before 10am if (Time.TimeOfDay.Hours >= 10) return; // expect capture 10% of the daily range var expectedCaptureRange = 0.1m*ATR14; var allowedDollarLoss = MaximumPorfolioRiskPercentPerPosition * Portfolio.TotalPortfolioValue; var shares = (int) (allowedDollarLoss/expectedCaptureRange); // determine a position size based on an acceptable loss in proporton to our total portfolio value //var shares = (int) (MaximumLeverage*MaximumPorfolioRiskPercentPerPosition*Portfolio.TotalPortfolioValue/(0.4m*ATR14)); // max out at a little below our stated max, prevents margin calls and such var maxShare = (int) CalculateOrderQuantity(symbol, MaximumLeverage); shares = Math.Min(shares, maxShare); // min out at 1x leverage //var minShare = CalculateOrderQuantity(symbol, MaximumLeverage/2m); //shares = Math.Max(shares, minShare); // we're looking for a breakout of the opening range bar in the direction of the medium term trend if (ShouldEnterLong) { // breakout to the upside, go long (fills synchronously) MarketTicket = MarketOrder(symbol, shares); Log("Enter long @ " + MarketTicket.AverageFillPrice.SmartRounding() + " Shares: " + shares); Plot(symbol, "Enter", MarketTicket.AverageFillPrice); // we'll start with a global, non-trailing stop loss EnablePsarTrailingStop = false; // submit stop loss order for max loss on the trade var stopPrice = _security.Low*(1 - GlobalStopLossPercent); StopLossTicket = StopMarketOrder(symbol, -shares, stopPrice); if (EnableOrderUpdateLogging) { Log("Submitted stop loss @ " + stopPrice.SmartRounding()); } } else if (ShouldEnterShort) { // breakout to the downside, go short MarketTicket = MarketOrder(symbol, - -shares); Log("Enter short @ " + MarketTicket.AverageFillPrice.SmartRounding()); Plot(symbol, "Enter", MarketTicket.AverageFillPrice); // we'll start with a global, non-trailing stop loss EnablePsarTrailingStop = false; // submit stop loss order for max loss on the trade var stopPrice = _security.High*(1 + GlobalStopLossPercent); StopLossTicket = StopMarketOrder(symbol, -shares, stopPrice); if (EnableOrderUpdateLogging) { Log("Submitted stop loss @ " + stopPrice.SmartRounding() + " Shares: " + shares); } } } /// /// Manages our stop loss ticket /// private void ManageStopLoss() { // if we've already exited then no need to do more if (StopLossTicket == null || StopLossTicket.Status == OrderStatus.Filled) return; // only do this once per minute //if (Time.RoundDown(TimeSpan.FromMinutes(1)) != Time) return; // get the current stop price var stopPrice = StopLossTicket.Get(OrderField.StopPrice); // check for enabling the psar trailing stop if (ShouldEnablePsarTrailingStop(stopPrice)) { EnablePsarTrailingStop = true; Log("Enabled PSAR trailing stop @ ProfitPercent: " + _security.Holdings.UnrealizedProfitPercent.SmartRounding()); } // we've trigger the psar trailing stop, so start updating our stop loss tick if (EnablePsarTrailingStop && PSARMin.IsReady) { StopLossTicket.Update(new UpdateOrderFields {StopPrice = PSARMin}); Log("Submitted stop loss @ " + PSARMin.Current.Value.SmartRounding()); } } /// /// This event handler is fired for each and every order event the algorithm /// receives. We'll perform some logging and house keeping here /// public override void OnOrderEvent(OrderEvent orderEvent) { // print debug messages for all order events if (LiveMode || orderEvent.Status.IsFill() || EnableOrderUpdateLogging) { LiveDebug("Filled: " + orderEvent.FillQuantity + " Price: " + orderEvent.FillPrice); } // if this is a fill and we now don't own any stock, that means we've closed for the day if (!_security.Invested && orderEvent.Status == OrderStatus.Filled) { // reset values for tomorrow LastExitTime = Time; var ticket = Transactions.GetOrderTickets(x => x.OrderId == orderEvent.OrderId).Single(); Plot(symbol, "Exit", ticket.AverageFillPrice); } } /// /// If we're still invested by the end of the day, liquidate /// public override void OnEndOfDay(Symbol symbol) { if (symbol == _security.Symbol && _security.Invested) { Liquidate(); } } /// /// Determines whether or not we should plot. This is used /// to provide enough plot points but not too many, we don't /// need to plot every second in backtests to get an idea of /// how good or bad our algorithm is performing /// public bool ShouldPlot { get { // always in live if (LiveMode) return true; // set in top to override plotting during long backtests if (!EnablePlotting) return false; // every 30 seconds in backtest if (Time.RoundDown(TimeSpan.FromSeconds(PricePlotFrequencyInSeconds)) != Time) return false; // always if we're invested if (_security.Invested) return true; // always if it's before noon if (Time.TimeOfDay.Hours < 10.25) return true; // for an hour after our exit if (Time - LastExitTime < TimeSpan.FromMinutes(30)) return true; return false; } } /// /// In live mode it's nice to push messages to the debug window /// as well as the log, this allows easy real time inspection of /// how the algorithm is performing /// public void LiveDebug(object msg) { if (msg == null) return; if (LiveMode) { Debug(msg.ToString()); Log(msg.ToString()); } else { Log(msg.ToString()); } } /// /// Determines whether or not we should end a long position /// private bool ShouldEnterLong { // check to go in the same direction of longer term trend and opening break out get { return IsUptrend && HasEnoughRecentVolatility && _security.Close > OpeningBarRange.High; } } /// /// Determines whether or not we're currently in a medium term up trend /// private bool IsUptrend { get { return ADX14 > 20 && ADX14.PositiveDirectionalIndex > ADX14.NegativeDirectionalIndex; } } /// /// Determines whether or not we should enter a short position /// private bool ShouldEnterShort { // check to go in the same direction of longer term trend and opening break out get { return IsDowntrend && HasEnoughRecentVolatility && _security.Close < OpeningBarRange.Low; } } /// /// Determines whether or not we're currently in a medium term down trend /// private bool IsDowntrend { get { return ADX14 > 20 && ADX14.NegativeDirectionalIndex > ADX14.PositiveDirectionalIndex; } } /// /// Determines whether or not there's been enough recent volatility for /// this strategy to work /// private bool HasEnoughRecentVolatility { get { return _smoothedATR14 > _security.Close*AtrVolatilityThresholdPercent || _smoothedSTD14 > _security.Close*StdVolatilityThresholdPercent; } } /// /// Determines whether or not we should enable the psar trailing stop /// /// current stop price of our stop loss tick private bool ShouldEnablePsarTrailingStop(decimal stopPrice) { // no need to enable if it's already enabled return !EnablePsarTrailingStop // once we're up a certain percentage, we'll use PSAR to control our stop && _security.Holdings.UnrealizedProfitPercent > PercentProfitStartPsarTrailingStop // make sure the PSAR is on the right side && PsarIsOnRightSideOfPrice // make sure the PSAR is more profitable than our global loss && IsPsarMoreProfitableThanStop(stopPrice); } /// /// Determines whether or not the PSAR is on the right side of price depending on our long/short /// private bool PsarIsOnRightSideOfPrice { get { return (_security.Holdings.IsLong && PSARMin < _security.Close) || (_security.Holdings.IsShort && PSARMin > _security.Close); } } /// /// Determines whether or not the PSAR stop price is better than the specified stop price /// private bool IsPsarMoreProfitableThanStop(decimal stopPrice) { return (_security.Holdings.IsLong && PSARMin > stopPrice) || (_security.Holdings.IsShort && PSARMin < stopPrice); } #pragma warning restore 00162 } }