| 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,
}*/
}
}