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>
/// Ichimoku Kijun-sen robot converted from MetaTrader.
/// The strategy looks for price crossing the Kijun line while a 20-period LWMA confirms the trend.
/// It manages risk with time filters, configurable stop-loss, break-even and trailing rules.
/// </summary>
public class KijunSenRobotStrategy : Strategy
{
private readonly StrategyParam<int> _tenkanPeriod;
private readonly StrategyParam<int> _kijunPeriod;
private readonly StrategyParam<int> _senkouSpanBPeriod;
private readonly StrategyParam<int> _lwmaPeriod;
private readonly StrategyParam<decimal> _maFilterPips;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _breakEvenPips;
private readonly StrategyParam<decimal> _trailingStopPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<int> _tradingStartHour;
private readonly StrategyParam<int> _tradingEndHour;
private readonly StrategyParam<DataType> _candleType;
private Ichimoku _ichimoku = null!;
private WeightedMovingAverage _lwma = null!;
private decimal? _previousClose;
private decimal? _previousMa;
private decimal? _previousPrevMa;
private decimal? _previousKijun;
private decimal? _previousPrevKijun;
private decimal? _pendingLongLevel;
private decimal? _pendingShortLevel;
private bool? _isLongPosition;
private decimal? _entryPrice;
private decimal? _stopLossPrice;
private decimal? _takeProfitPrice;
private decimal _stopLossDistance;
private decimal _takeProfitDistance;
private decimal _breakEvenDistance;
private decimal _breakEvenStep;
private decimal _trailingDistance;
private bool _breakEvenApplied;
/// <summary>
/// Tenkan-sen calculation period.
/// </summary>
public int TenkanPeriod
{
get => _tenkanPeriod.Value;
set => _tenkanPeriod.Value = value;
}
/// <summary>
/// Kijun-sen calculation period.
/// </summary>
public int KijunPeriod
{
get => _kijunPeriod.Value;
set => _kijunPeriod.Value = value;
}
/// <summary>
/// Senkou Span B calculation period.
/// </summary>
public int SenkouSpanBPeriod
{
get => _senkouSpanBPeriod.Value;
set => _senkouSpanBPeriod.Value = value;
}
/// <summary>
/// Weighted moving average period used for slope confirmation.
/// </summary>
public int LwmaPeriod
{
get => _lwmaPeriod.Value;
set => _lwmaPeriod.Value = value;
}
/// <summary>
/// Minimum distance in pips between price and Kijun required by the LWMA filter.
/// </summary>
public decimal MaFilterPips
{
get => _maFilterPips.Value;
set => _maFilterPips.Value = value;
}
/// <summary>
/// Initial stop-loss in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Profit distance in pips required to move the stop-loss to break-even.
/// </summary>
public decimal BreakEvenPips
{
get => _breakEvenPips.Value;
set => _breakEvenPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips.
/// </summary>
public decimal TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Take-profit distance in pips.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// First trading hour (inclusive) in exchange time.
/// </summary>
public int TradingStartHour
{
get => _tradingStartHour.Value;
set => _tradingStartHour.Value = value;
}
/// <summary>
/// Last trading hour (exclusive) in exchange time.
/// </summary>
public int TradingEndHour
{
get => _tradingEndHour.Value;
set => _tradingEndHour.Value = value;
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="KijunSenRobotStrategy"/>.
/// </summary>
public KijunSenRobotStrategy()
{
_tenkanPeriod = Param(nameof(TenkanPeriod), 6)
.SetGreaterThanZero()
.SetDisplay("Tenkan Period", "Period for Ichimoku Tenkan line", "Ichimoku")
.SetOptimize(4, 12, 1);
_kijunPeriod = Param(nameof(KijunPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("Kijun Period", "Period for Ichimoku Kijun line", "Ichimoku")
.SetOptimize(8, 20, 1);
_senkouSpanBPeriod = Param(nameof(SenkouSpanBPeriod), 24)
.SetGreaterThanZero()
.SetDisplay("Senkou Span B Period", "Period for Ichimoku Senkou Span B", "Ichimoku")
.SetOptimize(18, 30, 1);
_lwmaPeriod = Param(nameof(LwmaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("LWMA Period", "Length of the confirmation LWMA", "Trend Filter")
.SetOptimize(10, 40, 2);
_maFilterPips = Param(nameof(MaFilterPips), 6m)
.SetNotNegative()
.SetDisplay("LWMA Filter (pips)", "Minimum distance between price and Kijun required by the LWMA", "Trend Filter")
.SetOptimize(0m, 20m, 1m);
_stopLossPips = Param(nameof(StopLossPips), 50m)
.SetNotNegative()
.SetDisplay("Stop Loss (pips)", "Initial protective stop distance", "Risk Management")
.SetOptimize(20m, 100m, 5m);
_breakEvenPips = Param(nameof(BreakEvenPips), 9m)
.SetNotNegative()
.SetDisplay("Break-even Trigger (pips)", "Profit distance required to protect the position", "Risk Management")
.SetOptimize(5m, 20m, 1m);
_trailingStopPips = Param(nameof(TrailingStopPips), 10m)
.SetNotNegative()
.SetDisplay("Trailing Stop (pips)", "Distance for the trailing stop after the position moves in profit", "Risk Management")
.SetOptimize(5m, 30m, 1m);
_takeProfitPips = Param(nameof(TakeProfitPips), 120m)
.SetNotNegative()
.SetDisplay("Take Profit (pips)", "Optional fixed profit target", "Risk Management")
.SetOptimize(40m, 200m, 10m);
_tradingStartHour = Param(nameof(TradingStartHour), 7)
.SetRange(0, 23)
.SetDisplay("Start Hour", "First trading hour (inclusive)", "Scheduling");
_tradingEndHour = Param(nameof(TradingEndHour), 19)
.SetRange(1, 24)
.SetDisplay("End Hour", "Last trading hour (exclusive)", "Scheduling");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for signal generation", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ichimoku = null!;
_lwma = null!;
_previousClose = null;
_previousMa = null;
_previousPrevMa = null;
_previousKijun = null;
_previousPrevKijun = null;
_pendingLongLevel = null;
_pendingShortLevel = null;
ResetPositionState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ichimoku = new Ichimoku
{
Tenkan = { Length = TenkanPeriod },
Kijun = { Length = KijunPeriod },
SenkouB = { Length = SenkouSpanBPeriod }
};
_lwma = new WeightedMovingAverage
{
Length = LwmaPeriod
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_ichimoku, _lwma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ichimoku);
DrawIndicator(area, _lwma);
DrawOwnTrades(area);
}
// protection handled manually via SL/TP/trailing
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue ichimokuValue, IIndicatorValue maValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!maValue.IsFinal)
return;
var ichimokuTyped = (IchimokuValue)ichimokuValue;
if (ichimokuTyped.Kijun is not decimal kijun)
return;
var maCurrent = maValue.ToDecimal();
ManageOpenPosition(candle, maCurrent);
if (IsFormedAndOnlineAndAllowTrading() && IsWithinTradingHours(candle.OpenTime))
{
EvaluateEntrySignals(candle, kijun, maCurrent);
}
UpdateHistory(candle, kijun, maCurrent);
}
private void ManageOpenPosition(ICandleMessage candle, decimal maCurrent)
{
if (Position == 0)
{
if (_isLongPosition != null || _entryPrice != null)
ResetPositionState();
return;
}
var isLong = Position > 0;
var actualEntry = _entryPrice ?? candle.ClosePrice;
if (_isLongPosition is null || _isLongPosition.Value != isLong || _entryPrice is null)
{
var entry = actualEntry != 0m ? actualEntry : candle.ClosePrice;
SetupPositionState(isLong, entry);
}
else if (actualEntry != 0m && _entryPrice.Value != actualEntry)
{
_entryPrice = actualEntry;
if (_isLongPosition.Value)
{
_stopLossPrice = _stopLossDistance > 0m ? _entryPrice - _stopLossDistance : null;
_takeProfitPrice = _takeProfitDistance > 0m ? _entryPrice + _takeProfitDistance : null;
}
else
{
_stopLossPrice = _stopLossDistance > 0m ? _entryPrice + _stopLossDistance : null;
_takeProfitPrice = _takeProfitDistance > 0m ? _entryPrice - _takeProfitDistance : null;
}
}
if (_entryPrice is not decimal entryPrice)
return;
_isLongPosition = isLong;
if (_previousMa.HasValue && _previousPrevMa.HasValue && _stopLossPrice.HasValue)
{
if (isLong && _stopLossPrice.Value < entryPrice && _previousMa.Value < _previousPrevMa.Value)
{
ClosePositionAndReset();
return;
}
if (!isLong && _stopLossPrice.Value > entryPrice && _previousMa.Value > _previousPrevMa.Value)
{
ClosePositionAndReset();
return;
}
}
if (isLong)
{
ApplyBreakEvenAndTrailingForLong(candle, entryPrice);
if (CheckStopLossHit(candle.LowPrice, _stopLossPrice))
{
ClosePositionAndReset();
return;
}
if (CheckTakeProfitHit(candle.HighPrice, _takeProfitPrice))
{
ClosePositionAndReset();
return;
}
}
else
{
ApplyBreakEvenAndTrailingForShort(candle, entryPrice);
if (CheckStopLossHitForShort(candle.HighPrice, _stopLossPrice))
{
ClosePositionAndReset();
return;
}
if (CheckTakeProfitHitForShort(candle.LowPrice, _takeProfitPrice))
{
ClosePositionAndReset();
return;
}
}
}
private void ApplyBreakEvenAndTrailingForLong(ICandleMessage candle, decimal entryPrice)
{
if (!_breakEvenApplied && _breakEvenDistance > 0m)
{
if (candle.ClosePrice - entryPrice >= _breakEvenDistance)
{
var newStop = entryPrice + (_breakEvenStep > 0m ? _breakEvenStep : 0m);
if (_stopLossPrice is not decimal currentStop || newStop > currentStop)
_stopLossPrice = newStop;
_breakEvenApplied = true;
}
}
if (_trailingDistance > 0m && candle.ClosePrice - entryPrice >= _trailingDistance)
{
var newStop = candle.ClosePrice - _trailingDistance;
if (_stopLossPrice is not decimal currentStop || newStop > currentStop)
_stopLossPrice = newStop;
}
}
private void ApplyBreakEvenAndTrailingForShort(ICandleMessage candle, decimal entryPrice)
{
if (!_breakEvenApplied && _breakEvenDistance > 0m)
{
if (entryPrice - candle.ClosePrice >= _breakEvenDistance)
{
var newStop = entryPrice - (_breakEvenStep > 0m ? _breakEvenStep : 0m);
if (_stopLossPrice is not decimal currentStop || newStop < currentStop)
_stopLossPrice = newStop;
_breakEvenApplied = true;
}
}
if (_trailingDistance > 0m && entryPrice - candle.ClosePrice >= _trailingDistance)
{
var newStop = candle.ClosePrice + _trailingDistance;
if (_stopLossPrice is not decimal currentStop || newStop < currentStop)
_stopLossPrice = newStop;
}
}
private static bool CheckStopLossHit(decimal lowPrice, decimal? stopPrice)
{
return stopPrice.HasValue && lowPrice <= stopPrice.Value;
}
private static bool CheckTakeProfitHit(decimal highPrice, decimal? takeProfitPrice)
{
return takeProfitPrice.HasValue && highPrice >= takeProfitPrice.Value;
}
private static bool CheckStopLossHitForShort(decimal highPrice, decimal? stopPrice)
{
return stopPrice.HasValue && highPrice >= stopPrice.Value;
}
private static bool CheckTakeProfitHitForShort(decimal lowPrice, decimal? takeProfitPrice)
{
return takeProfitPrice.HasValue && lowPrice <= takeProfitPrice.Value;
}
private void EvaluateEntrySignals(ICandleMessage candle, decimal kijun, decimal maCurrent)
{
if (_previousClose is not decimal previousClose ||
_previousMa is not decimal previousMa ||
_previousKijun is not decimal previousKijun)
{
return;
}
var maTrendUp = maCurrent > previousMa;
var maTrendDown = maCurrent < previousMa;
var filterOffset = ConvertPips(MaFilterPips);
var priceOpenedBelow = candle.OpenPrice < kijun;
var priceOpenedAbove = candle.OpenPrice > kijun;
var priceClosedAbove = candle.ClosePrice > kijun;
var priceClosedBelow = candle.ClosePrice < kijun;
var priceTouchedBelow = candle.LowPrice <= kijun;
var priceTouchedAbove = candle.HighPrice >= kijun;
var priceWasBelow = previousClose < previousKijun;
var priceWasAbove = previousClose > previousKijun;
var kijunNotFalling = !_previousPrevKijun.HasValue || kijun >= _previousPrevKijun.Value;
var kijunNotRising = !_previousPrevKijun.HasValue || kijun <= _previousPrevKijun.Value;
if (_pendingLongLevel is null)
{
if (priceClosedAbove && (priceOpenedBelow || priceWasBelow || priceTouchedBelow) && kijunNotFalling)
{
if (filterOffset <= 0m || maCurrent < kijun - filterOffset)
{
_pendingLongLevel = kijun;
_pendingShortLevel = null;
}
}
}
if (_pendingShortLevel is null)
{
if (priceClosedBelow && (priceOpenedAbove || priceWasAbove || priceTouchedAbove) && kijunNotRising)
{
if (filterOffset <= 0m || maCurrent > kijun + filterOffset)
{
_pendingShortLevel = kijun;
_pendingLongLevel = null;
}
}
}
var volume = Volume + Math.Abs(Position);
if (volume <= 0m)
return;
if (_pendingLongLevel.HasValue && maTrendUp && Position <= 0)
{
BuyMarket(volume);
SetupPositionState(true, candle.ClosePrice);
_pendingLongLevel = null;
_pendingShortLevel = null;
return;
}
if (_pendingShortLevel.HasValue && maTrendDown && Position >= 0)
{
SellMarket(volume);
SetupPositionState(false, candle.ClosePrice);
_pendingLongLevel = null;
_pendingShortLevel = null;
}
}
private void UpdateHistory(ICandleMessage candle, decimal kijun, decimal maCurrent)
{
_previousPrevKijun = _previousKijun;
_previousKijun = kijun;
_previousPrevMa = _previousMa;
_previousMa = maCurrent;
_previousClose = candle.ClosePrice;
}
private bool IsWithinTradingHours(DateTimeOffset time)
{
var hour = time.Hour;
return hour >= TradingStartHour && hour < TradingEndHour;
}
private void SetupPositionState(bool isLong, decimal entryPrice)
{
_isLongPosition = isLong;
_entryPrice = entryPrice;
_breakEvenApplied = false;
_stopLossDistance = ConvertPips(StopLossPips);
_takeProfitDistance = ConvertPips(TakeProfitPips);
_breakEvenDistance = ConvertPips(BreakEvenPips);
_breakEvenStep = ConvertPips(1m);
_trailingDistance = ConvertPips(TrailingStopPips);
_stopLossPrice = _stopLossDistance > 0m ? (isLong ? entryPrice - _stopLossDistance : entryPrice + _stopLossDistance) : null;
_takeProfitPrice = _takeProfitDistance > 0m ? (isLong ? entryPrice + _takeProfitDistance : entryPrice - _takeProfitDistance) : null;
}
private void ClosePositionAndReset()
{
if (Position != 0)
if (Position > 0) SellMarket(Math.Abs(Position)); else if (Position < 0) BuyMarket(Math.Abs(Position));
ResetPositionState();
}
private void ResetPositionState()
{
_isLongPosition = null;
_entryPrice = null;
_stopLossPrice = null;
_takeProfitPrice = null;
_stopLossDistance = 0m;
_takeProfitDistance = 0m;
_breakEvenDistance = 0m;
_breakEvenStep = 0m;
_trailingDistance = 0m;
_breakEvenApplied = false;
}
private decimal ConvertPips(decimal value)
{
if (value <= 0m)
return 0m;
var step = GetPipStep();
return value * step;
}
private decimal GetPipStep()
{
var priceStep = Security?.PriceStep;
if (priceStep is null || priceStep <= 0m)
return 1m;
var stepValue = priceStep.Value;
var decimals = GetDecimalPlaces(stepValue);
if (decimals == 3 || decimals == 5)
return stepValue * 10m;
return stepValue;
}
private static int GetDecimalPlaces(decimal value)
{
value = Math.Abs(value);
var decimals = 0;
while (value != Math.Truncate(value) && decimals < 10)
{
value *= 10m;
decimals++;
}
return decimals;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import Ichimoku, WeightedMovingAverage, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class kijun_sen_robot_strategy(Strategy):
def __init__(self):
super(kijun_sen_robot_strategy, self).__init__()
self._tenkan_period = self.Param("TenkanPeriod", 6)
self._kijun_period = self.Param("KijunPeriod", 12)
self._senkou_span_b_period = self.Param("SenkouSpanBPeriod", 24)
self._lwma_period = self.Param("LwmaPeriod", 20)
self._ma_filter_pips = self.Param("MaFilterPips", 6.0)
self._stop_loss_pips = self.Param("StopLossPips", 50.0)
self._break_even_pips = self.Param("BreakEvenPips", 9.0)
self._trailing_stop_pips = self.Param("TrailingStopPips", 10.0)
self._take_profit_pips = self.Param("TakeProfitPips", 120.0)
self._trading_start_hour = self.Param("TradingStartHour", 7)
self._trading_end_hour = self.Param("TradingEndHour", 19)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._previous_close = None
self._previous_ma = None
self._previous_prev_ma = None
self._previous_kijun = None
self._previous_prev_kijun = None
self._pending_long_level = None
self._pending_short_level = None
self._is_long = None
self._entry_price = None
self._stop_loss_price = None
self._take_profit_price = None
self._sl_distance = 0.0
self._tp_distance = 0.0
self._be_distance = 0.0
self._be_step = 0.0
self._trail_distance = 0.0
self._be_applied = False
@property
def TenkanPeriod(self):
return self._tenkan_period.Value
@TenkanPeriod.setter
def TenkanPeriod(self, value):
self._tenkan_period.Value = value
@property
def KijunPeriod(self):
return self._kijun_period.Value
@KijunPeriod.setter
def KijunPeriod(self, value):
self._kijun_period.Value = value
@property
def SenkouSpanBPeriod(self):
return self._senkou_span_b_period.Value
@SenkouSpanBPeriod.setter
def SenkouSpanBPeriod(self, value):
self._senkou_span_b_period.Value = value
@property
def LwmaPeriod(self):
return self._lwma_period.Value
@LwmaPeriod.setter
def LwmaPeriod(self, value):
self._lwma_period.Value = value
@property
def MaFilterPips(self):
return self._ma_filter_pips.Value
@MaFilterPips.setter
def MaFilterPips(self, value):
self._ma_filter_pips.Value = value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@StopLossPips.setter
def StopLossPips(self, value):
self._stop_loss_pips.Value = value
@property
def BreakEvenPips(self):
return self._break_even_pips.Value
@BreakEvenPips.setter
def BreakEvenPips(self, value):
self._break_even_pips.Value = value
@property
def TrailingStopPips(self):
return self._trailing_stop_pips.Value
@TrailingStopPips.setter
def TrailingStopPips(self, value):
self._trailing_stop_pips.Value = value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@TakeProfitPips.setter
def TakeProfitPips(self, value):
self._take_profit_pips.Value = value
@property
def TradingStartHour(self):
return self._trading_start_hour.Value
@TradingStartHour.setter
def TradingStartHour(self, value):
self._trading_start_hour.Value = value
@property
def TradingEndHour(self):
return self._trading_end_hour.Value
@TradingEndHour.setter
def TradingEndHour(self, value):
self._trading_end_hour.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(kijun_sen_robot_strategy, self).OnStarted2(time)
self._previous_close = None
self._previous_ma = None
self._previous_prev_ma = None
self._previous_kijun = None
self._previous_prev_kijun = None
self._pending_long_level = None
self._pending_short_level = None
self._reset_position_state()
ichimoku = Ichimoku()
ichimoku.Tenkan.Length = self.TenkanPeriod
ichimoku.Kijun.Length = self.KijunPeriod
ichimoku.SenkouB.Length = self.SenkouSpanBPeriod
lwma = WeightedMovingAverage()
lwma.Length = self.LwmaPeriod
self._ichimoku = ichimoku
self._lwma = lwma
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(ichimoku, lwma, self.ProcessCandle).Start()
# protection handled manually via SL/TP/trailing
def ProcessCandle(self, candle, ichi_value, ma_value):
if candle.State != CandleStates.Finished:
return
if not ma_value.IsFinal:
return
ma_current = float(ma_value)
if not ichi_value.IsFinal:
self._update_history(candle, None, ma_current)
return
kijun = ichi_value.Kijun
if kijun is None:
self._update_history(candle, None, ma_current)
return
kijun_val = float(kijun)
self._manage_open_position(candle, ma_current)
if self.IsFormedAndOnlineAndAllowTrading():
hour = candle.OpenTime.Hour
if hour >= int(self.TradingStartHour) and hour < int(self.TradingEndHour):
self._evaluate_entry_signals(candle, kijun_val, ma_current)
self._update_history(candle, kijun_val, ma_current)
def _manage_open_position(self, candle, ma_current):
if self.Position == 0:
if self._is_long is not None or self._entry_price is not None:
self._reset_position_state()
return
is_long = self.Position > 0
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
actual_entry = self._entry_price if self._entry_price is not None else close
if self._is_long is None or self._is_long != is_long or self._entry_price is None:
entry_val = actual_entry if actual_entry != 0.0 else close
self._setup_position_state(is_long, entry_val)
elif actual_entry != 0.0 and self._entry_price != actual_entry:
self._entry_price = actual_entry
if self._is_long:
self._stop_loss_price = self._entry_price - self._sl_distance if self._sl_distance > 0.0 else None
self._take_profit_price = self._entry_price + self._tp_distance if self._tp_distance > 0.0 else None
else:
self._stop_loss_price = self._entry_price + self._sl_distance if self._sl_distance > 0.0 else None
self._take_profit_price = self._entry_price - self._tp_distance if self._tp_distance > 0.0 else None
entry = self._entry_price if self._entry_price is not None else close
self._is_long = is_long
if self._previous_ma is not None and self._previous_prev_ma is not None and self._stop_loss_price is not None:
if is_long and self._stop_loss_price < entry and self._previous_ma < self._previous_prev_ma:
self._close_position_and_reset()
return
if not is_long and self._stop_loss_price > entry and self._previous_ma > self._previous_prev_ma:
self._close_position_and_reset()
return
if is_long:
self._apply_be_trailing_long(candle, entry)
if self._stop_loss_price is not None and low <= self._stop_loss_price:
self._close_position_and_reset()
return
if self._take_profit_price is not None and high >= self._take_profit_price:
self._close_position_and_reset()
return
else:
self._apply_be_trailing_short(candle, entry)
if self._stop_loss_price is not None and high >= self._stop_loss_price:
self._close_position_and_reset()
return
if self._take_profit_price is not None and low <= self._take_profit_price:
self._close_position_and_reset()
return
def _apply_be_trailing_long(self, candle, entry):
close = float(candle.ClosePrice)
if not self._be_applied and self._be_distance > 0.0:
if close - entry >= self._be_distance:
new_stop = entry + (self._be_step if self._be_step > 0.0 else 0.0)
if self._stop_loss_price is None or new_stop > self._stop_loss_price:
self._stop_loss_price = new_stop
self._be_applied = True
if self._trail_distance > 0.0 and close - entry >= self._trail_distance:
new_stop = close - self._trail_distance
if self._stop_loss_price is None or new_stop > self._stop_loss_price:
self._stop_loss_price = new_stop
def _apply_be_trailing_short(self, candle, entry):
close = float(candle.ClosePrice)
if not self._be_applied and self._be_distance > 0.0:
if entry - close >= self._be_distance:
new_stop = entry - (self._be_step if self._be_step > 0.0 else 0.0)
if self._stop_loss_price is None or new_stop < self._stop_loss_price:
self._stop_loss_price = new_stop
self._be_applied = True
if self._trail_distance > 0.0 and entry - close >= self._trail_distance:
new_stop = close + self._trail_distance
if self._stop_loss_price is None or new_stop < self._stop_loss_price:
self._stop_loss_price = new_stop
def _evaluate_entry_signals(self, candle, kijun, ma_current):
if self._previous_close is None or self._previous_ma is None or self._previous_kijun is None:
return
close = float(candle.ClosePrice)
open_price = float(candle.OpenPrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
prev_close = self._previous_close
prev_kijun = self._previous_kijun
prev_ma = self._previous_ma
filter_offset = self._convert_pips(float(self.MaFilterPips))
kijun_not_falling = self._previous_prev_kijun is None or kijun >= self._previous_prev_kijun
kijun_not_rising = self._previous_prev_kijun is None or kijun <= self._previous_prev_kijun
if self._pending_long_level is None:
price_closed_above = close > kijun
price_opened_below = open_price < kijun
price_was_below = prev_close < prev_kijun
price_touched_below = low <= kijun
if price_closed_above and (price_opened_below or price_was_below or price_touched_below) and kijun_not_falling:
if filter_offset <= 0.0 or ma_current < kijun - filter_offset:
self._pending_long_level = kijun
self._pending_short_level = None
if self._pending_short_level is None:
price_closed_below = close < kijun
price_opened_above = open_price > kijun
price_was_above = prev_close > prev_kijun
price_touched_above = high >= kijun
if price_closed_below and (price_opened_above or price_was_above or price_touched_above) and kijun_not_rising:
if filter_offset <= 0.0 or ma_current > kijun + filter_offset:
self._pending_short_level = kijun
self._pending_long_level = None
ma_trend_up = ma_current > prev_ma
ma_trend_down = ma_current < prev_ma
volume = float(self.Volume) + abs(float(self.Position))
if volume <= 0:
return
if self._pending_long_level is not None and ma_trend_up and self.Position <= 0:
self.BuyMarket(volume)
self._setup_position_state(True, close)
self._pending_long_level = None
self._pending_short_level = None
return
if self._pending_short_level is not None and ma_trend_down and self.Position >= 0:
self.SellMarket(volume)
self._setup_position_state(False, close)
self._pending_long_level = None
self._pending_short_level = None
def _update_history(self, candle, kijun, ma_current):
self._previous_prev_kijun = self._previous_kijun
self._previous_kijun = kijun
self._previous_prev_ma = self._previous_ma
self._previous_ma = ma_current
self._previous_close = float(candle.ClosePrice)
def _setup_position_state(self, is_long, entry_price):
self._is_long = is_long
self._entry_price = entry_price
self._be_applied = False
self._sl_distance = self._convert_pips(float(self.StopLossPips))
self._tp_distance = self._convert_pips(float(self.TakeProfitPips))
self._be_distance = self._convert_pips(float(self.BreakEvenPips))
self._be_step = self._convert_pips(1.0)
self._trail_distance = self._convert_pips(float(self.TrailingStopPips))
if self._sl_distance > 0.0:
self._stop_loss_price = entry_price - self._sl_distance if is_long else entry_price + self._sl_distance
else:
self._stop_loss_price = None
if self._tp_distance > 0.0:
self._take_profit_price = entry_price + self._tp_distance if is_long else entry_price - self._tp_distance
else:
self._take_profit_price = None
def _close_position_and_reset(self):
if self.Position > 0:
self.SellMarket(abs(float(self.Position)))
elif self.Position < 0:
self.BuyMarket(abs(float(self.Position)))
self._reset_position_state()
def _reset_position_state(self):
self._is_long = None
self._entry_price = None
self._stop_loss_price = None
self._take_profit_price = None
self._sl_distance = 0.0
self._tp_distance = 0.0
self._be_distance = 0.0
self._be_step = 0.0
self._trail_distance = 0.0
self._be_applied = False
def _convert_pips(self, value):
if value <= 0.0:
return 0.0
return value * self._get_pip_step()
def _get_pip_step(self):
if self.Security is None or self.Security.PriceStep is None:
return 1.0
step = float(self.Security.PriceStep)
if step <= 0.0:
return 1.0
decimals = self._get_decimal_places(step)
if decimals == 3 or decimals == 5:
return step * 10.0
return step
def _get_decimal_places(self, value):
value = abs(value)
decimals = 0
while value != int(value) and decimals < 10:
value *= 10.0
decimals += 1
return decimals
def OnReseted(self):
super(kijun_sen_robot_strategy, self).OnReseted()
self._previous_close = None
self._previous_ma = None
self._previous_prev_ma = None
self._previous_kijun = None
self._previous_prev_kijun = None
self._pending_long_level = None
self._pending_short_level = None
self._reset_position_state()
def CreateClone(self):
return kijun_sen_robot_strategy()