namespace QuantConnect
{
public partial class Constitution : QCAlgorithm
{
//Algorithm Settings:
//Algorithm Working Variables:
string symbol = "SPY";
DateTime exitDate = new DateTime();
RelativeStrengthIndexCustom rsi = new RelativeStrengthIndexCustom(10,5);
//Set up the initial algorithm backtesting settings:
public override void Initialize()
{
SetStartDate(2000, 1, 1);
SetEndDate(DateTime.Now.Date.AddDays(-1));
SetCash(25000);
AddSecurity(SecurityType.Equity, symbol, Resolution.Minute);
}
///<summary>
/// Enter the market, monitor the loss/peak gain and code up a fake stop.
///</summary>
public void OnData(TradeBars data)
{
TradeBar SPY = data[symbol];
decimal price = data[symbol].Close;
//Update calculation for SPY-RSI:
rsi.AddSample(SPY);
//Don't trade on same day we just exited.
if (exitDate.Date == Time.Date) return;
//We dont have stock yet: this is a completely dumb entry strategy so lets just go long to kick it off.
if (!Portfolio.HoldStock)
{
ScanForEntry(SPY);
return;
}
//We have stock, but scan for a profit taking opportunity:
if (Portfolio.HoldStock)
{
ScanForEntry(SPY);
return;
}
}
///<summary>
/// Scan for an entry signal to invest.
///</summary>
public void ScanForEntry(TradeBar SPY)
{
//Once we have enough data, start the Entry detection.
if (rsi.Ready)
{
if (!rsi.Buy)
{
SetHoldings(symbol, -1); //Over bought
Log("Entry-Short: " + "-1" + " Holdings: " + Portfolio[symbol].Quantity);
}
else if (rsi.Buy)
{
SetHoldings(symbol, 1); //Over sold
Log("Entry-Long: " + "1" + " Holdings: " + Portfolio[symbol].Quantity);
}
}
}
}
}
using System.Collections.Concurrent;
namespace QuantConnect {
/*
* Relative Strength Index Indicator:
*
* 100
* RSI = 100 - ------------
* 1 + RS
*
* Where RS = Avg of X Period Close Up / Absolute(Avg) X of Period Close Down.
*
*/
public class RelativeStrengthIndexCustom
{
//Public Access to the RSI Output
public decimal RSI {
get {
return (100 - (100 / (1 + _rs)));
}
}
//Public Access to the RSI Crossover value
public bool Buy {
get {
if (_rs >= _SMA)
{
return (true);
} else {
return (false);
}
}
}
//Public Access to Know if RSI Indicator Ready
public bool Ready {
get {
return (_upward.Count >= _period) && (_downward.Count >= _period);
}
}
//Private Class Variables:
private decimal _rs = 0, _SMA = 0;
private bool _ema = false;
private decimal _period = 14, _MAperiod = 0;
private decimal _joinBars = 1;
private Candle _superCandle = new Candle();
private Candle _previousCandle = new Candle();
private FixedSizedQueue<decimal> _downward = new FixedSizedQueue<decimal>(0);
private FixedSizedQueue<decimal> _upward = new FixedSizedQueue<decimal>(0);
private FixedSizedQueue<decimal> _SMAqueue = new FixedSizedQueue<decimal>(0);
private decimal _upwardSum = 0, _avgUpward = 0;
private decimal _downwardSum = 0, _avgDownward = 0;
private decimal _SMAsum = 0;
//Initialize the RSI with 'period' candles
public RelativeStrengthIndexCustom(int period, int MAperiod = 0, int joinBars = 1, bool useEMA = false) {
//Range check variables:
if (period < 2) period = 2;
//Class settings:
_period = (decimal)period; // How many samples is the RSI?
_MAperiod = (decimal)MAperiod; //Period of the MA crossover
_ema = useEMA; // Use the EMA average for RSI
_joinBars = joinBars; // Join multiple tradebars together
//Remember the upward and downward movements in a FIFO queue:
_upward = new FixedSizedQueue<decimal>(period);
_downward = new FixedSizedQueue<decimal>(period);
//SMA FIFO queue:
_SMAqueue = new FixedSizedQueue<decimal>(MAperiod);
//Starting values of MA SUMS:
_upwardSum = 0; _downwardSum = 0; _SMAsum = 0;
}
//Add a new sample to build the RSI Indicator:
public void AddSample(TradeBar bar) {
//Build a multibar candle, until join reached return.
_superCandle.Update(bar);
if (_superCandle.Samples < _joinBars) return;
//Initialize the first loop.
if (_previousCandle.Samples == 0) {
_previousCandle = _superCandle;
_superCandle = new Candle();
return;
}
//Get the difference between this bar and previous bar:
decimal difference = _superCandle.Close - _previousCandle.Close;
//Get a sample for SMA
decimal SMAsample = _superCandle.Close;
//Update the Moving Average Calculations:
if (difference >= 0) {
if (_ema) {
_avgUpward = UpdateDirectionalEMA(ref _upward, difference);
_avgDownward = UpdateDirectionalEMA(ref _downward, 0);
} else {
_avgUpward = UpdateDirectionalSMA(ref _upward, ref _upwardSum, difference);
_avgDownward = UpdateDirectionalSMA(ref _downward, ref _downwardSum, 0);
}
}
if (difference <= 0) {
difference = Math.Abs(difference);
if (_ema) {
_avgUpward = UpdateDirectionalEMA(ref _upward, 0);
_avgDownward = UpdateDirectionalEMA(ref _downward, difference);
} else {
_avgUpward = UpdateDirectionalSMA(ref _upward, ref _upwardSum, 0);
_avgDownward = UpdateDirectionalSMA(ref _downward, ref _downwardSum, difference);
}
}
//Refresh RS Factor:
//RS Index Automatically Updated in the Public Property Above:
if (_avgDownward != 0) {
_rs = _avgUpward / _avgDownward;
} else {
_rs = Decimal.MaxValue - 1;
}
//Update SMA calculations
if (_MAperiod != 0) {
_SMA = UpdateSMA(ref _SMAqueue, ref _SMAsum, _rs);
}
//Reset for next loop:
_previousCandle = _superCandle;
_superCandle = new Candle();
}
// Update the moving average and fixed length queue in a generic fashion to work for up and downward movement.
// Return the average.
private decimal UpdateDirectionalSMA(ref FixedSizedQueue<decimal> queue, ref decimal sum, decimal sample) {
//Increment Sum
sum += sample;
//If we've shuffled off queue, remove from sum:
if(queue.Enqueue(sample)) {
sum -= queue.LastDequeued;
}
//When less than period samples, only divide by the number of samples.
if (queue.Count < _period) {
return (sum / (decimal)queue.Count);
} else {
return (sum / _period);
}
}
private decimal UpdateSMA(ref FixedSizedQueue<decimal> queue, ref decimal sum, decimal sample) {
//Increment Sum
sum += sample;
//If we've shuffled off queue, remove from sum:
if(queue.Enqueue(sample)) {
sum -= queue.LastDequeued;
}
//When less than period samples, only divide by the number of samples.
if (queue.Count < _period) {
return (sum / (decimal)queue.Count);
} else {
return (sum / _period);
}
}
// Update the moving average and fixed length queue in a generic fashion to work for up and downward movement.
// Return the average.
private decimal UpdateDirectionalEMA(ref FixedSizedQueue<decimal> queue, decimal sample) {
queue.Enqueue(sample);
if (queue.Count == 1) {
return sample;
} else {
return (1m / _period) * sample + ((_period - 1m) / _period) * queue.LastEnqueued;
}
}
//Fixed length queue that dumps things off when no more space in queue.
private class FixedSizedQueue<T> : ConcurrentQueue<T> {
public int Size { get; private set; }
public T LastDequeued { get; private set; }
public T LastEnqueued {get; private set;}
public bool Dequeued { get; private set; }
public FixedSizedQueue(int size) { Size = size; }
public new bool Enqueue(T obj) {
base.Enqueue(obj);
LastEnqueued = obj;
Dequeued = false;
lock (this) {
if (base.Count > Size) {
T outObj;
Dequeued = base.TryDequeue(out outObj);
LastDequeued = outObj;
}
}
return Dequeued;
}
}
/// <summary>
/// Simple online "super-tradebar" generator for making an OHLC from multiple bars.
/// </summary>
public class Candle {
public decimal Open = 0;
public decimal High = Decimal.MinValue;
public decimal Low = Decimal.MaxValue;
public decimal Close = 0;
public int Samples = 0;
public void Update(TradeBar bar) {
if (Open == 0) Open = bar.Open;
if (High < bar.High) High = bar.High;
if (Low > bar.Low) Low = bar.Low;
Close = bar.Close;
Samples++;
}
}
}
}
using System.Collections.Concurrent;
namespace QuantConnect {
/*
* Relative Strength Index Indicator:
*
* 100
* RSI = 100 - ------------
* 1 + RS
*
* Where RS = Avg of X Period Close Up / Absolute(Avg) X of Period Close Down.
*
*/
public class RelativeStrengthIndexCustom2
{
//Public Access to the RSI Output
public decimal RSI {
get {
return (100 - (100 / (1 + _rs)));
}
}
//Public Access to Know if RSI Indicator Ready
public bool Ready {
get {
return (_upward.Count >= _period) && (_downward.Count >= _period);
}
}
//Private Class Variables:
private decimal _rs = 0;
private bool _ema = false;
private decimal _period = 14;
private decimal _joinBars = 1;
private Candle _superCandle = new Candle();
private Candle _previousCandle = new Candle();
private FixedSizedQueue<decimal> _downward = new FixedSizedQueue<decimal>(0);
private FixedSizedQueue<decimal> _upward = new FixedSizedQueue<decimal>(0);
private decimal _upwardSum = 0, _avgUpward = 0;
private decimal _downwardSum = 0, _avgDownward = 0;
//Initialize the RSI with 'period' candles
public RelativeStrengthIndexCustom2(int period, int joinBars = 1, bool useEMA = false) {
//Range check variables:
if (period < 2) period = 2;
//Class settings:
_period = (decimal)period; // How many samples is the RSI?
_ema = useEMA; // Use the EMA average for RSI
_joinBars = joinBars; // Join multiple tradebars together
//Remember the upward and downward movements in a FIFO queue:
_upward = new FixedSizedQueue<decimal>(period);
_downward = new FixedSizedQueue<decimal>(period);
//Online implementation of SMA - needs moving sum of all components:
_upwardSum = 0; _downwardSum = 0;
}
//Add a new sample to build the RSI Indicator:
public void AddSample(TradeBar bar) {
//Build a multibar candle, until join reached return.
_superCandle.Update(bar);
if (_superCandle.Samples < _joinBars) return;
//Initialize the first loop.
if (_previousCandle.Samples == 0) {
_previousCandle = _superCandle;
_superCandle = new Candle();
return;
}
//Get the difference between this bar and previous bar:
decimal difference = _superCandle.Close - _previousCandle.Close;
//Update the Moving Average Calculations:
if (difference >= 0) {
if (_ema) {
_avgUpward = UpdateDirectionalEMA(ref _upward, difference);
_avgDownward = UpdateDirectionalEMA(ref _downward, 0);
} else {
_avgUpward = UpdateDirectionalSMA(ref _upward, ref _upwardSum, difference);
_avgDownward = UpdateDirectionalSMA(ref _downward, ref _downwardSum, 0);
}
}
if (difference <= 0) {
difference = Math.Abs(difference);
if (_ema) {
_avgUpward = UpdateDirectionalEMA(ref _upward, 0);
_avgDownward = UpdateDirectionalEMA(ref _downward, difference);
} else {
_avgUpward = UpdateDirectionalSMA(ref _upward, ref _upwardSum, 0);
_avgDownward = UpdateDirectionalSMA(ref _downward, ref _downwardSum, difference);
}
}
//Refresh RS Factor:
//RS Index Automatically Updated in the Public Property Above:
if (_avgDownward != 0) {
_rs = _avgUpward / _avgDownward;
} else {
_rs = Decimal.MaxValue - 1;
}
//Reset for next loop:
_previousCandle = _superCandle;
_superCandle = new Candle();
}
// Update the moving average and fixed length queue in a generic fashion to work for up and downward movement.
// Return the average.
private decimal UpdateDirectionalSMA(ref FixedSizedQueue<decimal> queue, ref decimal sum, decimal sample) {
//Increment Sum
sum += sample;
//If we've shuffled off queue, remove from sum:
if(queue.Enqueue(sample)) {
sum -= queue.LastDequeued;
}
//When less than period samples, only divide by the number of samples.
if (queue.Count < _period) {
return (sum / (decimal)queue.Count);
} else {
return (sum / _period);
}
}
// Update the moving average and fixed length queue in a generic fashion to work for up and downward movement.
// Return the average.
private decimal UpdateDirectionalEMA(ref FixedSizedQueue<decimal> queue, decimal sample) {
queue.Enqueue(sample);
if (queue.Count == 1) {
return sample;
} else {
return (1m / _period) * sample + ((_period - 1m) / _period) * queue.LastEnqueued;
}
}
//Fixed length queue that dumps things off when no more space in queue.
private class FixedSizedQueue<T> : ConcurrentQueue<T> {
public int Size { get; private set; }
public T LastDequeued { get; private set; }
public T LastEnqueued {get; private set;}
public bool Dequeued { get; private set; }
public FixedSizedQueue(int size) { Size = size; }
public new bool Enqueue(T obj) {
base.Enqueue(obj);
LastEnqueued = obj;
Dequeued = false;
lock (this) {
if (base.Count > Size) {
T outObj;
Dequeued = base.TryDequeue(out outObj);
LastDequeued = outObj;
}
}
return Dequeued;
}
}
/// <summary>
/// Simple online "super-tradebar" generator for making an OHLC from multiple bars.
/// </summary>
public class Candle {
public decimal Open = 0;
public decimal High = Decimal.MinValue;
public decimal Low = Decimal.MaxValue;
public decimal Close = 0;
public int Samples = 0;
public void Update(TradeBar bar) {
if (Open == 0) Open = bar.Open;
if (High < bar.High) High = bar.High;
if (Low > bar.Low) Low = bar.Low;
Close = bar.Close;
Samples++;
}
}
}
}
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
//}
}
namespace QuantConnect
{
/*
* QuantConnect University
* Martingale Position Sizing Experiment
*
* Martingale is a [risky/gambling] technique which can be applied to trading:
* When a trade is going against you, double down and flip the position (long -> short).
* If the position is still going against you continue flipping direction until you win.
*
* Typically martingale curves are perfectly straight, until they drop off sharply a cliff!
* This is because they hide the intra-trade risk and only show the closing profits. QC shows the full
* equity curve exposing true martingale risks.
*/
public partial class QCUMartingalePositionSizing : QCAlgorithm
{
//Algorithm Settings:
decimal peakTroughDeltaBeforeFlip = 0.02m; // Percentage below high watermark before we flip our position
decimal targetProfit = 0.05m; //Target profit for strategy. When achieve this exit.
//Algorithm Working Variables:
string symbol = "SPY";
decimal magnitudeDirection = 0.2m, tradeStringProfit = 0m, leverage = 4m;
DateTime exitDate = new DateTime();
RelativeStrengthIndexCustom rsi = new RelativeStrengthIndexCustom(30);
//Set up the initial algorithm backtesting settings:
public override void Initialize()
{
SetStartDate(2000, 1, 1);
SetEndDate(DateTime.Now.Date.AddDays(-1));
SetCash(25000);
AddSecurity(SecurityType.Equity, symbol, Resolution.Minute, true, leverage, false);
}
///<summary>
/// Loss per share to exit/flip direction on the asset
///</summary>
private decimal MaxLossPerShare
{
get
{
//Simpler calc for max loss per share: max loss % * stock price.
return Portfolio[symbol].AveragePrice * peakTroughDeltaBeforeFlip;
}
}
///<summary>
///Sum of the martingale flip-losses + current unrealised profit
///</summary>
private decimal UnrealizedTradeStringProfit
{
get
{
return tradeStringProfit + Portfolio.TotalUnrealizedProfit;
}
}
///<summary>
///Short hand bool for detecting if the algorithm has reached minimum profit target
// -> Profit on holdings rather than cash -- makes gains achievable.
///</summary>
private bool MinimumProfitTargetReached
{
get
{
return (Portfolio.TotalUnrealizedProfit / Math.Abs(Portfolio.TotalUnleveredAbsoluteHoldingsCost)) > targetProfit;
}
}
///<summary>
/// Enter the market, monitor the loss/peak gain and code up a fake stop.
///</summary>
public void OnData(TradeBars data)
{
TradeBar SPY = data[symbol];
decimal price = data[symbol].Close;
//Update calculation for SPY-RSI:
rsi.AddSample(SPY);
//Don't trade on same day we just exited.
if (exitDate.Date == Time.Date) return;
//We dont have stock yet: this is a completely dumb entry strategy so lets just go long to kick it off.
if (!Portfolio.HoldStock)
{
ScanForEntry(SPY);
return;
}
//We have stock, but scan for a profit taking opportunity:
if (MinimumProfitTargetReached)
{
ScanForExit(SPY);
return;
}
//Finally we're not in green, but not flipped yet: monitor the loss to change direction:
if (Math.Abs(price - Portfolio[symbol].AveragePrice) > MaxLossPerShare && Portfolio.TotalUnrealizedProfit < 0)
{
SetMagnitudeDirection(-2);
Flip();
}
}
///<summary>
/// Scan for an entry signal to invest.
///</summary>
public void ScanForEntry(TradeBar SPY)
{
//Once we have enough data, start the Entry detection.
if (rsi.Ready)
{
if (rsi.RSI > 75) {
magnitudeDirection = -0.2m;
SetHoldings(symbol, -magnitudeDirection); //Over bought
Log("Entry-Short: " + magnitudeDirection + " Holdings: " + Portfolio[symbol].Quantity);
}
else if (rsi.RSI < 30)
{
magnitudeDirection = 0.2m;
SetHoldings(symbol, magnitudeDirection); //Over sold
Log("Entry-Long: " + magnitudeDirection + " Holdings: " + Portfolio[symbol].Quantity);
}
}
}
///<summary>
/// For now dumb exit;
///</summary>
public void ScanForExit(TradeBar SPY)
{
Log("Exit: " + magnitudeDirection + " Realized Profit/Loss: " + UnrealizedTradeStringProfit.ToString("C"));
Liquidate();
tradeStringProfit = 0;
magnitudeDirection = 0.2m;
exitDate = Time.Date;
return;
}
///<summary>
/// Set and Normalise MagnitudeDirection Multiplier
///</summary>
public void SetMagnitudeDirection(decimal multiplier)
{
//Apply multiplier:
magnitudeDirection = magnitudeDirection * multiplier;
decimal direction = magnitudeDirection / Math.Abs(magnitudeDirection);
//Normalize Max Investment to Max Leverage
if (Math.Abs(magnitudeDirection) > leverage)
{
magnitudeDirection = direction * leverage;
}
//Normalize Minimum Investment to 20%;
if (Math.Abs(magnitudeDirection) < 0.2m)
{
magnitudeDirection = direction * 0.2m;
}
}
///<summary>
/// Flip & Invert our Holdings When We're Wrong:
///</summary>
public void Flip()
{
//Record the loss
tradeStringProfit += Portfolio.TotalUnrealisedProfit;
exitDate = Time.Date;
SetHoldings(symbol, magnitudeDirection);
Log("Flip: " + magnitudeDirection + " Holdings: " + Portfolio[symbol].Quantity + " String Loss: " + tradeStringProfit.ToString("C"));
}
}
}