/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Linq;
using System.Collections.Generic;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
using QuantConnect.Orders;
namespace QuantConnect.Algorithm.Examples
{
class MeanRevertSymbolData
{
public string Symbol;
//public Momentum OneMonthPerformance { get; set; }
public Momentum ThreeMonthPerformance { get; set; }
public SimpleMovingAverage SMA200 { get; set; }
public CommodityChannelIndex CCI5 { get; set; }
// public decimal ObjectiveScore
// {
// get
// {
// // we weight the one month performance higher
// decimal weight1 = 100;
// decimal weight2 = 75;
//
// return (weight1 * OneMonthPerformance + weight2 * ThreeMonthPerformance) / (weight1 + weight2);
// }
// }
}
/// <summary>
/// In this example we look at the canonical 15/30 day moving average cross. This algorithm
/// will go long when the 15 crosses above the 30 and will liquidate when the 15 crosses
/// back below the 30.
/// </summary>
public class CommodityChannelIndexAlgorithm : QCAlgorithm
{
//private const string Symbol = "SPY";
private DateTime previous;
//private CommodityChannelIndex cci;
//private SimpleMovingAverage sma;
private SimpleMovingAverage[] ribbon;
private Dictionary<string,int> Quantity = new Dictionary<string,int>();
private int LastDay;
private const decimal _stopLimit = 1.001m;
private const decimal _limitVal = 0.03m;
private readonly HashSet<int> _immediateCancellations = new HashSet<int>();
private const decimal _maxPctPerSymbol = 0.1m;
private decimal _minCash = 0m;
private decimal _sumOrdersYetToBeFilled = 0m;
// these are the symbols we trade
List<string> Symbols = new List<string>
{
"UPRO",
"UDOW",
"EDC",
"TNA",
"YINN",
"BIB",
"CURE",
"DRN",
"RUSL",
"SOXL",
"TMF"
};
List<MeanRevertSymbolData> SymbolData = new List<MeanRevertSymbolData>();
private readonly List<OrderTicket> _stopTickets = new List<OrderTicket>();
private readonly List<OrderTicket> _limitBuyTickets = new List<OrderTicket>();
/// <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()
{
// set up our analysis span
SetStartDate(2009, 04, 16);
SetEndDate(2015, 01, 01);
SetCash(53000); //Set Strategy Cash
_minCash = 0.05m*Portfolio.Cash;
// request SPY data with minute resolution
//AddSecurity(SecurityType.Equity, Symbol, Resolution.Daily);
foreach (var symbol in Symbols)
{
// ideally we would use daily data
AddSecurity(SecurityType.Equity, symbol, Resolution.Daily);
//var oneMonthPerformance = MOM(symbol, 30, Resolution.Daily);
var threeMonthPerformance = MOM(symbol, 90, Resolution.Daily);
var cci = CCI(symbol, 5,MovingAverageType.Simple,Resolution.Daily);
var sma = SMA(symbol, 200, Resolution.Daily);
SymbolData.Add(new MeanRevertSymbolData
{
Symbol = symbol,
// OneMonthPerformance = oneMonthPerformance,
ThreeMonthPerformance = threeMonthPerformance,
CCI5 = cci,
SMA200 = sma
});
Quantity [symbol] = 0;
}
// create a 30 day exponential moving average
//slow = EMA(Symbol, 30, Resolution.Daily);
int ribbonCount = 8;
int ribbonInterval = 15;
//ribbon = Enumerable.Range(0, ribbonCount).Select(x => SMA(Symbol, (x + 1)*ribbonInterval, Resolution.Daily)).ToArray();
}
private int GetNumSymbols(decimal currentPrice)
{
var risk = 0.01m;
if (Portfolio.Cash < _minCash) {
Log ("Free margin, no money left");
return 0;
}
var min_value = Math.Min(Portfolio.Cash, Portfolio.TotalPortfolioValue);
var this_risk = risk * min_value;
var symbol_risk = 0.14m * currentPrice;
var quantity = (int)(Math.Round(this_risk / (symbol_risk)));
var totalPrice = quantity * currentPrice;
if (totalPrice > _maxPctPerSymbol*min_value)
{
quantity = (int)(Math.Round(_maxPctPerSymbol*min_value/currentPrice));
}
return quantity;
}
/// <summary>
/// Update stop limit orders
/// </summary>
private void UpdateStops()
{
foreach(var ticket in _stopTickets)
{
Log(ticket.ToString());
ticket.Update(new UpdateOrderFields
{
LimitPrice = Securities[ticket.Symbol].Low - _limitVal,
StopPrice = Securities[ticket.Symbol].Low * _stopLimit,
Tag = "Change prices: " + Time
});
Log("UPDATE2:: " + ticket.UpdateRequests.Last());
}
}
private void RemoveStop(string symbol)
{
for (var i = 0; i < _stopTickets.Count; i++) {
if (_stopTickets [i].Symbol == symbol)
_stopTickets.RemoveAt (i);
}
}
/// <summary>
/// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
/// </summary>
/// <param name="data">TradeBars IDictionary object with your stock data</param>
public void OnData(TradeBars data)
{
// a couple things to notice in this method:
// 1. We never need to 'update' our indicators with the data, the engine takes care of this for us
// 2. We can use indicators directly in math expressions
// 3. We can easily plot many indicators at the same time
// wait for our slow ema to fully initialize
foreach (var symboldata in SymbolData) {
if (!symboldata.CCI5.IsReady) return;
if (!symboldata.SMA200.IsReady) return;
}
if (Time.Day != LastDay) {
// foreach (var symboldata in SymbolData) {
// var holdings = Portfolio[symboldata.Symbol].Quantity;
// // we only want to go long if we're currently short or flat
// if (holdings > 0) {
//
// var stopPrice = Securities [symboldata.Symbol].Low * _stopLimit;
// var limitPrice = Securities [symboldata.Symbol].Low - _limitVal;
//
// var newStopTicket = StopLimitOrder (symboldata.Symbol, Quantity [symboldata.Symbol], stopPrice, limitPrice);
// _stopLimitTickets.Add (newStopTicket);
// }
// }
UpdateStops ();
}
LastDay = Time.Day;
foreach (var symboldata in SymbolData) {
var diff = Securities[symboldata.Symbol].Price / symboldata.SMA200;
if (diff < 0.01m)
{
Log("price under SMA 200, not buying.");
}
var holdings = Portfolio[symboldata.Symbol].Quantity;
// we only want to go long if we're currently short or flat
if (holdings <= 0)
{
if (symboldata.CCI5 <= -100)
{
var cash_left = Portfolio.Cash - _sumOrdersYetToBeFilled;
int num = GetNumSymbols(Securities[symboldata.Symbol].Price);
if (num > 0 && cash_left > _minCash)
{
Quantity[symboldata.Symbol] = num;
Log("BUY, symbol = " + symboldata.Symbol + " cci = " + symboldata.CCI5 + " num = " + num + " >> " + Securities[symboldata.Symbol].Price);
var limitPrice = Securities [symboldata.Symbol].Price * .999m;
var newTicket = LimitOrder(symboldata.Symbol, num, limitPrice);
_limitBuyTickets.Add (newTicket);
_sumOrdersYetToBeFilled += (decimal)num * limitPrice;
//UpdateTemporaryCashBalance ();
//var newStopTicket = StopMarketOrder(symboldata.Symbol, -1*Quantity[symboldata.Symbol], stopPrice);
//Log("BUY >> " + Securities[Symbol].Price);
//SetHoldings(Symbol, 1.0);
}
}
}
if (holdings > 0 && symboldata.CCI5 > 100)
{
//var num = (int)(-0.5 * Quantity);
var num = -1 * Quantity[symboldata.Symbol];
Log("SELL symbol = " + symboldata.Symbol + " cci = " + symboldata.CCI5 + " num = " + num + " >> " + Securities[symboldata.Symbol].Price);
var newTicket = LimitOrder(symboldata.Symbol,num, Securities[symboldata.Symbol].Price * .999m);
//var stopPrice = Securities[Symbol].Low * 1.001m;
//var limitPrice = Securities[Symbol].Low - 0.03m;
//var newStopTicket = StopLimitOrder(Symbol, Quantity, stopPrice,limitPrice);
//Liquidate(Symbol);
}
}
// only once per day
if (previous.Date == data.Time.Date) return;
// define a small tolerance on our checks to avoid bouncing
const decimal tolerance = 0.00015m;
// we only want to liquidate if we're currently long
// if the fast is less than the slow we'll liquidate our long
//Plot(Symbol, "Price", data[Symbol].Price);
// easily plot indicators, the series name will be the name of the indicator
//Plot(Symbol, cci, sma);
//Plot("Ribbon", ribbon);
previous = data.Time;
}
// private void UpdateTemporaryCashBalance()
// {
// _sumOrdersYetToBeFilled = 0;
// foreach (var ticket in _limitBuyTickets) {
// if (ticket.Status.IsOpen () && !ticket.Status.IsFill ()) {
// _sumOrdersYetToBeFilled += ticket.Quantity * ticket.LimitPrice;
// }
// }
// }
public override void OnOrderEvent(OrderEvent orderEvent)
{
// if (_immediateCancellations.Contains(orderEvent.OrderId))
// {
// _immediateCancellations.Remove(orderEvent.OrderId);
// Transactions.CancelOrder(orderEvent.OrderId);
// }
if (orderEvent.Status == OrderStatus.Filled)
{
Log("FILLED:: " + Transactions.GetOrderById(orderEvent.OrderId) + " FILL PRICE:: " + orderEvent.FillPrice.SmartRounding());
if (orderEvent.FillQuantity > 0)
{
Log("BUY >> " + orderEvent.FillPrice.SmartRounding());
Log("Cash left: " + Portfolio.Cash);
var stopPrice = Securities[orderEvent.Symbol].Low * _stopLimit;
var limitPrice = Securities[orderEvent.Symbol].Low - _limitVal;
var newStopTicket = StopLimitOrder(orderEvent.Symbol, Quantity[orderEvent.Symbol], stopPrice,limitPrice);
_stopTickets.Add (newStopTicket);
_sumOrdersYetToBeFilled -= orderEvent.FillPrice * orderEvent.FillQuantity;
// UpdateTemporaryCashBalance ();
if (Portfolio.Cash < _minCash) {
foreach (var buyTicket in _limitBuyTickets) {
if (buyTicket.Status.IsOpen () && !buyTicket.Status.IsFill ()) {
buyTicket.Cancel ();
}
}
}
}
else
{
Log("SELL >> " + orderEvent.FillPrice.SmartRounding());
Log("Cash left: " + Portfolio.Cash);
RemoveStop(orderEvent.Symbol);
}
}
else
{
Log(orderEvent.ToString());
//Log("TICKET:: " + _tickets.Last());
}
}
}
}