Overall Statistics |
Total Trades 40 Average Win 0.52% Average Loss -6.86% Compounding Annual Return -28.570% Drawdown 42.900% Expectancy -0.260 Net Profit -21.961% Sharpe Ratio -0.694 Loss Rate 31% Win Rate 69% Profit-Loss Ratio 0.08 Alpha -0.354 Beta 1.485 Annual Standard Deviation 0.336 Annual Variance 0.113 Information Ratio -1.191 Tracking Error 0.264 Treynor Ratio -0.157 Total Fees $40.00 |
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 { // initialize our changes to nothing private SecurityChanges _changes = SecurityChanges.None; // Minimum price per stock for it to be of interest. decimal _minPrice = 0.2m; // Maximum price per stock for it to be of interest. decimal _maxPrice = 20m; // Pull the top stocks that meet our min/max price, sorted by total Volume int _chunk = 50; // 3 mo peek should be no less than the current price*_highModifier. decimal _highModifier = 1.08m; // Target $ amount when making a purchase. Should not be below $400, which makes the $2 to buy/sell .5% of the purchase price. decimal _buyLimit = 400m; Dictionary<double, decimal> _sellModifiers = new Dictionary<double, decimal>(); //Initialize the data and resolution you require for your strategy: public override void Initialize() { //Start and End Date range for the backtest: //SetStartDate(DateTime.Now.Date.AddYears(-2)); SetStartDate(DateTime.Now.Date.AddDays(-90*3)); SetEndDate(DateTime.Now.Date.AddDays(-90)); //Cash allocation SetCash(2500); // TODO: Take a percentage of the cash monthly? SetWarmup(TimeSpan.FromDays(90)); // Set up the days on which the sale price will be modified. _sellModifiers.Add(1, 1.03m); //_sellModifiers.Add(30, 1.01m); //_sellModifiers.Add(120, 0.60m); UniverseSettings.Resolution = Resolution.Daily; AddUniverse(CoarseSelectionFunction); } //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) { try { if (Portfolio.Cash < _buyLimit) return; foreach(var security in _changes.AddedSecurities) { var symbol = security.Symbol; if(!Securities.ContainsKey(security.Symbol)) { Debug("Did not contain security " + symbol.Value); } if(!security.HoldStock) { // If the stock doesn't pass our criteria then drop it. var remove = false; if(!security.HasData || !AddHolding(security)) remove = true; //Securities.Remove(symbol); } else { Debug(string.Format("Already invested in {0}", symbol)); } } } catch (Exception ex) { Error(string.Format("OnData: {0}", ex.Message)); Error(ex); } } private bool AddHolding(Security security) { var symbol = security.Symbol; try { //Debug(string.Format("Testing {0}", symbol.Value)); var price = security.Price; var quantity = BuyQuantity(security); if(quantity == 0) return false; var ticket = LimitOrder(symbol, quantity, security.Price); Debug(string.Format("Order {4:000} to buy {0} {1} at ${2:#.00} for ${3:#.00}.", quantity, symbol.Value, security.Price, security.Price*quantity, ticket.OrderId)); return true; } catch (Exception ex) { Error(string.Format("AddHolding for {0}: {1}", symbol, ex.Message)); Error(ex); return false; } } // Built-in handler for order completion. // When buying an order, set up our initial high and low stops. // When selling an order for profit, set up the next sell point. public override void OnOrderEvent(OrderEvent orderEvent) { var symbol = orderEvent.Symbol; try { if(!Securities.ContainsKey(symbol)) { Error(string.Format("Security for symbol '{0}' not found in Securities collection when processing OrderId {1}.", symbol, orderEvent.OrderId)); return; } var security = Securities[symbol]; var order = Transactions.GetOrderById(orderEvent.OrderId); var quantity = orderEvent.FillQuantity; var price = orderEvent.FillPrice; switch (orderEvent.Direction) { case OrderDirection.Buy: if(security.Holdings.HoldingsCost != price*quantity) { Error(string.Format("Expected the security's HoldingsCost ({0}) and order event's total cost ({1}) to be the same for OrderId {2} for symbol '{3}'.", security.Holdings.HoldingsCost, orderEvent.FillPrice*orderEvent.FillQuantity, orderEvent.OrderId, symbol)); } if(quantity != 0) { Debug(string.Format("Order {4:000} bought {0} {1} at ${2:#.00} for ${3:#.00}.", quantity, symbol.Value, price, price*quantity, orderEvent.OrderId)); // Put in order to sell once we've met our buy price. Start at one day to ensure we aren't day trading ScheduleSale(symbol, orderEvent.UtcTime, quantity, price); } break; case OrderDirection.Sell: // For a sale, the quantity is in the negative. Reverse it for logging. quantity = -quantity; if(quantity != 0) Debug(string.Format("Order {5:000} sold {0} {1} at ${2:#.00} for ${3:#.00}. Net: ${4}", quantity, symbol.Value, price, price*quantity, security.Holdings != null ? security.Holdings.Profit.ToString("#.00") : "?", orderEvent.OrderId)); break; } } catch (Exception ex) { Error(string.Format("OnOrderEvent for {0}: {1}", symbol.Value, ex.Message)); Error(ex); } } private void ScheduleSale(Symbol symbol, DateTime buyDate, int quantity, decimal price) { foreach(var sellModifier in _sellModifiers) { var sellPrice = price*sellModifier.Value; // Start at one day to ensure we aren't day trading. Schedule.On(DateRules.On(buyDate.AddDays(sellModifier.Key)), TimeRules.AfterMarketOpen(symbol, 1), () => { if(Securities.ContainsKey(symbol) && Securities[symbol].Invested) { var ticket = LimitOrder(symbol, -quantity, sellPrice); Debug(string.Format("Order {3:000} to sell {5} {4} at ${1:0.00} after {2} days.", symbol.Value, sellPrice, sellModifier.Key, ticket.OrderId, symbol.Value, quantity)); } }); } } private int BuyQuantity(Security security) { if(Portfolio.Cash < _buyLimit) return 0; var spendRemainingCash = Math.Floor(Portfolio.Cash / security.Price); if(spendRemainingCash < 1) return 0; return (int)Math.Min(Math.Ceiling(_buyLimit / security.Price), spendRemainingCash); } private IEnumerable<Symbol> CoarseSelectionFunction(IEnumerable<CoarseFundamental> coarse) { try { return (from c in coarse where c.Price >= _minPrice && c.Price <= _maxPrice orderby c.DollarVolume descending select c.Symbol).Take(_chunk); } catch (Exception ex) { Error(string.Format("CoarseSelectionFunction: {0}", ex.Message)); Error(ex); return null; } } // this event fires whenever we have changes to our universe public override void OnSecuritiesChanged(SecurityChanges changes) { _changes = changes; /*if (changes.AddedSecurities.Count > 0) { Debug("Securities added: " + string.Join(",", changes.AddedSecurities.Select(x => x.Symbol.Value))); } if (changes.RemovedSecurities.Count > 0) { Debug("Securities removed: " + string.Join(",", changes.RemovedSecurities.Select(x => x.Symbol.Value))); }*/ } /*private enum OrderTypes { BuyInitial, Sell, }*/ } }