| Overall Statistics |
|
Total Trades 54 Average Win 11.64% Average Loss -4.64% Compounding Annual Return 7.358% Drawdown 61.00% Expectancy 0.365 Net Profit 65.781% Sharpe Ratio 0.416 Loss Rate 61% Win Rate 39% Profit-Loss Ratio 2.51 Alpha 0.114 Beta -0.147 Annual Standard Deviation 0.24 Annual Variance 0.058 Information Ratio 0.012 Tracking Error 0.354 Treynor Ratio -0.679 |
namespace QuantConnect
{
/*
King Keltner - trend following algorithm from "Building Winning Trading Systems" by George Pruitt.
Buys when price action above Keltner channel, shorts when below.
Exits when price action crosses average.
*/
//---------------------------------------------------------------------------- ALGO
public class KingKeltner : QCAlgorithm
{
//primary instrument to trade
string symbol = "USO";
//indicators
SimpleMovingAverage _sma;
SimpleMovingAverage _exit;
AverageTrueRange _atr;
//other
decimal _pSMA;
decimal upBand;
decimal dnBand;
decimal action;
//conso0lidating
private TimeSpan _barPeriod = TimeSpan.FromDays(1);
private Consolidator _consolidator;
//---------------------------------------------------------------------------- INIT
public override void Initialize()
{
//Start and End Date range for the backtest:
SetStartDate(2008, 1, 1);
SetEndDate(DateTime.Now);
//Cash allocation
SetCash(25000);
AddSecurity(SecurityType.Equity, symbol, Resolution.Minute, true, 1, false);
Securities[symbol].Model = new CustomTransactionModel();
//Setup Consolidator bar bar
_consolidator = new Consolidator(_barPeriod);
//Custom Data Indicators:
_sma = new SimpleMovingAverage(40);
//exit sma
_exit = new SimpleMovingAverage(40);
//etr for Keltner channel
_atr = ATR(symbol, 40, MovingAverageType.Simple, Resolution.Daily);
}
public void OnData(TradeBars data)
{
if (_consolidator.Update(data[symbol]))
{
try
{
//weighting current price to avoid outliers
action = ((data[symbol].High+data[symbol].Low+data[symbol].Close)/3);
//updating custom indies
TradeBar bar;
if (data.TryGetValue(symbol, out bar))
{
// pump the daily data into our sma
_sma.Update(bar.Time, action);
_exit.Update(bar.Time, action);
}
//making channel
upBand = _sma + _atr;
dnBand = _sma - _atr;
//Plot indicators
Plot("Indicators", "UpBand", upBand);
Plot("Indicators", "DnBand", dnBand);
Plot("Indicators", "Price", data[symbol].Close);
//quantity for Order()
//int qty = (int)Math.Floor(Portfolio.Cash / data[symbol].Close);
//---------------------------------------------------------------------------- EXITS
if (Portfolio.HoldStock)
{
if(Portfolio[symbol].IsLong && action <= _exit)
{
Liquidate(symbol);
} else
if(Portfolio[symbol].IsShort && action >= _exit)
{
Liquidate(symbol);
}
}
//---------------------------------------------------------------------------- ENTRIES
if(!Portfolio.HoldStock && _pSMA < _sma && action > upBand)
{
//Order(symbol, qty);
SetHoldings(symbol, 1);
} else
if(!Portfolio.HoldStock && _pSMA > _sma && action < dnBand)
{
//Order(symbol, -qty);
SetHoldings(symbol, -1);
}
_pSMA = _sma;
} catch(Exception err)
{
Debug(err.Message);
}
}// end of consolidator
} // end of trabers ond
}// end of algo
}//end of namespacenamespace QuantConnect.Securities
{
public class CustomTransactionModel : ISecurityTransactionModel
{
/// <summary> Initialize the default transaction model class </summary>
public CustomTransactionModel()
{ }
/// <summary>
/// Default market fill model for the base security class. Fills at the last traded price.
/// </summary>
/// <param name="asset">Security asset we're filling</param>
/// <param name="order">Order packet to model</param>
/// <returns>Order fill informaton detailing the average price and quantity filled.</returns>
/// <seealso cref="StopMarketFill(Security, StopMarketOrder)"/>
/// <seealso cref="LimitFill(Security, LimitOrder)"/>
public OrderEvent MarketFill(Security asset, MarketOrder order)
{
//Default order event to return.
var fill = new OrderEvent(order);
//Order [fill]price for a market order model is the current security price.
order.Price = asset.Price;
order.Status = OrderStatus.Filled;
//For backtesting, we assuming the order is 100% filled on first attempt.
fill.FillPrice = asset.Price;
fill.FillQuantity = order.Quantity;
fill.Status = order.Status;
return fill;
}
/// <summary>
/// Default stop fill model implementation in base class security. (Stop Market Order Type)
/// </summary>
/// <param name="asset">Security asset we're filling</param>
/// <param name="order">Order packet to model</param>
/// <returns>Order fill informaton detailing the average price and quantity filled.</returns>
/// <seealso cref="MarketFill(Security, MarketOrder)"/>
/// <seealso cref="LimitFill(Security, LimitOrder)"/>
public OrderEvent StopMarketFill(Security asset, StopMarketOrder order)
{
//Default order event to return.
var fill = new OrderEvent(order);
//If its cancelled don't need anymore checks:
if (fill.Status == OrderStatus.Canceled) return fill;
//Check if the Stop Order was filled: opposite to a limit order
switch (order.Direction)
{
case OrderDirection.Sell:
//-> 1.1 Sell Stop: If Price below setpoint, Sell:
if (asset.Price < order.StopPrice)
{
order.Status = OrderStatus.Filled;
order.Price = asset.Price;
}
break;
case OrderDirection.Buy:
//-> 1.2 Buy Stop: If Price Above Setpoint, Buy:
if (asset.Price > order.StopPrice)
{
order.Status = OrderStatus.Filled;
order.Price = asset.Price;
}
break;
}
if (order.Status == OrderStatus.Filled || order.Status == OrderStatus.PartiallyFilled)
{
fill.FillQuantity = order.Quantity;
fill.FillPrice = asset.Price; //Stop price as security price because can gap past stop price.
fill.Status = order.Status;
}
return fill;
}
/// <summary>
/// Default limit order fill model in the base security class.
/// </summary>
/// <param name="asset">Security asset we're filling</param>
/// <param name="order">Order packet to model</param>
/// <returns>Order fill informaton detailing the average price and quantity filled.</returns>
/// <seealso cref="StopMarketFill(Security, StopMarketOrder)"/>
/// <seealso cref="MarketFill(Security, MarketOrder)"/>
public OrderEvent LimitFill(Security asset, LimitOrder order)
{
//Initialise;
var fill = new OrderEvent(order);
//If its cancelled don't need anymore checks:
if (fill.Status == OrderStatus.Canceled) return fill;
//Depending on the resolution, return different data types:
var marketData = asset.GetLastData();
decimal marketDataMinPrice;
decimal marketDataMaxPrice;
if (marketData.DataType == MarketDataType.TradeBar)
{
marketDataMinPrice = ((TradeBar)marketData).Low;
marketDataMaxPrice = ((TradeBar)marketData).High;
}
else
{
marketDataMinPrice = marketData.Value;
marketDataMaxPrice = marketData.Value;
}
//-> Valid Live/Model Order:
switch (order.Direction)
{
case OrderDirection.Buy:
//Buy limit seeks lowest price
if (marketDataMinPrice < order.LimitPrice)
{
//Set order fill:
order.Status = OrderStatus.Filled;
order.Price = asset.Price;
}
break;
case OrderDirection.Sell:
//Sell limit seeks highest price possible
if (marketDataMaxPrice > order.LimitPrice)
{
order.Status = OrderStatus.Filled;
order.Price = asset.Price;
}
break;
}
if (order.Status == OrderStatus.Filled || order.Status == OrderStatus.PartiallyFilled)
{
fill.FillQuantity = order.Quantity;
fill.FillPrice = asset.Price;
fill.Status = order.Status;
}
return fill;
}
/// <summary>
/// Get the slippage approximation for this order
/// </summary>
/// <param name="security">Security asset we're filling</param>
/// <param name="order">Order packet to model</param>
/// <returns>decimal approximation for slippage</returns>
public virtual decimal GetSlippageApproximation(Security security, Order order)
{
return 0;
}
/// <summary>
/// Default security transaction model - no fees.
/// </summary>
public virtual decimal GetOrderFee(decimal quantity, decimal price)
{
return 0;
}
/// <summary>
/// Process an order to see if it has been filled and return the matching order event.
/// </summary>
/// <param name="vehicle">Asset we're working with</param>
/// <param name="order">Order class to check if filled.</param>
/// <returns>Order fill informaton detailing the average price and quantity filled.</returns>
[Obsolete("Fill method has been made obsolete, use order type fill methods directly.")]
public virtual OrderEvent Fill(Security vehicle, Order order)
{
return new OrderEvent(order);
}
/// <summary>
/// Default market fill model for the base security class. Fills at the last traded price.
/// </summary>
/// <param name="security">Security asset we're filling</param>
/// <param name="order">Order packet to model</param>
/// <returns>Order fill informaton detailing the average price and quantity filled.</returns>
/// <seealso cref="StopMarketFill(Security, StopMarketOrder)"/>
/// <seealso cref="LimitFill(Security, LimitOrder)"/>
[Obsolete("MarketFill(Security, Order) method has been made obsolete, use MarketFill(Security, MarketOrder) method instead.")]
public virtual OrderEvent MarketFill(Security security, Order order)
{
return MarketFill(security, order as MarketOrder);
}
/// <summary>
/// Default stop fill model implementation in base class security. (Stop Market Order Type)
/// </summary>
/// <param name="security">Security asset we're filling</param>
/// <param name="order">Order packet to model</param>
/// <returns>Order fill informaton detailing the average price and quantity filled.</returns>
/// <seealso cref="LimitFill(Security, LimitOrder)"/>
/// <seealso cref="MarketFill(Security, MarketOrder)"/>
[Obsolete("StopFill(Security, Order) method has been made obsolete, use StopMarketFill(Security, StopMarketOrder) method instead.")]
public virtual OrderEvent StopFill(Security security, Order order)
{
return StopMarketFill(security, order as StopMarketOrder);
}
/// <summary>
/// Default limit order fill model in the base security class.
/// </summary>
/// <param name="security">Security asset we're filling</param>
/// <param name="order">Order packet to model</param>
/// <returns>Order fill informaton detailing the average price and quantity filled.</returns>
/// <seealso cref="StopMarketFill(Security, StopMarketOrder)"/>
/// <seealso cref="MarketFill(Security, MarketOrder)"/>
[Obsolete("LimitFill(Security, Order) method has been made obsolete, use LimitFill(Security, LimitOrder) method instead.")]
public virtual OrderEvent LimitFill(Security security, Order order)
{
return LimitFill(security, order as LimitOrder);
}
} // End Algorithm Transaction Filling Classes
} // End QC Namespaceusing System;
using System.Collections;
using System.Collections.Generic;
using QuantConnect.Securities;
using QuantConnect.Models;
namespace QuantConnect
{
/*
* TimeSpanConsolidator Helper Routine: Assemble generic timespan bar lengths: e.g. 10 minutes:
*
* 1. Setup the new Consolidator class with the timespan period:
* var _consolidator = new Consolidator(TimeSpan.FromMinutes(10));
*
* 2. Add in the data with the update routine. It will return true when bar ready
* if (_consolidator.Update(data["MSFT"])) { UseBar }
*/
public class Consolidator
{
private TradeBar _resultBar;
private TradeBar _workingBar;
private DateTime _start;
private TimeSpan _period;
//Result:
public TradeBar Bar
{
get
{
return _resultBar;
}
}
//Constructor: Set the period we'd like to scan
public Consolidator(TimeSpan span)
{
this._period = span;
this._resultBar = new TradeBar();
this._workingBar = new TradeBar(new DateTime(), "", Decimal.Zero, Decimal.MinValue, Decimal.MaxValue, 0, 0);
}
//Submit this bar, return true if we've started a new one.
public bool Update(TradeBar newBar)
{
//Intialize:
if (_start == new DateTime())
{
_start = newBar.Time;
}
//While we're less than end date, keep adding to this bar:
if (newBar.Time < (_start + _period))
{
//Building bar:
AddToBar(newBar);
return false;
}
else
{
//Completed bar: start new one:
_resultBar = _workingBar;
//Create a new bar:
_workingBar = new TradeBar(newBar.Time, newBar.Symbol, Decimal.Zero, Decimal.MinValue, Decimal.MaxValue, 0, 0);
//Start of this bar:
_start = newBar.Time;
AddToBar(newBar);
return true;
}
}
//Add to a tradebar
private void AddToBar(TradeBar newBar)
{
//Add this data to working bar:
if (_workingBar.Time == new DateTime()) _workingBar.Time = newBar.Time;
if (_workingBar.Symbol == "") _workingBar.Symbol = newBar.Symbol;
if (_workingBar.Open == Decimal.Zero) _workingBar.Open = newBar.Open;
if (newBar.High > _workingBar.High) _workingBar.High = newBar.High;
if (newBar.Low < _workingBar.Low) _workingBar.Low = newBar.Low;
_workingBar.Close = newBar.Close;
_workingBar.Volume = newBar.Volume;
}
}
}