using System;
using System.Collections;
using System.Collections.Generic;
using QuantConnect.Securities;
using QuantConnect.Models;
namespace QuantConnect
{
// Simple Heikin-Ashi adjustable candlestick trading algorithm.
// Exit point is two opposing trend bars, entry is two consecutive bars in same direction.
// Only trades in exchange opening hours and liquidates all before closing.
// See: http://www.investopedia.com/articles/technical/04/092204.asp
// Like other moving averages. This is a lagging indicator, so the chop will eliminate any gains if used by it self.
// However it can be used in conjunction with other indicators to spot trends.
public class SimpleHeikinAshi : QCAlgorithm
{
public enum TrendDirection
{
Up,
Down
};
string symbol;
int rollingWindowSize = 3;
RollingWindow<TradeBar> history = null;
TimeSpan barPeriod = TimeSpan.FromMinutes(30); //Adjust for desired bar size
//Initialize
public override void Initialize()
{
symbol = "QQQ";
SetStartDate(2020, 9, 24);
SetEndDate(DateTime.Now.Date.AddDays(-1));
SetCash(25000);
AddSecurity(SecurityType.Equity, symbol, Resolution.Minute);
history = new RollingWindow<TradeBar>(rollingWindowSize);
var consolidator = new TradeBarConsolidator(barPeriod);
consolidator.DataConsolidated += OnDataConsolidated;
SubscriptionManager.AddConsolidator(symbol, consolidator);
}
public void OnDataConsolidated(object sender, TradeBar data)
{
var instrument = Securities[symbol];
var exchange = instrument.Exchange;
var marketClose = exchange.Hours.GetNextMarketClose(instrument.LocalTime, false);
var marketOpen = exchange.Hours.GetNextMarketOpen(instrument.LocalTime.AddDays(-1), false);
var tradeStartAfterOpen = marketOpen.AddMinutes(1);
TradeBar thisHeikinAshiBar;
if (!history.Any())
thisHeikinAshiBar = CalculateHeikinAshiBar(data, null);
else
thisHeikinAshiBar = CalculateHeikinAshiBar(data, history[0]);
history.Add(thisHeikinAshiBar);
var holdings = Securities[symbol].Holdings.Quantity;
//Establish first three bars before trading
if (history.IsReady)
{
TrendDirection barTrendDirection = getTrendDirection(thisHeikinAshiBar);
TrendDirection previousBarTrendDirection1 = getTrendDirection(history[1]);
TrendDirection previousBarTrendDirection2 = getTrendDirection(history[2]);
//Wait for the maket open volatility to settle before trading.
if (exchange.ExchangeOpen && Time > tradeStartAfterOpen && Time <= marketClose)
{
//Exit on one bar trend change
if (holdings == 0 && (barTrendDirection == TrendDirection.Up))
{
LimitOrder(symbol, 1, (Securities[symbol].Price * 1.001m));
StopLimitOrder(symbol, -1, (Securities[symbol].Price * .9988m), (Securities[symbol].Price * .9982m));
}
//Two candles treding up - Go long
if (holdings != 0 && (barTrendDirection == TrendDirection.Down))
{
Transactions.CancelOpenOrders(symbol);
LimitOrder(symbol, -1, (Securities[symbol].Price * .998m));
Debug("SELLING" + holdings);
}
}
//Sell any holdings before market close.
var oneBeforeClose = marketClose.AddMinutes(-1);
if (Time == oneBeforeClose)
{
if (!Portfolio.HoldStock)
{
Liquidate(symbol);
history.Reset();
}
}
}
}
public void OnData(TradeBars data)
{ }
//Returns a TradeBar where OHLC values are smoothed from previous bar.
public TradeBar CalculateHeikinAshiBar(TradeBar bar, TradeBar previousHeikinAshiBar)
{
TradeBar result = (TradeBar)bar.Clone();
//Average price of the current bar
result.Close = (bar.Open + bar.High + bar.Low + bar.Close) / 4;
//Midpoint of the previous bar
if (previousHeikinAshiBar != null)
result.Open = (previousHeikinAshiBar.Open + previousHeikinAshiBar.Close) / 2;
else
result.Open = (bar.Open + bar.Close) / 2;
result.High = Math.Max(Math.Max(bar.High, result.Open), result.Close);
result.Low = Math.Min(Math.Min(bar.Low, result.Open), result.Close);
return result;
}
//Establishes the green/red direction of the candlestick.
public TrendDirection getTrendDirection(TradeBar bar)
{
TrendDirection result = (bar.Open > bar.Close) ? TrendDirection.Down : TrendDirection.Up;
return result;
}
}
}