Overall Statistics
Total Trades
1380
Average Win
0.8%
Average Loss
-4.54%
Compounding Annual Return
9.159%
Drawdown
59.400%
Expectancy
0.031
Net Profit
276.411%
Sharpe Ratio
0.5
Loss Rate
12%
Win Rate
88%
Profit-Loss Ratio
0.18
Alpha
0.095
Beta
0.31
Annual Standard Deviation
0.228
Annual Variance
0.052
Information Ratio
0.2
Tracking Error
0.26
Treynor Ratio
0.367
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.03m;  // Percentage below high watermark before we flip our position
        decimal targetProfit = 0.02m;               //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(14);
        
        
        //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 > 70) {
                    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"));
        }
    }
}
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 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 RelativeStrengthIndexCustom(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++;
            }
        }
        
    }
}