Heiken Ashi Idea Strategy
Overview
The strategy reproduces the behaviour of the original HeikenAshiIdea.mq4 expert advisor using the StockSharp high-level API. It waits for aligned bullish or bearish signals on two timeframes of Heikin Ashi candles and then places pending limit orders at a configurable distance from the market. The goal is to catch strong continuation moves when the most recent Heikin Ashi candle has no wick against the direction of the trend.
Trading Logic
- Heikin Ashi reconstruction – the strategy internally rebuilds Heikin Ashi candles for the primary trading timeframe and for a higher confirmation timeframe. For each timeframe the last two Heikin Ashi candles are stored so that the body direction and the presence of wicks can be analysed.
- Breakout condition – a long setup appears when both timeframes show:
- the most recent Heikin Ashi candle is bullish and its open equals the low (no lower shadow), and
- the previous Heikin Ashi candle is also bullish but it has a lower shadow. A short setup requires the symmetric bearish conditions (no upper shadow on the latest candle and an upper shadow on the previous one).
- ATR volatility filter – the Average True Range with configurable length must be rising (
ATR[t] > ATR[t-1]) if the filter is enabled. This reproduces the originalActiveMarketvolatility check. - Trading window – signals are ignored outside the user defined trading session (default 09:00–19:00).
- Order placement – when a signal is valid the strategy places a single pending limit order:
- Long signal → buy limit order at
ClosePrice - DistancePoints * PriceStep. - Short signal → sell limit order at
ClosePrice + DistancePoints * PriceStep. Existing opposite pending orders are cancelled before a new order is queued. The strategy tracks only one pending order per direction and automatically clears references when the order becomes inactive.
- Long signal → buy limit order at
- Position management – optional take-profit and stop-loss distances are translated into StockSharp protective mechanisms via
StartProtection. When a new candle of the “close-all” timeframe opens, the strategy cancels all pending orders and closes any open position if the flag is enabled. This mimics theUseCloseAllbehaviour from the original EA.
Risk Management
- Protective levels are expressed in price steps (points) to stay close to the MetaTrader implementation. They are optional; using
0disables the corresponding protection. - Pending orders are only placed when the calculated distance is positive and the trading volume is above zero.
- The strategy never averages positions automatically; it first flattens the opposite pending order before scheduling a new one.
- A tolerance equal to half of the instrument price step is used when checking if Heikin Ashi candles have or have not wicks. This prevents floating point rounding issues while staying faithful to the original strict comparisons.
Parameters
| Name | Description | Default |
|---|---|---|
DistancePoints |
Distance in price steps for the pending limit orders. | 8 |
StopLossPoints |
Stop-loss distance in price steps (0 disables the stop). | 0 |
TakeProfitPoints |
Take-profit distance in price steps (0 disables the target). | 20 |
UseCloseAllOnNewBar |
Close position and cancel orders when a new candle of the close-all timeframe opens. | true |
CandleType |
Primary candle type used for trading signals. | 30m timeframe |
HigherCandleType |
Confirmation candle type for the multi-timeframe filter. | 1d timeframe |
CloseAllCandleType |
Candle type that triggers the close-all routine. | 7d timeframe |
StartHour |
First hour of the trading session (inclusive). | 9 |
EndHour |
Last hour of the trading session (inclusive). | 19 |
UseAtrFilter |
Enable the ATR rising volatility filter. | true |
AtrPeriod |
ATR period used by the volatility filter. | 14 |
Additional Notes
- The strategy uses the built-in
Volumeproperty fromStrategyas the base order size. Adjust it before starting the strategy. - Because the StockSharp implementation uses candle close prices for pending order placement, live execution can differ slightly from the original MT4 code that used bid/ask quotes, but the core idea remains intact.
- To extend the logic for different markets simply tune the candle types, trading window and distance parameters while keeping the multi-timeframe confirmation in place.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Multi-timeframe Heikin Ashi strategy that uses pending limit orders and an ATR filter.
/// </summary>
public class HeikenAshiIdeaStrategy : Strategy
{
private readonly StrategyParam<decimal> _distancePoints;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<bool> _useCloseAll;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<DataType> _higherCandleType;
private readonly StrategyParam<DataType> _closeAllCandleType;
private readonly StrategyParam<int> _startHour;
private readonly StrategyParam<int> _endHour;
private readonly StrategyParam<bool> _useAtrFilter;
private readonly StrategyParam<int> _atrPeriod;
private AverageTrueRange _atr;
private bool _hasAtrValue;
private bool _hasPrevAtrValue;
private decimal _lastAtrValue;
private decimal _prevAtrValue;
private bool _baseHasCurrent;
private bool _baseHasPrevious;
private HeikinAshiCandle _baseCurrentHa;
private HeikinAshiCandle _basePreviousHa;
private bool _higherHasCurrent;
private bool _higherHasPrevious;
private HeikinAshiCandle _higherCurrentHa;
private HeikinAshiCandle _higherPreviousHa;
private Order _buyOrder;
private Order _sellOrder;
private DateTimeOffset? _lastCloseAllTime;
private decimal _priceStep;
private decimal _comparisonTolerance;
/// <summary>
/// Distance in price steps used to offset pending orders from the market price.
/// </summary>
public decimal DistancePoints
{
get => _distancePoints.Value;
set => _distancePoints.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in price steps (0 disables the protective stop).
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance expressed in price steps (0 disables the target).
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Whether to flatten positions when a new candle of the close-all timeframe opens.
/// </summary>
public bool UseCloseAllOnNewBar
{
get => _useCloseAll.Value;
set => _useCloseAll.Value = value;
}
/// <summary>
/// Primary candle type used for trade signals.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Higher timeframe candle type that confirms the trend.
/// </summary>
public DataType HigherCandleType
{
get => _higherCandleType.Value;
set => _higherCandleType.Value = value;
}
/// <summary>
/// Candle type used to trigger the close-all routine.
/// </summary>
public DataType CloseAllCandleType
{
get => _closeAllCandleType.Value;
set => _closeAllCandleType.Value = value;
}
/// <summary>
/// First hour of the trading window (inclusive).
/// </summary>
public int StartHour
{
get => _startHour.Value;
set => _startHour.Value = value;
}
/// <summary>
/// Last hour of the trading window (inclusive).
/// </summary>
public int EndHour
{
get => _endHour.Value;
set => _endHour.Value = value;
}
/// <summary>
/// Whether the strategy requires rising ATR values before placing new orders.
/// </summary>
public bool UseAtrFilter
{
get => _useAtrFilter.Value;
set => _useAtrFilter.Value = value;
}
/// <summary>
/// ATR period used in the volatility filter.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="HeikenAshiIdeaStrategy"/> class.
/// </summary>
public HeikenAshiIdeaStrategy()
{
_distancePoints = Param(nameof(DistancePoints), 8m)
.SetGreaterThanZero()
.SetDisplay("Pending Distance (pts)", "Distance for pending limit orders measured in price steps.", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 0m)
.SetDisplay("Stop Loss (pts)", "Stop-loss distance in price steps. Set to 0 to disable the protective stop.", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 20m)
.SetDisplay("Take Profit (pts)", "Take-profit distance in price steps. Set to 0 to disable the target.", "Risk");
_useCloseAll = Param(nameof(UseCloseAllOnNewBar), true)
.SetDisplay("Close On Higher Bar", "Flatten positions when a new candle of the close-all timeframe opens.", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Primary Candle Type", "Primary timeframe used for trading signals.", "Data");
_higherCandleType = Param(nameof(HigherCandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Higher Candle Type", "Confirmation timeframe used for Heikin Ashi trend filter.", "Data");
_closeAllCandleType = Param(nameof(CloseAllCandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Close-All Candle Type", "Timeframe that triggers a complete exit on a new bar.", "Data");
_startHour = Param(nameof(StartHour), 0)
.SetRange(0, 23)
.SetDisplay("Start Hour", "First hour of the trading window (inclusive).", "Session");
_endHour = Param(nameof(EndHour), 23)
.SetRange(0, 23)
.SetDisplay("End Hour", "Last hour of the trading window (inclusive).", "Session");
_useAtrFilter = Param(nameof(UseAtrFilter), false)
.SetDisplay("Use ATR Filter", "Require rising ATR to allow new orders.", "Filters");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period used for the ATR volatility filter.", "Filters");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
yield return (Security, HigherCandleType);
if (UseCloseAllOnNewBar)
yield return (Security, CloseAllCandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_atr?.Reset();
_hasAtrValue = false;
_hasPrevAtrValue = false;
_lastAtrValue = 0m;
_prevAtrValue = 0m;
_baseHasCurrent = false;
_baseHasPrevious = false;
_baseCurrentHa = default;
_basePreviousHa = default;
_higherHasCurrent = false;
_higherHasPrevious = false;
_higherCurrentHa = default;
_higherPreviousHa = default;
_buyOrder = null;
_sellOrder = null;
_lastCloseAllTime = null;
_priceStep = 0m;
_comparisonTolerance = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_priceStep = Security?.PriceStep ?? 1m;
if (_priceStep <= 0m)
_priceStep = 1m;
_comparisonTolerance = _priceStep / 2m;
_atr = new AverageTrueRange { Length = AtrPeriod };
var primarySubscription = SubscribeCandles(CandleType);
primarySubscription
.Bind(_atr, ProcessPrimaryCandle)
.Start();
var higherSubscription = SubscribeCandles(HigherCandleType);
higherSubscription
.Bind(ProcessHigherCandle)
.Start();
if (UseCloseAllOnNewBar)
{
var closeAllSubscription = SubscribeCandles(CloseAllCandleType);
closeAllSubscription
.Bind(ProcessCloseAllCandle)
.Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, primarySubscription);
DrawOwnTrades(area);
}
var takeProfitUnit = TakeProfitPoints > 0m ? new Unit(TakeProfitPoints * _priceStep, UnitTypes.Absolute) : null;
var stopLossUnit = StopLossPoints > 0m ? new Unit(StopLossPoints * _priceStep, UnitTypes.Absolute) : null;
if (takeProfitUnit != null || stopLossUnit != null)
{
StartProtection(takeProfitUnit, stopLossUnit);
}
}
private void ProcessPrimaryCandle(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
UpdateAtrState(atrValue);
UpdateHeikinAshiState(candle, ref _baseHasCurrent, ref _baseHasPrevious, ref _baseCurrentHa, ref _basePreviousHa);
TryPlaceOrders(candle);
}
private void ProcessHigherCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
UpdateHeikinAshiState(candle, ref _higherHasCurrent, ref _higherHasPrevious, ref _higherCurrentHa, ref _higherPreviousHa);
}
private void ProcessCloseAllCandle(ICandleMessage candle)
{
if (!UseCloseAllOnNewBar || candle.State != CandleStates.Finished)
return;
if (_lastCloseAllTime == candle.OpenTime)
return;
_lastCloseAllTime = candle.OpenTime;
CancelTrackedOrders();
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
}
private void UpdateAtrState(decimal atrValue)
{
if (!_atr.IsFormed)
return;
if (_hasAtrValue)
{
_prevAtrValue = _lastAtrValue;
_hasPrevAtrValue = true;
}
_lastAtrValue = atrValue;
_hasAtrValue = true;
}
private void UpdateHeikinAshiState(ICandleMessage candle, ref bool hasCurrent, ref bool hasPrevious, ref HeikinAshiCandle current, ref HeikinAshiCandle previous)
{
var hadCurrent = hasCurrent;
var last = current;
var haOpen = hadCurrent ? (last.Open + last.Close) / 2m : (candle.OpenPrice + candle.ClosePrice) / 2m;
var haClose = (candle.OpenPrice + candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 4m;
var haHigh = Math.Max(Math.Max(candle.HighPrice, haOpen), haClose);
var haLow = Math.Min(Math.Min(candle.LowPrice, haOpen), haClose);
var haCandle = new HeikinAshiCandle(haOpen, haHigh, haLow, haClose);
if (hadCurrent)
{
previous = last;
hasPrevious = true;
}
current = haCandle;
hasCurrent = true;
}
private void TryPlaceOrders(ICandleMessage candle)
{
if (DistancePoints <= 0m || StopLossPoints < 0m || TakeProfitPoints < 0m)
return;
var timeOfDay = candle.OpenTime.TimeOfDay;
if (!IsWithinTradeHours(timeOfDay))
return;
if (!_baseHasPrevious || !_higherHasPrevious)
return;
if (UseAtrFilter)
{
if (!_hasPrevAtrValue || _lastAtrValue <= _prevAtrValue)
return;
}
UpdateOrderReferences();
var longSignal = IsHeikinBullishBreakout(_baseCurrentHa, _basePreviousHa) && IsHeikinBullishBreakout(_higherCurrentHa, _higherPreviousHa);
var shortSignal = IsHeikinBearishBreakout(_baseCurrentHa, _basePreviousHa) && IsHeikinBearishBreakout(_higherCurrentHa, _higherPreviousHa);
var offset = DistancePoints * _priceStep;
if (longSignal && Position <= 0m)
{
if (_sellOrder != null && _sellOrder.State == OrderStates.Active)
{
CancelOrder(_sellOrder);
_sellOrder = null;
}
if (_buyOrder == null || _buyOrder.State != OrderStates.Active)
{
var price = candle.ClosePrice - offset;
if (price <= 0m)
price = _priceStep;
var volume = Volume + (Position < 0m ? Math.Abs(Position) : 0m);
if (volume > 0m)
_buyOrder = BuyLimit(price, volume);
}
}
else if (shortSignal && Position >= 0m)
{
if (_buyOrder != null && _buyOrder.State == OrderStates.Active)
{
CancelOrder(_buyOrder);
_buyOrder = null;
}
if (_sellOrder == null || _sellOrder.State != OrderStates.Active)
{
var price = candle.ClosePrice + offset;
var volume = Volume + (Position > 0m ? Math.Abs(Position) : 0m);
if (volume > 0m)
_sellOrder = SellLimit(price, volume);
}
}
}
private void UpdateOrderReferences()
{
if (_buyOrder != null && _buyOrder.State != OrderStates.Active)
_buyOrder = null;
if (_sellOrder != null && _sellOrder.State != OrderStates.Active)
_sellOrder = null;
}
private void CancelTrackedOrders()
{
if (_buyOrder != null)
{
if (_buyOrder.State == OrderStates.Active)
CancelOrder(_buyOrder);
_buyOrder = null;
}
if (_sellOrder != null)
{
if (_sellOrder.State == OrderStates.Active)
CancelOrder(_sellOrder);
_sellOrder = null;
}
}
private bool IsWithinTradeHours(TimeSpan time)
{
var start = TimeSpan.FromHours(StartHour);
var end = TimeSpan.FromHours(EndHour);
if (end < start)
return time >= start || time <= end;
return time >= start && time <= end;
}
private bool IsHeikinBullishBreakout(HeikinAshiCandle current, HeikinAshiCandle previous)
{
return IsBullish(current) && HasNoLowerShadow(current) && IsBullish(previous) && HasLowerShadow(previous);
}
private bool IsHeikinBearishBreakout(HeikinAshiCandle current, HeikinAshiCandle previous)
{
return IsBearish(current) && HasNoUpperShadow(current) && IsBearish(previous) && HasUpperShadow(previous);
}
private static bool IsBullish(HeikinAshiCandle candle)
{
return candle.Close > candle.Open;
}
private static bool IsBearish(HeikinAshiCandle candle)
{
return candle.Close < candle.Open;
}
private bool HasNoLowerShadow(HeikinAshiCandle candle)
{
return Math.Abs(candle.Open - candle.Low) <= _comparisonTolerance;
}
private bool HasLowerShadow(HeikinAshiCandle candle)
{
return Math.Abs(candle.Open - candle.Low) > _comparisonTolerance;
}
private bool HasNoUpperShadow(HeikinAshiCandle candle)
{
return Math.Abs(candle.Open - candle.High) <= _comparisonTolerance;
}
private bool HasUpperShadow(HeikinAshiCandle candle)
{
return Math.Abs(candle.Open - candle.High) > _comparisonTolerance;
}
private readonly struct HeikinAshiCandle
{
public HeikinAshiCandle(decimal open, decimal high, decimal low, decimal close)
{
Open = open;
High = high;
Low = low;
Close = close;
}
public decimal Open { get; }
public decimal High { get; }
public decimal Low { get; }
public decimal Close { get; }
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import AverageTrueRange
class heiken_ashi_idea_strategy(Strategy):
"""Multi-timeframe Heikin Ashi strategy using pending limit orders and ATR filter."""
def __init__(self):
super(heiken_ashi_idea_strategy, self).__init__()
self._distance_points = self.Param("DistancePoints", 8.0) \
.SetGreaterThanZero() \
.SetDisplay("Pending Distance (pts)", "Distance for pending limit orders in price steps", "Trading")
self._stop_loss_points = self.Param("StopLossPoints", 0.0) \
.SetDisplay("Stop Loss (pts)", "Stop-loss distance in price steps (0 disables)", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 20.0) \
.SetDisplay("Take Profit (pts)", "Take-profit distance in price steps (0 disables)", "Risk")
self._use_close_all = self.Param("UseCloseAllOnNewBar", True) \
.SetDisplay("Close On Higher Bar", "Flatten positions when close-all timeframe bar opens", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Primary Candle Type", "Primary timeframe for signals", "Data")
self._higher_candle_type = self.Param("HigherCandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Higher Candle Type", "Confirmation timeframe for HA trend filter", "Data")
self._close_all_candle_type = self.Param("CloseAllCandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Close-All Candle Type", "Timeframe that triggers complete exit", "Data")
self._start_hour = self.Param("StartHour", 0) \
.SetDisplay("Start Hour", "First hour of trading window", "Session")
self._end_hour = self.Param("EndHour", 23) \
.SetDisplay("End Hour", "Last hour of trading window", "Session")
self._use_atr_filter = self.Param("UseAtrFilter", False) \
.SetDisplay("Use ATR Filter", "Require rising ATR to allow new orders", "Filters")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("ATR Period", "Period used for ATR volatility filter", "Filters")
self._has_atr_value = False
self._has_prev_atr = False
self._last_atr_value = 0.0
self._prev_atr_value = 0.0
self._base_ha_current = None
self._base_ha_previous = None
self._higher_ha_current = None
self._higher_ha_previous = None
self._buy_order = None
self._sell_order = None
self._last_close_all_time = None
self._price_step = 0.0
self._tolerance = 0.0
@property
def DistancePoints(self):
return self._distance_points.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def UseCloseAllOnNewBar(self):
return self._use_close_all.Value
@property
def CandleType(self):
return self._candle_type.Value
@property
def HigherCandleType(self):
return self._higher_candle_type.Value
@property
def CloseAllCandleType(self):
return self._close_all_candle_type.Value
@property
def StartHour(self):
return self._start_hour.Value
@property
def EndHour(self):
return self._end_hour.Value
@property
def UseAtrFilter(self):
return self._use_atr_filter.Value
@property
def AtrPeriod(self):
return self._atr_period.Value
def OnStarted2(self, time):
super(heiken_ashi_idea_strategy, self).OnStarted2(time)
sec = self.Security
self._price_step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 1.0
self._tolerance = self._price_step / 2.0
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
primary = self.SubscribeCandles(self.CandleType)
primary \
.Bind(atr, self.process_primary) \
.Start()
higher = self.SubscribeCandles(self.HigherCandleType)
higher \
.Bind(self.process_higher) \
.Start()
if self.UseCloseAllOnNewBar:
close_all = self.SubscribeCandles(self.CloseAllCandleType)
close_all \
.Bind(self.process_close_all) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, primary)
self.DrawOwnTrades(area)
tp_val = float(self.TakeProfitPoints)
sl_val = float(self.StopLossPoints)
tp_unit = Unit(tp_val * self._price_step, UnitTypes.Absolute) if tp_val > 0 else None
sl_unit = Unit(sl_val * self._price_step, UnitTypes.Absolute) if sl_val > 0 else None
if tp_unit is not None or sl_unit is not None:
self.StartProtection(tp_unit, sl_unit)
def process_primary(self, candle, atr_val):
if candle.State != CandleStates.Finished:
return
self._update_atr(float(atr_val))
self._update_ha(candle, True)
self._try_place_orders(candle)
def process_higher(self, candle):
if candle.State != CandleStates.Finished:
return
self._update_ha(candle, False)
def process_close_all(self, candle):
if not self.UseCloseAllOnNewBar or candle.State != CandleStates.Finished:
return
if self._last_close_all_time == candle.OpenTime:
return
self._last_close_all_time = candle.OpenTime
self._cancel_tracked()
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
def _update_atr(self, val):
if self._has_atr_value:
self._prev_atr_value = self._last_atr_value
self._has_prev_atr = True
self._last_atr_value = val
self._has_atr_value = True
def _update_ha(self, candle, is_base):
o = float(candle.OpenPrice)
h = float(candle.HighPrice)
l = float(candle.LowPrice)
c = float(candle.ClosePrice)
if is_base:
prev = self._base_ha_current
else:
prev = self._higher_ha_current
if prev is not None:
ha_open = (prev[0] + prev[3]) / 2.0
else:
ha_open = (o + c) / 2.0
ha_close = (o + h + l + c) / 4.0
ha_high = max(h, ha_open, ha_close)
ha_low = min(l, ha_open, ha_close)
new_ha = (ha_open, ha_high, ha_low, ha_close)
if is_base:
self._base_ha_previous = self._base_ha_current
self._base_ha_current = new_ha
else:
self._higher_ha_previous = self._higher_ha_current
self._higher_ha_current = new_ha
def _try_place_orders(self, candle):
if float(self.DistancePoints) <= 0:
return
tod = candle.OpenTime.TimeOfDay
if not self._in_hours(tod):
return
if self._base_ha_previous is None or self._higher_ha_previous is None:
return
if self.UseAtrFilter:
if not self._has_prev_atr or self._last_atr_value <= self._prev_atr_value:
return
self._update_order_refs()
long_sig = (self._ha_bull_break(self._base_ha_current, self._base_ha_previous) and
self._ha_bull_break(self._higher_ha_current, self._higher_ha_previous))
short_sig = (self._ha_bear_break(self._base_ha_current, self._base_ha_previous) and
self._ha_bear_break(self._higher_ha_current, self._higher_ha_previous))
offset = float(self.DistancePoints) * self._price_step
if long_sig and self.Position <= 0:
if self._sell_order is not None and self._sell_order.State == 1:
self.CancelOrder(self._sell_order)
self._sell_order = None
if self._buy_order is None or self._buy_order.State != 1:
price = float(candle.ClosePrice) - offset
if price <= 0:
price = self._price_step
vol = self.Volume + (abs(self.Position) if self.Position < 0 else 0)
if vol > 0:
self._buy_order = self.BuyLimit(price, vol)
elif short_sig and self.Position >= 0:
if self._buy_order is not None and self._buy_order.State == 1:
self.CancelOrder(self._buy_order)
self._buy_order = None
if self._sell_order is None or self._sell_order.State != 1:
price = float(candle.ClosePrice) + offset
vol = self.Volume + (abs(self.Position) if self.Position > 0 else 0)
if vol > 0:
self._sell_order = self.SellLimit(price, vol)
def _in_hours(self, tod):
start = TimeSpan.FromHours(self.StartHour)
end = TimeSpan.FromHours(self.EndHour)
if end < start:
return tod >= start or tod <= end
return tod >= start and tod <= end
def _update_order_refs(self):
if self._buy_order is not None and self._buy_order.State != 1:
self._buy_order = None
if self._sell_order is not None and self._sell_order.State != 1:
self._sell_order = None
def _cancel_tracked(self):
if self._buy_order is not None:
if self._buy_order.State == 1:
self.CancelOrder(self._buy_order)
self._buy_order = None
if self._sell_order is not None:
if self._sell_order.State == 1:
self.CancelOrder(self._sell_order)
self._sell_order = None
def _ha_bull_break(self, curr, prev):
if curr is None or prev is None:
return False
return (self._is_bullish(curr) and self._no_lower_shadow(curr) and
self._is_bullish(prev) and self._has_lower_shadow(prev))
def _ha_bear_break(self, curr, prev):
if curr is None or prev is None:
return False
return (self._is_bearish(curr) and self._no_upper_shadow(curr) and
self._is_bearish(prev) and self._has_upper_shadow(prev))
def _is_bullish(self, ha):
return ha[3] > ha[0]
def _is_bearish(self, ha):
return ha[3] < ha[0]
def _no_lower_shadow(self, ha):
return abs(ha[0] - ha[2]) <= self._tolerance
def _has_lower_shadow(self, ha):
return abs(ha[0] - ha[2]) > self._tolerance
def _no_upper_shadow(self, ha):
return abs(ha[0] - ha[1]) <= self._tolerance
def _has_upper_shadow(self, ha):
return abs(ha[0] - ha[1]) > self._tolerance
def OnReseted(self):
super(heiken_ashi_idea_strategy, self).OnReseted()
self._has_atr_value = False
self._has_prev_atr = False
self._last_atr_value = 0.0
self._prev_atr_value = 0.0
self._base_ha_current = None
self._base_ha_previous = None
self._higher_ha_current = None
self._higher_ha_previous = None
self._buy_order = None
self._sell_order = None
self._last_close_all_time = None
self._price_step = 0.0
self._tolerance = 0.0
def CreateClone(self):
return heiken_ashi_idea_strategy()