Overall Statistics
//Copyright Warren Harding 2016, granted to the public domain.
//Use entirely at your own risk.
//Custom algorithm development: warrencharding@yahoo.com.
//Do not remove this copyright notice.

using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using QuantConnect.Data.Market;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.Slippage;
using QuantConnect.Securities;
namespace QuantConnect
{
    public class Algo4 : QCAlgorithm
    {
    	//You can adjust this first set to optimize.
    	decimal lowerBandRatio = 0.8m;
    	decimal upperBandRatio = 1m;
    	decimal rsiBuyCutoff=50;
    	decimal dollarVolumeCutoff = 1000000;
    	decimal maximumTrade=25000;
    	decimal minimumTrade = 500m;
    	private const int takeCount = 10;
        private SecurityChanges securityChanges = SecurityChanges.None;
    	int shortMaPeriod=1;
    	int longMaPeriod=20;
    	int rsiPeriod=3;
    	//Your broker is going to hate you if you set these too low as it will result in large amounts of unfilled
    	//order cancellations. It bogs down LEAN as well.
    	int barsToHoldBuyOrdersFor=0;
    	int barsToHoldSellOrdersFor=0;
    	
    	Resolution resolution = Resolution.Daily;
    	
    	decimal ratioOfDollarVolumeForMaxTrade;

		ConcurrentDictionary<Symbol, StockData> universeDictionary = new ConcurrentDictionary<Symbol, StockData>();
		List<OrderTicketWrapper> buyOrders=new List<OrderTicketWrapper>();
		List<OrderTicketWrapper> sellOrders=new List<OrderTicketWrapper>();
        
        public override void Initialize()
        {
            SetStartDate(2015, 9, 30);
			SetEndDate(2016, 9, 30);
            SetCash(100000);
            
            AddUniverse(coarse =>
            {
                return (from c in coarse
                        let stockData = universeDictionary.GetOrAdd(c.Symbol, sym => new StockData(c.Symbol,shortMaPeriod,longMaPeriod,rsiPeriod))
                        where stockData.Update(c.EndTime, c.Price)
                        where c.DollarVolume > dollarVolumeCutoff
                        where stockData.RSI>rsiBuyCutoff
                        where (1-stockData.MovingAverageDifferenceRatio) < lowerBandRatio
                        orderby stockData.MovingAverageDifferenceRatio descending
                        select c.Symbol).Take(takeCount);
            });
        
           	//Be careful adjusting this next one, too high of a setting will result in unrealistically large
    		//purchases being made with no regards for slippage.    
        	if (resolution == Resolution.Daily)
	    	{
	    		ratioOfDollarVolumeForMaxTrade = .25m / 6.5m / 60m;
	    	}
	    	else if (resolution==Resolution.Minute)
	    	{
	    		ratioOfDollarVolumeForMaxTrade = .25m;
	    	}
	    	
        }
		
        public void OnData(TradeBars data)
        {
			Buy(data);
			Sell(data);
        }
        
        public void Buy(TradeBars data)
        {
            CancelExpiredOrders(buyOrders,barsToHoldBuyOrdersFor);
        	int quantity = 0;
            decimal buyPrice;
            TradeBar bar;
            string ticker;
    	    foreach (var security in securityChanges.AddedSecurities)
            {
            	ticker=security.Symbol.ToString();
            	if (!Portfolio[security.Symbol].HoldStock & data.ContainsKey(ticker))
            	{
            		bar=data[ticker];
	            	quantity=SizePosition(bar);
	            	if (quantity > 0)
	            	{
	            		buyPrice=bar.Close;
	            		OrderTicketWrapper orderTicketWrapper=new OrderTicketWrapper();
	                    orderTicketWrapper.orderTicket = LimitOrder(bar.Symbol, quantity,buyPrice);
	                    orderTicketWrapper.price=buyPrice;
	                    buyOrders.Add(orderTicketWrapper);
	            	}
            	}
            }
            foreach (OrderTicketWrapper orderTickerWrapper in buyOrders)
            {
                orderTickerWrapper.count++;
            }
        }
        
        public void Sell(TradeBars data)
        {
        	CancelExpiredOrders(sellOrders,barsToHoldSellOrdersFor);
        	decimal sellPrice;
    	    TradeBar bar;
            
            foreach (SecurityHolding stock in Portfolio.Values)
            {
                if (Portfolio[stock.Symbol].Quantity > 0 & data.ContainsKey(stock.Symbol))
                {
                    bar = data[stock.Symbol];

                	sellPrice = universeDictionary[stock.Symbol].LongEMA * upperBandRatio;
                	if (bar.Close > sellPrice)
                	{
                    	sellPrice = bar.Close;
                    	OrderTicketWrapper orderTicketWrapper = new OrderTicketWrapper();
                        orderTicketWrapper.orderTicket = LimitOrder(stock.Symbol, -Portfolio[stock.Symbol].Quantity, sellPrice);
                        orderTicketWrapper.price=sellPrice;
                        sellOrders.Add(orderTicketWrapper);
                	}

                }
            }
            foreach (OrderTicketWrapper orderTickerWrapper in sellOrders)
            {
                orderTickerWrapper.count++;
            }
        }
        
        public override void OnSecuritiesChanged(SecurityChanges changes)
        {
            securityChanges = changes;
        }
        
        static void CancelExpiredOrders(List<OrderTicketWrapper> orderTickerWrappers,int barsToHoldOrdersFor)
        {
        	foreach (OrderTicketWrapper orderTickerWrapper in orderTickerWrappers)
        	{
			    if (orderTickerWrapper.count>barsToHoldOrdersFor)
	        	{
	        		orderTickerWrapper.orderTicket.Cancel();
	        	}
        	}
        	orderTickerWrappers.RemoveAll(x=>x.orderTicket.Status==OrderStatus.Filled | x.orderTicket.Status==OrderStatus.Canceled);
        }
        
        
        static decimal SumBuyOrders(List<OrderTicketWrapper> buyOrders)
        {
        	decimal sum=0;
            foreach (OrderTicketWrapper orderTickerWrapper in buyOrders)
            {
                sum += orderTickerWrapper.orderTicket.Quantity * orderTickerWrapper.price;
            }
            return sum;
        }
        
        int SizePosition(TradeBar bar)
        {
            decimal maxTrade=bar.Close*bar.Volume*ratioOfDollarVolumeForMaxTrade;
            if (maxTrade>maximumTrade)
            {
            	maxTrade=maximumTrade;
            }
            int quantity =(int)Math.Floor(Math.Min(Portfolio.Cash-SumBuyOrders(buyOrders), maxTrade) / bar.Close);
            quantity = RoundLot(quantity);
            if (quantity * bar.Close < minimumTrade)
        	{
        		return 0;
        	}
        	return quantity;
        }
        
        static int RoundLot(int inOddLotQuantity)
        {
            decimal inQuantity = (decimal)inOddLotQuantity;
            if (inQuantity > 2000000)
            {
                decimal small = inQuantity / 1000000;
                small = Math.Floor(small);
                return (int)(small * 1000000);
            }
            if (inQuantity > 200000)
            {
                decimal small = inQuantity / 100000;
                small = Math.Floor(small);
                return (int)(small * 100000);
            }
            if (inQuantity > 20000)
            {
                decimal small = inQuantity / 10000;
                small = Math.Floor(small);
                return (int)(small * 10000);
            }
            if (inQuantity > 2000)
            {
                decimal small = inQuantity / 1000;
                small = Math.Floor(small);
                return (int)(small * 1000);
            }
            if (inQuantity > 200)
            {
                decimal small = inQuantity / 100;
                small = Math.Floor(small);
                return (int)(small * 100);
            }
            if (inQuantity > 20)
            {
                decimal small = inQuantity / 10;
                small = Math.Floor(small);
                return (int)(small * 10);
            }
            return inOddLotQuantity;
        }
       
       	class OrderTicketWrapper
		{
			public OrderTicket orderTicket;
			public int count=0;
			public decimal price;
		}
       
        class StockData
        {
        	public StockData(string ticker,int shortMAPeriod,int longMAPeriod,int RsiPeriod)
        	{
	      		Ticker=ticker;
	            ShortEMA = new ExponentialMovingAverage(shortMAPeriod);
	            LongEMA = new ExponentialMovingAverage(longMAPeriod);
				RSI = new RelativeStrengthIndex(RsiPeriod,MovingAverageType.Exponential);
        	}
        	
        	public string Ticker;
            public ExponentialMovingAverage ShortEMA;
            public ExponentialMovingAverage LongEMA;
			public RelativeStrengthIndex RSI;

            public decimal MovingAverageDifferenceRatio
            {
                get
                {
                	return (LongEMA - ShortEMA)/ShortEMA;
               	}
            }

            public bool Update(DateTime time, decimal value)
            {
                return ShortEMA.Update(time, value) && LongEMA.Update(time, value) && RSI.Update(time, value);
            }
        }
    }
}