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; } } }