Overall Statistics
Total Trades
2162
Average Win
0.47%
Average Loss
-3.34%
Compounding Annual Return
166.046%
Drawdown
17.300%
Expectancy
0.140
Net Profit
966.588%
Sharpe Ratio
1.15
Loss Rate
0%
Win Rate
100%
Profit-Loss Ratio
0.14
Alpha
0.955
Beta
0.659
Annual Standard Deviation
0.903
Annual Variance
0.815
Information Ratio
1.012
Tracking Error
0.901
Treynor Ratio
1.576
Total Fees
$0.00
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using QuantConnect.Orders;
using QuantConnect.Orders.Slippage;
using QuantConnect.Securities;

namespace QuantConnect.Algorithm.CSharp
{
    /// <summary>
    /// Bottom Fishing of Penny Stocks.
    /// </summary>
    public class PennyStocksBottomFishingAlgorithm : QCAlgorithm
    {
        static int daysInSlowEma = 45;
        static int daysInFastEma = 3;
        static int daysIn20Ema = 20;

        // minimal stock price in dollars
        private readonly decimal minStockPrice = 0.4m;
        private readonly decimal maxStockPrice = 2.0m;

        private readonly decimal minDollarVolume = 100000;

        // moving averages by stock symbol
        private readonly ConcurrentDictionary<Symbol, MovingAverages> averages = new ConcurrentDictionary<Symbol, MovingAverages>();
        private readonly int maxNumberOfCandidates = 100;
        private readonly decimal maxBuyOrdersAtOnce = 10;
        List<Symbol> candidates = new List<Symbol>();

        private class MovingAverages
        {
            public readonly ExponentialMovingAverage Fast;
            public readonly ExponentialMovingAverage Slow;
            public readonly ExponentialMovingAverage ema20;

            public MovingAverages()
            {
                Fast = new ExponentialMovingAverage(daysInFastEma);
                Slow = new ExponentialMovingAverage(daysInSlowEma);
                ema20 = new ExponentialMovingAverage(daysIn20Ema);
            }

            // updates the EMA50 and EMA100 indicators, returning true when they're both ready
            public bool Update(DateTime time, decimal value)
            {
                return Fast.Update(time, value) && Slow.Update(time, value) && ema20.Update(time, value);
            }
        }

        Symbol benchmark = QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA);
        ISlippageModel slippageModel;

        // Custom slippage implementation
        public class CustomSlippageModel : ISlippageModel
        {
            private readonly QCAlgorithm _algorithm;

            public CustomSlippageModel(QCAlgorithm algorithm)
            {
                _algorithm = algorithm;
            }

            public decimal GetSlippageApproximation(Security asset, Order order)
            {
                return 0;
            }
        }

        /// <summary>
        /// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
        /// </summary>
        public override void Initialize()
        {
            UniverseSettings.Resolution = Resolution.Daily;

            this.SetBrokerageModel(BrokerageName.Alpaca, AccountType.Cash);
            this.slippageModel = new CustomSlippageModel(this);

            SetStartDate(2016, 1, 1);
            SetEndDate(2018, 6, 1);
            SetCash(1000);

            AddUniverse(CoarseSelectionFunction, FineSelectionFunction);

            // TODO age

            this.AddEquity(benchmark);
            this.SetBenchmark(benchmark);
            this.Log("TODO");

            Schedule.On(DateRules.EveryDay("SPY"), TimeRules.BeforeMarketClose("SPY", 1), () =>
            {
                if (this.Securities["SPY"].Exchange.ExchangeOpen)
                {
                    //                    this.Log("BeforeMarketClose");
                    CancelAllOpenOrders();
                }
            });

            Schedule.On(DateRules.EveryDay("SPY"), TimeRules.Every(TimeSpan.FromMinutes(105)), () =>
            {
                if (this.Securities["SPY"].Exchange.ExchangeOpen)
                {
                    //                   this.Log("Every 105 minutes");
                    Rebalance();
                }
            });
        }

        decimal BuyFactor = .9995m;
        decimal SellFactor = 1.005m;

        void Rebalance()
        {
            CancelAllOpenOrders();

            TryToSellExistingPositions();

            TryToBuy();
        }

        void CancelOpenBuyOrders()
        {
            var submittedTickets = Transactions.GetOrderTickets(
                  t => t.Status == OrderStatus.Submitted && t.Quantity > 0);

            CancelOrders(submittedTickets);
        }

        bool HasOpenBuyOrders(Symbol symbol)
        {
            return Transactions.GetOrderTickets(
                  t => t.Status == OrderStatus.Submitted && t.Quantity > 0 && t.Symbol == symbol).Count() > 0;
        }

        void CancelAllOpenOrders()
        {
            var submittedTickets = Transactions.GetOrderTickets(
                  t => t.Status == OrderStatus.Submitted);

            CancelOrders(submittedTickets);
        }

        void CancelOrders(IEnumerable<OrderTicket> tickets)
        {
            foreach (OrderTicket ticket in tickets)
            {
                this.Log("Cancelling order for: " + ticket.Symbol);
                ticket.Cancel();
            }
        }

        void TryToSellExistingPositions()
        {
            // Order sell at profit target in hope that somebody actually buys it
            foreach (SecurityHolding security in this.Portfolio.Values)
            {
                if (security.Invested)
                {
                    // TODO remove
                    if (!HasOpenBuyOrders(security.Symbol))
                    {
                        var costBasis = this.Portfolio[security.Symbol].AveragePrice;
                        // TODO age 
                        var sellPrice = make_div_by_05(costBasis * SellFactor, false);
                        var quantity = -this.Portfolio[security.Symbol].Quantity;

                        this.Log("Sell order for: " + security.Symbol + ", quantity=" + quantity + ", sellPrice=" + sellPrice);
                        this.LimitOrder(security.Symbol, quantity, sellPrice);
                    }
                }
            }
        }

        // if cents not divisible by .05, round down if buy, round up if sell
        decimal make_div_by_05(decimal s, bool buy = false)
        {
            s *= 20.00m;
            if (buy)
            {
                s = Math.Floor(s);
            }
            else
            {
                s = Math.Ceiling(s);
            }

            s /= 20.00m;

            return s;
        }

        void TryToBuy()
        {
            decimal weight = 1.00m / this.maxBuyOrdersAtOnce;

            int numberOfOrders = 0;
            foreach (Symbol candidate in this.candidates)
            {
                decimal averagePrice = this.GetOrCreateAverage(candidate).ema20;
                decimal currentPrice = this.Securities[candidate].Price;
                decimal buyPrice = currentPrice;

                if (currentPrice <= 1.25m * averagePrice)
                {
                    buyPrice = currentPrice * BuyFactor;
                }

                buyPrice = make_div_by_05(buyPrice, true);
                if (buyPrice > 0)
                {
                    int quantity = (int)Math.Round(weight * this.Portfolio.Cash / buyPrice, MidpointRounding.ToEven);
                    if (quantity > 0)
                    {
                        this.Log("Buy order for: " + candidate + ", quantity=" + quantity + ", buyPrice=" + buyPrice);
                        this.LimitOrder(candidate, quantity, buyPrice);

                        numberOfOrders++;
                        if (numberOfOrders >= this.maxBuyOrdersAtOnce)
                        {
                            break;
                        }
                    }
                }
            }
        }

        public IEnumerable<Symbol> CoarseSelectionFunction(IEnumerable<CoarseFundamental> coarse)
        {
            var selectedByPriceAndVolume = (from stock in coarse
                                            where stock.Price >= this.minStockPrice
                                            where stock.Price <= this.maxStockPrice
                                            where stock.DollarVolume >= this.minDollarVolume
                                            where stock.HasFundamentalData
                                            select stock);

            return SelectWorstPerformingStocks(selectedByPriceAndVolume).Select(stock => stock.Symbol);
        }

        public IEnumerable<Symbol> FineSelectionFunction(IEnumerable<FineFundamental> fine)
        {
            var selectedStocks = (from stock in fine
                                  where stock.CompanyReference.CountryId == "USA"
                                  where !stock.CompanyReference.IsLimitedPartnership
                                  where !stock.CompanyReference.IsREIT
                                  where stock.SecurityReference.ExchangeId != "OTC"
                                  where !stock.SecurityReference.IsDepositaryReceipt
                                  where stock.SecurityReference.ShareClassStatus == "A"
                                  where stock.SecurityReference.SecurityType == "ST00000001"
                                  select stock).Select(stock => stock.Symbol);

            this.candidates.Clear();

            foreach (Symbol selectedStock in selectedStocks)
            {
                this.AddEquity(selectedStock);
                Security security = this.Securities[selectedStock];

                if (security.Price >= this.minStockPrice && security.Price <= this.maxStockPrice)
                {
                    this.candidates.Add(selectedStock);
                    this.Securities[selectedStock].SetSlippageModel(this.slippageModel);
                }

                //                this.Log("Adding candidate: " + selectedStock + ", "+ security.Exchange +", "+ security.Price + ", " + security.AskPrice);
            }

            return this.candidates;
        }

        private IEnumerable<CoarseFundamental> SelectWorstPerformingStocks(IEnumerable<CoarseFundamental> coarse)
        {
            List<CoarseFundamental> selected = new List<CoarseFundamental>();

            // select stocks with fast EMA below slow EMA
            foreach (CoarseFundamental stock in coarse)
            {
                // create or update averages for the stock
                MovingAverages average = GetOrCreateAverage(stock.Symbol);

                // if indicators are ready
                if (average.Update(stock.EndTime, stock.Price))
                {
                    // if fast EMA is below slow EMA
                    if (average.Fast < average.Slow)
                    {
                        selected.Add(stock);
                    }
                }
            }

            return selected.OrderBy(stock => CalculatePercentageDifference(stock.Symbol)).Take(this.maxNumberOfCandidates);
        }

        private decimal CalculatePercentageDifference(Symbol symbol)
        {
            var average = GetOrCreateAverage(symbol);
            decimal difference = (average.Fast - average.Slow) / average.Slow;

            return difference;
        }

        private MovingAverages GetOrCreateAverage(Symbol symbol)
        {
            MovingAverages average;
            if (this.averages.ContainsKey(symbol))
            {
                this.averages.TryGetValue(symbol, out average);
            }
            else
            {
                average = new MovingAverages();
                this.averages.TryAdd(symbol, average);
            }

            return average;
        }
    }
}