Стратегия Heiken Ashi Idea
Обзор
Стратегия повторяет поведение оригинального советника HeikenAshiIdea.mq4, используя высокоуровневый API StockSharp. Она анализирует Heikin Ashi свечи на двух таймфреймах и выставляет отложенные лимитные заявки на заданном расстоянии, если оба таймфрейма подтверждают импульс без противоположных теней.
Логика торговли
- Пересчёт Heikin Ashi – стратегия самостоятельно формирует Heikin Ashi свечи по основному торговому таймфрейму и по старшему подтверждающему таймфрейму. Для каждого таймфрейма хранятся две последние свечи, чтобы оценивать направление тела и наличие теней.
- Условие входа – длинный сигнал формируется, когда:
- текущая Heikin Ashi свеча бычья и её открытие равно минимуму (нет нижней тени);
- предыдущая Heikin Ashi свеча также бычья, но имеет нижнюю тень. Для короткого сигнала условия симметричны: текущая свеча медвежья без верхней тени, предыдущая медвежья с верхней тенью.
- Фильтр ATR – при включенном фильтре средний истинный диапазон должен расти (
ATR[t] > ATR[t-1]), что соответствует функцииActiveMarketв исходном коде. - Торговая сессия – сигналы игнорируются вне настроенного торгового окна (по умолчанию 09:00–19:00).
- Размещение заявок – при появлении сигнала выставляется одна отложенная заявка:
- длинная заявка: buy limit по цене
ClosePrice - DistancePoints * PriceStep; - короткая заявка: sell limit по цене
ClosePrice + DistancePoints * PriceStep. Перед размещением новой заявки отменяется противоположная активная заявка. Стратегия отслеживает только по одной заявке в каждом направлении и автоматически сбрасывает ссылки на исполненные или снятые заявки.
- длинная заявка: buy limit по цене
- Управление позицией – опциональные значения take-profit и stop-loss задаются в шагах цены и подключаются через
StartProtection. Если включен флагUseCloseAllOnNewBar, то при появлении новой свечи выбранного таймфрейма все заявки отменяются и позиция закрывается, что повторяет поведениеUseCloseAllв MQL.
Управление риском
- Stop-loss и take-profit выражены в шагах цены, как в оригинальном советнике. Значение
0отключает соответствующий уровень. - Заявки ставятся только при положительном расстоянии и ненулевом объёме.
- Стратегия не усредняется: перед выставлением заявки в другом направлении отменяются противоположные заявки.
- Для проверки отсутствия/наличия теней используется допуск, равный половине шага цены, что позволяет избежать ошибок округления и при этом сохраняет строгие сравнения оригинала.
Параметры
| Параметр | Описание | Значение по умолчанию |
|---|---|---|
DistancePoints |
Расстояние для лимитных заявок в шагах цены. | 8 |
StopLossPoints |
Стоп-лосс в шагах цены (0 – выключено). |
0 |
TakeProfitPoints |
Тейк-профит в шагах цены (0 – выключено). |
20 |
UseCloseAllOnNewBar |
Закрывать позицию и отменять заявки при новой свече таймфрейма закрытия. | true |
CandleType |
Основной торговый таймфрейм. | 30 минут |
HigherCandleType |
Подтверждающий таймфрейм. | 1 день |
CloseAllCandleType |
Таймфрейм, запускающий процедуру закрытия. | 7 дней |
StartHour |
Начало торгового окна (час включительно). | 9 |
EndHour |
Конец торгового окна (час включительно). | 19 |
UseAtrFilter |
Использовать ли фильтр роста ATR. | true |
AtrPeriod |
Период ATR для фильтра. | 14 |
Дополнительные замечания
- В качестве объёма сделок используется свойство
Strategy.Volume; скорректируйте его перед запуском. - Из-за использования закрытия свечи для расчёта цены лимитных заявок возможны небольшие отличия от версии MT4, которая опиралась на bid/ask. Основная логика при этом сохранена.
- Подстройка таймфреймов, торгового окна и расстояния до заявок позволяет адаптировать стратегию к другим инструментам.
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()