Overall Statistics
Total Trades
5
Average Win
0%
Average Loss
-2.89%
Compounding Annual Return
-28.067%
Drawdown
35.600%
Expectancy
-1
Net Profit
-23.165%
Sharpe Ratio
-0.852
Loss Rate
100%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
-0.23
Beta
0.007
Annual Standard Deviation
0.273
Annual Variance
0.075
Information Ratio
0.445
Tracking Error
0.401
Treynor Ratio
-33.52
Total Fees
$14.21
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Data.Custom;
using QuantConnect.Data.Market;
using System;
using System.Collections;
using System.Collections.Generic;
using QuantConnect.Indicators;
using QuantConnect.Data.Consolidators;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;

using QuantConnect.Securities;

namespace QuantConnect
{
    /*
    *   QuantConnect University: Full Basic Template:
    *
    *   The underlying QCAlgorithm class is full of helper methods which enable you to use QuantConnect.
    *   We have explained some of these here, but the full algorithm can be found at:
    *   https://github.com/QuantConnect/QCAlgorithm/blob/master/QuantConnect.Algorithm/QCAlgorithm.cs
    */
    public class BasicTemplateAlgorithm : QCAlgorithm
    {
        //Initializers:
        int onDataTick = 0;
        int logTick = 71000;
        // DateTime logDate = new DateTime(2008, 9, 22);
        double dayCount = 0;
        bool entryPoint = false;
        int timeTick = 0;
        int timeTickMod;
        TradeBars prices = new TradeBars();

        ArrayList symbols = new ArrayList();

        DataDictionary<CreditDefaultSwap> swaps = new DataDictionary<CreditDefaultSwap>();
        int sellcount = 0;
        Chart plotter = new Chart("Strategy Equity");
        
        //Init data and resolution for strategy:
        public override void Initialize()
        {
            //Start and End Date range for the backtest:
            SetStartDate(2008, 1, 5); timeTickMod = 2000;
            SetEndDate(2008, 10, 22);
            
            
            //SetEndDate(DateTime.Now.Date.AddDays(-400));
            //SetEndDate(DateTime.Now.Date.AddDays(-1));

            //Cash allocation
            SetCash(100000);

            symbols.Add("XOM");
            symbols.Add("CVX"); // XOM/CVX 2yr 65%  24,1,68,32  |  14,77,52,48  |  14,1,78,22
            symbols.Add("HAL");
            //symbols.Add("GLOP");
            //symbols.Add("LNG"); // SMLP
            //symbols.Add("APC"); // ANADARKO
            //symbols.Add("DVN"); // devon
            //symbols.Add("ECA"); // encana
            //symbols.Add("HAL");
            //symbols.Add("BHI");
            //symbols.Add("MTRX");
            //symbols.Add("SMLP");

            foreach (string symbol in symbols)
            {
                AddSecurity(SecurityType.Equity, symbol, Resolution.Minute);
                //Securities[symbol].SlippageModel = new ConstantSlippageModel(0.001m);
                Securities[symbol].SetLeverage(1m); // we are not using leverage
            }

            //plotter.AddSeries(new Series("SOXX", SeriesType.Line, 0));
            // SeriesType.Scatter SeriesType.Flag
            Series trades = new Series("trade", SeriesType.Scatter, 0);
            trades.Color = System.Drawing.Color.LightCoral;
            plotter.AddSeries(trades); // , "$", System.Drawing.Color.LightCoral));
            AddChart(plotter);
        }

        //Data Event Handler: New data arrives here. "TradeBars" type is a dictionary of strings so you can access it by symbol.
        public void OnData(TradeBars data)
        {
            // in OnData, returns outside of 9:30am - 4pm
            //if (Time.Hour < 9 || (Time.Hour == 9 && Time.Minute < 30)) return;
            //if ( Time.Hour >= 16) return;

            // "TradeBars" object holds many "TradeBar" objects: it is a dictionary indexed by the symbol:
            //Save price data:
            foreach (string symbol in data.Keys)
            {
                if (!prices.ContainsKey(symbol))
                {
                    prices.Add(symbol, data[symbol]);
                    plotter.AddSeries(new Series(symbol, SeriesType.Line, 0));
                    Log("init symbol " + symbol);

                }
                else
                {
                    prices[symbol] = data[symbol];
                }
            }
            
            if ( (timeTick % 10000) == 0 )
            {
                LogHoldings("status");
            }
            timeTick++;

            // initial (dumb) entryPoint... buy equal amounts of each stock
            if (!entryPoint)
            {
                entryPoint = true;
                int count = data.Keys.Count;
                foreach (string entrySym in data.Keys)
                {
                    Order(entrySym, 99000 / (count * data[entrySym].Price));
                    LogHoldings("entry");
                }
            }

            decimal sellQuant = 30000;

            TradeBar targ = data["XOM"];
            TradeBar r1 = data["HAL"];

            if ( onDataTick > logTick )
            {
                string reason = "SellHi " + targ.Symbol + " p " + String.Format("{0:0.00}", targ.Price) + " q " + sellQuant + " B " + r1.Symbol + "@" + String.Format("{0:0.00}", r1.Price);
                CreditDefaultSwap(targ, sellQuant, r1, reason);
            }

            targ = data["CVX"];
            r1 = data["HAL"];

            if ( onDataTick > logTick )
            {
                string reason = "SellHi " + targ.Symbol + " p " + String.Format("{0:0.00}", targ.Price) + " q " + sellQuant + " B " + r1.Symbol + "@" + String.Format("{0:0.00}", r1.Price);
                CreditDefaultSwap(targ, sellQuant, r1, reason);
            }

            ArrayList removeKeys = new ArrayList();
            foreach (string swapkey in swaps.Keys)
            {
                CreditDefaultSwap swap = swaps[swapkey];
                swap.buyme = prices[swap.buyme.Symbol];
                swap.sellme = prices[swap.sellme.Symbol];
                UpdateSell(swap);
                UpdateBuy(swap);
                

                
                if (swap.buyTicket == null && swap.sellQuant == 0)
                {
                	LogHoldings("done " + swap.reason);
                	Plot("Strategy Equity", "trade", prices[swap.buyme.Symbol].Close );
                    removeKeys.Add(swapkey);
                }

            }
            foreach (string swapkey in removeKeys)
            {
                swaps.Remove(swapkey);
            }
            
            if ( removeKeys.Count > 0  && swaps.Count > 0 )
            {
            	
            	Log( "dead swaps? " + swaps.Count );
            	foreach (string swapkey in swaps.Keys)
            	{
            	    CreditDefaultSwap swap = swaps[swapkey];
                    UpdateSell(swap);
                    UpdateBuy(swap);
                    Log( "Cancel dead swaps?" );
                    CancelSwapSynchronously(swap);
            	}
            	
            }

            onDataTick++;
        }

        // Requirements:
        // Jeremy Irons: "We are selling to willing buyers at the current fair market price."
        // To maximize profit, we must achieve these conflicting goals:
        // - sellme as quickly as possible using a limit order near the current price.
        // - once sellme limit order is filled, buyme as quickly as possible using a limit order.
        // - we must limit total slippage to 0.2%, which gives us 0.1% on the sell side and 0.1% on the buy side.
        // - if slippage > 0.2% this strategy fails, since we are making ~50 swaps per year.
        //   ( 50 swaps per year * 0.2% equates to 10% )
        // - this strategy fails when spreads are high, so if this is a small/mid cap stock,
        //   can only trade from 9:30 to 9:35, or 3:55 to 3:58, or during high volume periods when spread is low.
        // - It is imperative that we are long every day at 3:59, otherwise we miss out on upside news.
        // - limit orders are executed asynchronously so each phase must be monitored on a tick-by-tick basis
        void CreditDefaultSwap(TradeBar sellme, decimal sellQuant, TradeBar buyme, string reason)
        {
        	foreach ( string swapKey in swaps.Keys )
        	{
        		CreditDefaultSwap inplay = swaps[swapKey];
        		if ( (sellme.Symbol == inplay.buyme.Symbol) ||
        		     (buyme.Symbol == inplay.sellme.Symbol))
        		{
        			Log( "???uhoh inplay inversion! S " + sellme.Symbol + " B " + buyme.Symbol );
        			return;
        		}
        		
        		if (inplay.buyme.Symbol != buyme.Symbol )
                {
                	if ( logTick > 0 && onDataTick >= logTick )
                       Log("inplay but buying different " + inplay.buyme.Symbol + " " + buyme.Symbol );
                    return;
                }
                
                if ( inplay.sellme.Symbol != sellme.Symbol )
                {
                	if ( logTick > 0 && onDataTick >= logTick )
                       Log("inplay but selling different " + inplay.sellme.Symbol + " " + sellme.Symbol );
                    return;
                }
                
                if ( logTick > 0 && onDataTick >= logTick )
                   Log("inplay " + inplay.sellme.Symbol + " " + inplay.buyme + " " + sellme.Symbol + " " + buyme.Symbol );
            }
            CreditDefaultSwap swap = null;

            /////////////////// SELL phase of swap ////////////////////////////
            int remainQ = Portfolio.Securities[sellme.Symbol].Holdings.Quantity;
            if (remainQ >= 100) // short this dog
            {
                if (sellQuant > remainQ) sellQuant = remainQ;

                bool needNewSell = true;
                if (swaps.ContainsKey(sellme.Symbol))
                    swap = swaps[sellme.Symbol];
                else
                    swap = new CreditDefaultSwap();
                swap.sellme = sellme; // self ref MUST be non-null
                swap.buyme = buyme;
                if (swap.sellTicket != null)
                {
                    if (swap.sellTicket.Status == OrderStatus.Filled)
                    {
                        Log(" SQ continuing, sold " + swap.sellTicket.QuantityFilled + " " + sellme.Symbol + " " +  String.Format("{0:0.00}", swap.sellTicket.AverageFillPrice));
                        swap.sellTicket = null;
                        // tbd needs work
                        // need to continue selling remaining shares
                    }
                    else
                        needNewSell = false;
                }

                if (needNewSell)
                {
                    swap.reason = reason;
                    swap.sellPrice = sellme.Close * 0.999m;
                    swap.sellQuant = sellQuant;
                    swap.sellTicket = LimitOrder(sellme.Symbol, -(int)sellQuant, swap.sellPrice);
                    swap.firstSellTick = 0;

                    if (swap.sellTicket.Status == OrderStatus.Invalid)
                    {
                        swap.sellQuant = 0;
                        swap.sellTicket = null;
                        if (swap.buyTicket == null)
                            swap = null; // abort this swap
                    }
                    else
                    {
                        if (!swaps.ContainsKey(sellme.Symbol))
                            swaps.Add(sellme.Symbol, swap);
                    }
                }

                UpdateSell(swap);
                /* original moronic market order:
                Sell(targ.Symbol, sellQuant);
                sellcount = Time.Hour;
                */
            }



            ////////////////////// BUY phase of swap //////////////////////////////////
            // The buy phase is tricky... need to wait until the asynch sell phase
            // complete.  
            int quantity = (int)Math.Floor((Portfolio.Cash - 100) / buyme.Price);
            bool needBuy = (quantity >= 100);

            // However, even if sell fails, we may have cash available so go long now!
            if (needBuy)
            {
                if (swaps.ContainsKey(sellme.Symbol))
                {
                    swap = swaps[sellme.Symbol];
                }
                else
                {
                    swap = new CreditDefaultSwap();
                    swap.buyPrice = buyme.Close * 1.001m;
                    //if ( onDataTick > logTick )
                       Log( " buyPrice " + swap.buyPrice );

                    swaps.Add(sellme.Symbol, swap);
                }

                swap.reason = reason;
                swap.buyme = buyme;
                swap.sellme = sellme;
            }

            UpdateBuy(swap);

            if (swap != null && swap.buyQuant == 0 && swap.sellQuant == 0)
            {
                Log("done immediate? " + swap.reason);
                if (swaps.ContainsKey(sellme.Symbol))
                    swaps.Remove(sellme.Symbol);
            }


            /* original moronic market order
            if (quantity >= 100)
                Order(buyme.Symbol, quantity);
            */
        }

        public void UpdateSell(CreditDefaultSwap swap)
        {
            if (swap == null) return;
            if (swap.sellQuant == 0) return;

            switch (swap.sellTicket.Status)
            {
                case OrderStatus.Canceled:
                case OrderStatus.Invalid:
                    Log(" !!! Sell Cancelled or invalid " + swap.sellTicket.Status + " " + swap.sellQuant + " " + swap.reason);
                    swap.sellTicket = null;
                    swap.sellQuant = 0; // abort sell
                    break;

                case OrderStatus.Filled:
                    //if ( onDataTick > logTick )
                       Log(" SQ done " + swap.sellTicket.QuantityFilled + " " + swap.sellme.Symbol + " @ " +  String.Format("{0:0.00}", swap.sellTicket.AverageFillPrice));
                    swap.sellQuant = 0; // "phase 1 complete.  now on to phase 2... clean you two."
                    break;

                default: // like fishing "whoa-oa-oa... you almost had it"
                    // gets complicated...
                    if (swap.tick == onDataTick) return;
                    if (swap.firstSellTick == 0) swap.firstSellTick = onDataTick;
                    swap.tick = onDataTick;

                    int age = swap.tick - swap.firstSellTick;
                    if (age == 0) return; // not done
                    
                    swap.tick = onDataTick;
                    swap.sellPrice *= 0.999m;

                    if (age > 2)
                    {
                        if (age > 4)
                        {
                            Log("  Sell CANCEL... " + swap.sellQuant + " " + swap.sellme.Symbol + " p " +  String.Format("{0:0.00}", swap.sellPrice));
                            
                            Log( "Cancel sell age > 4" );
                            CancelSwapSynchronously(swap);
                            return;
                        }
                        else
                        {
                        	//if ( onDataTick > logTick )
                                Log("  Sell fishing... " + swap.sellQuant + " " + swap.sellme.Symbol + " p " +  String.Format("{0:0.00}", swap.sellPrice) );
                        }
                    }

                    swap.sellTicket.Update(new UpdateOrderFields
                    {
                        // we could change the quantity, but need to specify it
                        //Quantity = 

                        LimitPrice = swap.sellPrice,
                        Tag = "Update #" + (swap.sellTicket.UpdateRequests.Count + 1)
                    });
                    break;
            }
        }

        public void UpdateBuy(CreditDefaultSwap swap)
        {
            if (swap == null) return;

            int quantity = (int)Math.Floor((Portfolio.Cash - 100) / (swap.buyme.Close * 1.001m));
            quantity -= quantity % 100;
            bool needBuy = (quantity >= 100);

            if (needBuy)
            {
                swap.buyQuant = quantity;
                if (swap.buyTicket == null)
                {
                    swap.buyPrice = swap.buyme.Close * 1.001m; // tbd need to update with fresh data
                    swap.buyTicket = LimitOrder(swap.buyme.Symbol, (int)swap.buyQuant, swap.buyPrice);
                    swap.firstBuyTick = 0;
                    Log( "firstBuyTick " + swap.buyme.Symbol + " " + swap.buyPrice + " Q " + swap.buyQuant );
                }
            }

            if (swap.buyTicket != null)
            {
            	if ( logTick > 0 && onDataTick >= logTick )
            	{
            		LogHoldings("B ");
            	}
                switch (swap.buyTicket.Status)
                {
                    case OrderStatus.Canceled:
                    case OrderStatus.Invalid:
                        Log(" !!! buy Cancelled or invalid " + swap.buyTicket.Status + " " + swap.buyQuant + " " + swap.reason);
                        swap.buyQuant = 0;
                        swap.buyTicket = null;
                        break;

                    case OrderStatus.Filled:
                        //if ( onDataTick > logTick )
                            Log(" BQ " + swap.buyme.Symbol + " " + swap.buyQuant + " @ " +  String.Format("{0:0.00}", swap.buyTicket.AverageFillPrice) );
                        swap.buyQuant = 0;
                        swap.buyTicket = null;
                        break;

                    default:
                        // gets complicated
                        if (swap.tick == onDataTick) return;
                        if (swap.firstBuyTick == 0) swap.firstBuyTick = onDataTick;
                        swap.tick = onDataTick;

                        int age = swap.tick - swap.firstBuyTick;
                        if (age == 0) return; // not done
                        swap.buyPrice *= 1.001m;
                        Log( "buyTick " + swap.buyme.Symbol + " " + swap.buyPrice );
                        if (age > 2)
                        {

                            if (age > 4)
                            {
                                Log("  buy  CANCEL... " + swap.buyQuant + " " + swap.buyme.Symbol + " p " + swap.buyPrice);
                                CancelSwapSynchronously(swap);
                                return;
                            }
                        }
                        
                        //if ( onDataTick > logTick )
                            Log("  buy  fishing... " + swap.buyQuant + " " + swap.buyme.Symbol + " p " + swap.buyPrice);


                        int newq = (int)Math.Floor((Portfolio.Cash - 100) / swap.buyPrice);
                        if (newq >= 100)
                        {
                            newq -= newq % 100;
                        }
                        
                        swap.buyTicket.Update(new UpdateOrderFields
                        {
                            // we could change the quantity, but need to specify it
                            Quantity = newq,

                            LimitPrice = swap.buyPrice,
                            Tag = "Update #" + (swap.buyTicket.UpdateRequests.Count + 1)
                        });
                        break;

                }
            }
        }
        
        public void CancelSwapSynchronously(CreditDefaultSwap swap)
        {
            while ( swap.buyTicket != null &&
                   swap.sellTicket != null )
            {
                if ( swap.buyTicket != null )
                {
                    swap.buyTicket.Cancel();
                    swap.buyTicket.OrderClosed.WaitOne(1);
                    
                    switch ( swap.buyTicket.Status )
                    {
                        case OrderStatus.Canceled:
                        case OrderStatus.Invalid:
                            Log(" !!! buy Cancelled or invalid " + swap.buyQuant + " " + swap.reason);
                            swap.buyQuant = 0;
                            swap.buyTicket = null;
                            return;
                            
                        case OrderStatus.Filled:
                            //if ( onDataTick > logTick )
                            Log(" BQ " + swap.buyme.Symbol + " " + swap.buyQuant + " @ " +  String.Format("{0:0.00}", swap.buyTicket.AverageFillPrice) );
                            swap.buyQuant = 0;
                            swap.buyTicket = null;
                            return;
                            
                        default:
                            Log("swap.buyTicket " + swap.buyTicket.Status );
                            break;
                    }
                }
                
                if ( swap.sellTicket != null )
                {
                    swap.sellTicket.Cancel();
                    swap.sellTicket.OrderClosed.WaitOne(1);
                    
                    switch ( swap.sellTicket.Status )
                    {
                        case OrderStatus.Canceled:
                        case OrderStatus.Invalid:
                            Log(" !!! sell Cancelled or invalid " + swap.sellQuant + " " + swap.reason);
                            swap.sellQuant = 0;
                            swap.sellTicket = null;
                            return;
                            
                        case OrderStatus.Filled:
                            //if ( onDataTick > logTick )
                            Log(" BQ " + swap.sellme.Symbol + " " + swap.sellQuant + " @ " +  String.Format("{0:0.00}", swap.sellTicket.AverageFillPrice) );
                            swap.sellQuant = 0;
                            swap.sellTicket = null;
                            return;
                            
                        default:
                            Log("swap.sellTicket " + swap.sellTicket.Status );
                            break;
                    }
                }
            }
        }
        
        public void LogHoldings(string tag)
        {
        	string logstr = "";
        	foreach ( string symbol in symbols )
            {
                if ( Portfolio.Securities[symbol].Holdings.Quantity > 0 )
                {
                    logstr += symbol + " " + Portfolio.Securities[symbol].Holdings.Quantity + " @" + String.Format("{0:0.00}", prices[symbol].Price);
                }
            }
            Log(tag + " cash "  + String.Format("{0:0.00}", Portfolio.Cash)  + " margin "  + String.Format("{0:0.00}", Portfolio.MarginRemaining) + " " + logstr);
        }

        public void OnData(Quandl data)
        {
            //Debug("quandl data " + data.Price);
        }

        public override void OnEndOfDay()
        {
            dayCount++;
            sellcount = 0;
        }
    }
    
    // swap one security for another.
    public class CreditDefaultSwap // mockingly... no relation to a real-world CDS.
    {
        public int firstBuyTick;
        public int firstSellTick;
        public int tick;
        public string reason; // for logging

        public TradeBar sellme;
        public decimal sellPrice;
        public decimal sellQuant;
        public OrderTicket sellTicket;

        public TradeBar buyme;
        public decimal buyPrice;
        public decimal buyQuant;
        public OrderTicket buyTicket;
    }
}