Farhad Crab 策略 (C#)
概述
Farhad Crab 是一套趋势型策略,通过考察价格相对指数移动平均线 (EMA) 的位置来寻找回调入场机会,并使用固定止损、止盈、移动止损以及日线过滤器进行风险控制。原始的 MT5 专家顾问使用小时级别的 K 线执行交易,同时参考日线数据来决定是否平仓。本 C# 版本保持了这一框架,将工作周期的 EMA 与日线 EMA 交叉过滤结合使用。
核心思想
- 趋势过滤器: 在工作周期上计算 EMA(默认是 1 小时 K 线上的 15 周期 EMA)。若上一根 K 线的最低价高于 EMA,则允许做多;若上一根 K 线的最高价低于 EMA,则允许做空。
- 日线过滤器: 在日线周期上再计算一条 EMA。当日线 EMA 从下向上穿越日线收盘价时,平掉所有多单;当日线 EMA 从上向下穿越收盘价时,平掉所有空单。
- 风险管理: 止损和止盈以点 (pip) 为单位设置。移动止损在浮盈超过“移动止损距离 + 止损步长”时上移/下移保护价位,模拟 MT5 中
TrailingStop与TrailingStep的组合行为。 - 单一净头寸: 策略只维持一个净持仓量。当方向反转时,会先平掉原有仓位,再按照
Volume参数开出新仓。
交易规则
- 入场条件(工作周期):
- 若上一根 K 线的最低价高于 EMA(考虑
MaShift设定的位移),则做多。 - 若上一根 K 线的最高价低于 EMA,则做空。
- 若上一根 K 线的最低价高于 EMA(考虑
- 下单数量:
Volume参数定义基准下单量。若需要反向开仓,会自动加上现有反向仓位的数量以完成净头寸切换。 - 止损/止盈:
- 距离以点 (pip) 表示。点值根据品种的最小价格变动 (
PriceStep) 自动计算,对三位或五位报价的外汇品种会额外乘以 10,以符合 MT5 的处理方式。 - 将参数设为
0即可关闭相应的止损或止盈。
- 距离以点 (pip) 表示。点值根据品种的最小价格变动 (
- 移动止损:
- 仅当
TrailingStopPips大于 0 时启用。 - 做多时,当浮盈超过
TrailingStopPips + TrailingStepPips,止损价格更新为当前价 - TrailingStopPips;做空时对称处理。 - 止损步长可避免过于频繁地调整保护价。
- 仅当
- 日线平仓过滤:
- 使用最近两根已完成的日线。
- 如果两天前日线 EMA 低于收盘价,而昨天日线 EMA 高于收盘价,则平掉多单。
- 如果出现相反的穿越,则平掉空单。
参数
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
CandleType |
DataType |
1 小时时间框架 | 用于计算信号的工作周期。 |
MaLength |
int |
15 | 工作周期 EMA 的周期长度。 |
MaShift |
int |
0 | 将 EMA 值向后偏移的已完成 K 线数量。 |
DailyMaLength |
int |
15 | 日线 EMA 的周期长度,用于交叉平仓过滤。 |
StopLossPips |
decimal |
50 | 止损距离(点)。设为 0 可禁用。 |
TakeProfitPips |
decimal |
50 | 止盈距离(点)。设为 0 可禁用。 |
TrailingStopPips |
decimal |
10 | 移动止损距离(点)。设为 0 可禁用移动止损。 |
TrailingStepPips |
decimal |
5 | 重新上移/下移移动止损前所需的额外盈利(点)。 |
Volume |
decimal |
0.1 | 每次下单的基准数量。 |
与 MT5 版本的差异
- 仅实现指数移动平均,保持了原脚本的默认设置;其他平滑方式暂不支持。
- MT5 版本基于逐笔报价执行止损/止盈,这里改为在 K 线完成后根据最高价和最低价判断是否触发。
- 原代码中引用的 Parabolic SAR 指标未实际用于决策,因此在移植版本中移除。
- 移动止损通过内部记录的价格水平工作,不直接下发经纪商止损单;当价格触及计算出的水平时,策略会在下一根 K 线上执行平仓。
使用建议
- 根据目标交易节奏选择合适的
CandleType。默认的一小时 K 线可以重现原脚本的行为。 - 同时调整
MaLength与DailyMaLength,在入场敏感度与趋势过滤之间找到平衡。 - 对于五位报价的外汇产品,点值会自动换算为 0.0001。
- 回测时请确保获取到日线数据,以便日线 EMA 过滤器能够正常运行。
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>
/// Trend-following strategy converted from the FarhadCrab1 MT5 expert advisor.
/// The strategy enters on pullbacks to an EMA, manages risk with pip-based levels,
/// and closes positions based on a daily EMA crossover filter.
/// </summary>
public class FarhadCrab1Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _maLength;
private readonly StrategyParam<int> _maShift;
private readonly StrategyParam<int> _dailyMaLength;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _trailingStopPips;
private readonly StrategyParam<decimal> _trailingStepPips;
private readonly StrategyParam<decimal> _orderVolume;
private readonly Queue<decimal> _maValues = new();
private readonly DataType _dailyCandleType = TimeSpan.FromHours(1).TimeFrame();
private decimal? _stopLossPrice;
private decimal? _takeProfitPrice;
private decimal? _prevDailyClose;
private decimal? _prevDailyMa;
private decimal? _prevPrevDailyClose;
private decimal? _prevPrevDailyMa;
private ICandleMessage _previousCandle;
private decimal _entryPrice;
/// <summary>
/// Working candle type for the execution timeframe.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Period of the EMA used on the working timeframe.
/// </summary>
public int MaLength
{
get => _maLength.Value;
set => _maLength.Value = value;
}
/// <summary>
/// Number of completed candles to shift the EMA backwards.
/// </summary>
public int MaShift
{
get => _maShift.Value;
set => _maShift.Value = value;
}
/// <summary>
/// Period of the daily EMA that closes positions on crossovers.
/// </summary>
public int DailyMaLength
{
get => _dailyMaLength.Value;
set => _dailyMaLength.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take-profit distance expressed in pips.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips.
/// </summary>
public decimal TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Additional pip distance required before updating the trailing stop again.
/// </summary>
public decimal TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Base order volume in lots/contracts.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="FarhadCrab1Strategy"/> class.
/// </summary>
public FarhadCrab1Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Execution timeframe", "General");
_maLength = Param(nameof(MaLength), 15)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "EMA period on the working timeframe", "Indicators");
_maShift = Param(nameof(MaShift), 0)
.SetRange(0, 100)
.SetDisplay("EMA Shift", "Shift EMA value backwards by N candles", "Indicators");
_dailyMaLength = Param(nameof(DailyMaLength), 15)
.SetGreaterThanZero()
.SetDisplay("Daily EMA Length", "EMA period used on daily candles", "Indicators");
_stopLossPips = Param(nameof(StopLossPips), 50m)
.SetRange(0m, 500m)
.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Protection");
_takeProfitPips = Param(nameof(TakeProfitPips), 50m)
.SetRange(0m, 500m)
.SetDisplay("Take Profit (pips)", "Take-profit distance in pips", "Protection");
_trailingStopPips = Param(nameof(TrailingStopPips), 10m)
.SetRange(0m, 500m)
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Protection");
_trailingStepPips = Param(nameof(TrailingStepPips), 5m)
.SetRange(0m, 500m)
.SetDisplay("Trailing Step (pips)", "Extra gain in pips before updating the trailing stop", "Protection");
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Volume", "Base order volume", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType), (Security, _dailyCandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_maValues.Clear();
_stopLossPrice = null;
_takeProfitPrice = null;
_prevDailyClose = null;
_prevDailyMa = null;
_prevPrevDailyClose = null;
_prevPrevDailyMa = null;
_previousCandle = null;
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Ensure the base strategy volume reflects the configured parameter.
base.Volume = OrderVolume;
// Subscribe to the working timeframe candles with an EMA for entry decisions.
var ema = new ExponentialMovingAverage { Length = MaLength };
var candleSubscription = SubscribeCandles(CandleType);
candleSubscription
.Bind(ema, ProcessWorkingCandle)
.Start();
// Subscribe to daily candles with another EMA for exit filtering.
var dailyEma = new ExponentialMovingAverage { Length = DailyMaLength };
var dailySubscription = SubscribeCandles(_dailyCandleType);
dailySubscription
.Bind(dailyEma, ProcessDailyCandle)
.Start();
// Draw candles, indicator, and trades on the chart if charting is available.
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, candleSubscription);
DrawOwnTrades(area);
DrawIndicator(area, ema);
}
}
private void ProcessDailyCandle(ICandleMessage candle, decimal emaValue)
{
// Process only finished daily candles.
if (candle.State != CandleStates.Finished)
return;
_prevPrevDailyClose = _prevDailyClose;
_prevPrevDailyMa = _prevDailyMa;
_prevDailyClose = candle.ClosePrice;
_prevDailyMa = emaValue;
}
private void ProcessWorkingCandle(ICandleMessage candle, decimal emaValue)
{
// Use only completed candles for decision making.
if (candle.State != CandleStates.Finished)
return;
// Store EMA values so we can apply the configured shift.
UpdateMaBuffer(emaValue);
var shiftedMa = GetShiftedMaValue();
if (shiftedMa == null)
{
_previousCandle = candle;
return;
}
// Require at least one previous candle to evaluate entry conditions.
if (_previousCandle == null)
{
_previousCandle = candle;
return;
}
// Close positions when the daily EMA filter signals a crossover against us.
if (TryCloseByDailyFilter())
{
_previousCandle = candle;
return;
}
var pipSize = GetPipSize();
// Check for stop-loss or take-profit triggers before adjusting trailing stops.
if (CheckStopsAndTargets(candle))
{
_previousCandle = candle;
return;
}
// Update trailing stop levels if the position has moved far enough.
ApplyTrailingStop(candle, pipSize);
// Evaluate long entry condition: previous low above the EMA.
if (Position <= 0 && _previousCandle.LowPrice > shiftedMa.Value)
{
var volume = OrderVolume + (Position < 0 ? -Position : 0m);
if (volume > 0)
{
BuyMarket(volume);
SetRiskLevels(candle.ClosePrice, pipSize, true);
}
_previousCandle = candle;
return;
}
// Evaluate short entry condition: previous high below the EMA.
if (Position >= 0 && _previousCandle.HighPrice < shiftedMa.Value)
{
var volume = OrderVolume + (Position > 0 ? Position : 0m);
if (volume > 0)
{
SellMarket(volume);
SetRiskLevels(candle.ClosePrice, pipSize, false);
}
_previousCandle = candle;
return;
}
// Store the current candle for the next iteration.
_previousCandle = candle;
}
private bool TryCloseByDailyFilter()
{
if (_prevDailyClose == null || _prevDailyMa == null || _prevPrevDailyClose == null || _prevPrevDailyMa == null)
return false;
var prevClose = _prevDailyClose.Value;
var prevMa = _prevDailyMa.Value;
var prev2Close = _prevPrevDailyClose.Value;
var prev2Ma = _prevPrevDailyMa.Value;
// Bearish crossover: EMA moved above the daily close -> exit long positions.
if (Position > 0 && prevMa > prevClose && prev2Ma < prev2Close)
{
SellMarket(Position);
ResetRiskLevels();
return true;
}
// Bullish crossover: EMA moved below the daily close -> exit short positions.
if (Position < 0 && prevMa < prevClose && prev2Ma > prev2Close)
{
BuyMarket(-Position);
ResetRiskLevels();
return true;
}
return false;
}
private bool CheckStopsAndTargets(ICandleMessage candle)
{
if (Position > 0)
{
if (_stopLossPrice.HasValue && candle.LowPrice <= _stopLossPrice.Value)
{
SellMarket(Position);
ResetRiskLevels();
return true;
}
if (_takeProfitPrice.HasValue && candle.HighPrice >= _takeProfitPrice.Value)
{
SellMarket(Position);
ResetRiskLevels();
return true;
}
}
else if (Position < 0)
{
if (_stopLossPrice.HasValue && candle.HighPrice >= _stopLossPrice.Value)
{
BuyMarket(-Position);
ResetRiskLevels();
return true;
}
if (_takeProfitPrice.HasValue && candle.LowPrice <= _takeProfitPrice.Value)
{
BuyMarket(-Position);
ResetRiskLevels();
return true;
}
}
else if (_stopLossPrice.HasValue || _takeProfitPrice.HasValue)
{
// Clear stored levels when the position is flat.
ResetRiskLevels();
}
return false;
}
private void ApplyTrailingStop(ICandleMessage candle, decimal pipSize)
{
if (TrailingStopPips <= 0m)
return;
var entryPrice = _entryPrice;
var threshold = (TrailingStopPips + TrailingStepPips) * pipSize;
if (Position > 0)
{
var profit = candle.ClosePrice - entryPrice;
if (profit > threshold)
{
var minStop = candle.ClosePrice - threshold;
var candidate = candle.ClosePrice - TrailingStopPips * pipSize;
if (!_stopLossPrice.HasValue || _stopLossPrice.Value < minStop)
_stopLossPrice = candidate;
}
}
else if (Position < 0)
{
var profit = entryPrice - candle.ClosePrice;
if (profit > threshold)
{
var maxStop = candle.ClosePrice + threshold;
var candidate = candle.ClosePrice + TrailingStopPips * pipSize;
if (!_stopLossPrice.HasValue || _stopLossPrice.Value > maxStop)
_stopLossPrice = candidate;
}
}
}
private void SetRiskLevels(decimal executionPrice, decimal pipSize, bool isLong)
{
_entryPrice = executionPrice;
if (StopLossPips > 0m && pipSize > 0m)
_stopLossPrice = isLong
? executionPrice - StopLossPips * pipSize
: executionPrice + StopLossPips * pipSize;
else
_stopLossPrice = null;
if (TakeProfitPips > 0m && pipSize > 0m)
_takeProfitPrice = isLong
? executionPrice + TakeProfitPips * pipSize
: executionPrice - TakeProfitPips * pipSize;
else
_takeProfitPrice = null;
}
private void ResetRiskLevels()
{
_stopLossPrice = null;
_takeProfitPrice = null;
}
private decimal GetPipSize()
{
var security = Security;
if (security == null)
return 0.0001m;
var step = security.PriceStep ?? 0.0001m;
var decimals = security.Decimals;
if (decimals == 3 || decimals == 5)
return step * 10m;
return step;
}
private void UpdateMaBuffer(decimal emaValue)
{
_maValues.Enqueue(emaValue);
var maxCount = Math.Max(MaShift + 1, 1);
while (_maValues.Count > maxCount)
_maValues.Dequeue();
}
private decimal? GetShiftedMaValue()
{
var count = _maValues.Count;
var targetIndex = count - MaShift - 1;
if (targetIndex < 0)
return null;
var index = 0;
foreach (var value in _maValues)
{
if (index == targetIndex)
return value;
index++;
}
return null;
}
}
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 collections import deque
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class farhad_crab1_strategy(Strategy):
"""
FarhadCrab1: Trend-following on EMA pullbacks with trailing stop,
daily EMA crossover filter for exits, and pip-based SL/TP.
"""
def __init__(self):
super(farhad_crab1_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Execution timeframe", "General")
self._ma_length = self.Param("MaLength", 15) \
.SetDisplay("EMA Length", "EMA period", "Indicators")
self._ma_shift = self.Param("MaShift", 0) \
.SetDisplay("EMA Shift", "Shift EMA backwards by N candles", "Indicators")
self._daily_ma_length = self.Param("DailyMaLength", 15) \
.SetDisplay("Daily EMA Length", "EMA period on daily candles", "Indicators")
self._stop_loss_pips = self.Param("StopLossPips", 50.0) \
.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Protection")
self._take_profit_pips = self.Param("TakeProfitPips", 50.0) \
.SetDisplay("Take Profit (pips)", "Take-profit distance in pips", "Protection")
self._trailing_stop_pips = self.Param("TrailingStopPips", 10.0) \
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance", "Protection")
self._trailing_step_pips = self.Param("TrailingStepPips", 5.0) \
.SetDisplay("Trailing Step (pips)", "Extra gain before updating trailing stop", "Protection")
self._daily_candle_type = DataType.TimeFrame(TimeSpan.FromHours(1))
self._ma_values = deque()
self._stop_loss_price = None
self._take_profit_price = None
self._prev_daily_close = None
self._prev_daily_ma = None
self._prev_prev_daily_close = None
self._prev_prev_daily_ma = None
self._previous_candle = None
self._entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(farhad_crab1_strategy, self).OnReseted()
self._ma_values.clear()
self._stop_loss_price = None
self._take_profit_price = None
self._prev_daily_close = None
self._prev_daily_ma = None
self._prev_prev_daily_close = None
self._prev_prev_daily_ma = None
self._previous_candle = None
self._entry_price = 0.0
def OnStarted2(self, time):
super(farhad_crab1_strategy, self).OnStarted2(time)
ema = ExponentialMovingAverage()
ema.Length = self._ma_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, self._process_working_candle).Start()
daily_ema = ExponentialMovingAverage()
daily_ema.Length = self._daily_ma_length.Value
daily_sub = self.SubscribeCandles(self._daily_candle_type)
daily_sub.Bind(daily_ema, self._process_daily_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_daily_candle(self, candle, ema_val):
if candle.State != CandleStates.Finished:
return
self._prev_prev_daily_close = self._prev_daily_close
self._prev_prev_daily_ma = self._prev_daily_ma
self._prev_daily_close = float(candle.ClosePrice)
self._prev_daily_ma = float(ema_val)
def _process_working_candle(self, candle, ema_val):
if candle.State != CandleStates.Finished:
return
ema_val = float(ema_val)
self._update_ma_buffer(ema_val)
shifted_ma = self._get_shifted_ma()
if shifted_ma is None:
self._previous_candle = candle
return
if self._previous_candle is None:
self._previous_candle = candle
return
if self._try_close_by_daily_filter():
self._previous_candle = candle
return
pip_size = self._get_pip_size()
close = float(candle.ClosePrice)
if self._check_stops(candle):
self._previous_candle = candle
return
self._apply_trailing(candle, pip_size)
prev_low = float(self._previous_candle.LowPrice)
prev_high = float(self._previous_candle.HighPrice)
if self.Position <= 0 and prev_low > shifted_ma:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._set_risk_levels(close, pip_size, True)
self._previous_candle = candle
return
if self.Position >= 0 and prev_high < shifted_ma:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._set_risk_levels(close, pip_size, False)
self._previous_candle = candle
return
self._previous_candle = candle
def _try_close_by_daily_filter(self):
if (self._prev_daily_close is None or self._prev_daily_ma is None or
self._prev_prev_daily_close is None or self._prev_prev_daily_ma is None):
return False
if (self.Position > 0 and
self._prev_daily_ma > self._prev_daily_close and
self._prev_prev_daily_ma < self._prev_prev_daily_close):
self.SellMarket()
self._reset_risk()
return True
if (self.Position < 0 and
self._prev_daily_ma < self._prev_daily_close and
self._prev_prev_daily_ma > self._prev_prev_daily_close):
self.BuyMarket()
self._reset_risk()
return True
return False
def _check_stops(self, candle):
low = float(candle.LowPrice)
high = float(candle.HighPrice)
if self.Position > 0:
if self._stop_loss_price is not None and low <= self._stop_loss_price:
self.SellMarket()
self._reset_risk()
return True
if self._take_profit_price is not None and high >= self._take_profit_price:
self.SellMarket()
self._reset_risk()
return True
elif self.Position < 0:
if self._stop_loss_price is not None and high >= self._stop_loss_price:
self.BuyMarket()
self._reset_risk()
return True
if self._take_profit_price is not None and low <= self._take_profit_price:
self.BuyMarket()
self._reset_risk()
return True
elif self._stop_loss_price is not None or self._take_profit_price is not None:
self._reset_risk()
return False
def _apply_trailing(self, candle, pip_size):
ts_pips = float(self._trailing_stop_pips.Value)
if ts_pips <= 0:
return
step_pips = float(self._trailing_step_pips.Value)
threshold = (ts_pips + step_pips) * pip_size
close = float(candle.ClosePrice)
if self.Position > 0:
profit = close - self._entry_price
if profit > threshold:
candidate = close - ts_pips * pip_size
min_stop = close - threshold
if self._stop_loss_price is None or self._stop_loss_price < min_stop:
self._stop_loss_price = candidate
elif self.Position < 0:
profit = self._entry_price - close
if profit > threshold:
candidate = close + ts_pips * pip_size
max_stop = close + threshold
if self._stop_loss_price is None or self._stop_loss_price > max_stop:
self._stop_loss_price = candidate
def _set_risk_levels(self, price, pip_size, is_long):
self._entry_price = price
sl_pips = float(self._stop_loss_pips.Value)
tp_pips = float(self._take_profit_pips.Value)
if sl_pips > 0 and pip_size > 0:
self._stop_loss_price = price - sl_pips * pip_size if is_long else price + sl_pips * pip_size
else:
self._stop_loss_price = None
if tp_pips > 0 and pip_size > 0:
self._take_profit_price = price + tp_pips * pip_size if is_long else price - tp_pips * pip_size
else:
self._take_profit_price = None
def _reset_risk(self):
self._stop_loss_price = None
self._take_profit_price = None
def _get_pip_size(self):
if self.Security is None:
return 0.0001
step = float(self.Security.PriceStep) if self.Security.PriceStep is not None else 0.0001
decimals = self.Security.Decimals
if decimals == 3 or decimals == 5:
return step * 10
return step
def _update_ma_buffer(self, ema_val):
self._ma_values.append(ema_val)
max_count = max(self._ma_shift.Value + 1, 1)
while len(self._ma_values) > max_count:
self._ma_values.popleft()
def _get_shifted_ma(self):
count = len(self._ma_values)
target = count - self._ma_shift.Value - 1
if target < 0:
return None
idx = 0
for v in self._ma_values:
if idx == target:
return v
idx += 1
return None
def CreateClone(self):
return farhad_crab1_strategy()