/*
* 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
}
}