RM Stochastic Band 策略
概述
RM Stochastic Band Strategy 是 MetaTrader 专家顾问 EA RM Stochastic Band(作者 Ronny Maheza)的 StockSharp 高层 API 版本。策略同时监控三个不同时框上的随机指标,并且仅当三个时框的 %K 值同时指示超买或超卖时才开仓。进场之后,使用位于最高时框上的平均真实波幅(ATR)来计算止损和止盈,完全再现原始 EA 的波动率管理方法。此外,实现了可配置的最小资金阈值和自适应的点差过滤器。
核心逻辑
多时框确认
- 基础时框(默认 1 分钟)产生主要信号。
- 中级和高级时框(默认 5 分钟、15 分钟)必须与基础信号方向一致。
- 仅当三个时框的 %K 同时低于超卖阈值时买入;当三个时框的 %K 同时高于超买阈值时卖出。
ATR 波动率止损/止盈
- ATR 在最高时框(默认 15 分钟)上计算。
- 止损 =
入场价 ± ATR * StopLossMultiplier。 - 止盈 =
入场价 ± ATR * TakeProfitMultiplier。 - 在基础时框的已完成蜡烛上检查价格触及情况并市价离场。
执行与风控过滤
- 根据 Level-1 的最佳买卖价估算点差;如果当前点差超过标准上限,则使用更宽松的“分型账户”上限,与原 EA 的逻辑一致。
- 当投资组合价值低于
MinMargin时暂停交易。 - 同一时间仅允许一笔持仓,且存在活动委托时不会开新仓。
指标与订阅
| 指标 | 时框 | 用途 |
|---|---|---|
| 随机指标 (Stochastic Oscillator) | 基础时框 (默认 1 分钟) | 产生主要信号,仅使用 %K。 |
| 随机指标 | 中级时框 (默认 5 分钟) | 确认信号方向。 |
| 随机指标 | 高级时框 (默认 15 分钟) | 长周期确认。 |
| 平均真实波幅 (ATR) | 高级时框 (默认 15 分钟) | 计算止损和止盈距离。 |
策略还订阅 Level-1 行情以获取最佳买卖价,确保点差过滤器能够工作。
入场规则
- 做多:三个时框的 %K 值均低于
OversoldLevel。策略以OrderVolume的数量市价买入,并记录 ATR 计算出的止损/止盈。 - 做空:三个时框的 %K 值均高于
OverboughtLevel。策略以相同数量市价卖出。
出场规则
- 止损:多单在价格低点触及
入场价 - ATR * StopLossMultiplier时平仓;空单在高点触及入场价 + ATR * StopLossMultiplier时平仓。 - 止盈:多单在高点触及
入场价 + ATR * TakeProfitMultiplier时平仓;空单在低点触及入场价 - ATR * TakeProfitMultiplier时平仓。 - 每次平仓后都会清空内部的止损/止盈缓存,等待下一次信号重新计算。
参数
| 参数 | 说明 | 默认值 |
|---|---|---|
OrderVolume |
每次市价订单的成交量。 | 0.1 |
StochasticLength |
%K 的回溯长度。 | 5 |
StochasticSmoothing |
%K 的平滑参数。 | 3 |
StochasticSignalLength |
%D 的长度。 | 3 |
AtrPeriod |
在高时框计算 ATR 的周期。 | 14 |
StopLossMultiplier |
ATR 止损倍数。 | 1.5 |
TakeProfitMultiplier |
ATR 止盈倍数。 | 3.0 |
MinMargin |
允许交易的最小投资组合价值。 | 100 |
MaxSpreadStandard |
标准账户允许的最大点差。 | 3 |
MaxSpreadCent |
当标准上限被突破时使用的备用点差上限。 | 10 |
OversoldLevel |
判定超卖的 %K 阈值。 | 20 |
OverboughtLevel |
判定超买的 %K 阈值。 | 80 |
BaseCandleType |
基础时框(默认 1 分钟 K 线)。 | 1 分钟 |
MidCandleType |
中级确认时框。 | 5 分钟 |
HighCandleType |
高级确认 + ATR 时框。 | 15 分钟 |
所有参数均支持与原始 EA 相同的优化范围。
实现细节
- 指标值通过
SubscribeCandles(...).BindEx(...)获取,完全遵循 AGENTS.md 中的高层 API 要求,未直接访问内部缓存。 - 点差依据 Level-1 数据实时计算;若行情源缺少买卖报价,策略将保持待机状态,避免在不可靠的市场条件下下单。
- 头寸管理完全采用市价单,与原 EA 的做法保持一致。
- 原 MQL 代码虽定义了保本、追踪等输入参数,但并未实现相关逻辑,因此移植版本也不包含这些功能。
使用建议
- 在接入策略之前确认数据源提供 Level-1 行情,否则点差过滤会阻止交易。
- 根据标的资产的波动率调整随机指标阈值与 ATR 倍数。
- 在回测或优化时可尝试不同的时框组合,以适配与原始 M1/M5/M15 结构不同的市场周期。
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 stochastic oscillator strategy with ATR-based stop-loss and take-profit management.
/// Emulates the logic of the "EA RM Stochastic Band" MetaTrader expert advisor.
/// </summary>
public class RmStochasticBandStrategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<int> _stochasticLength;
private readonly StrategyParam<int> _stochasticSmoothing;
private readonly StrategyParam<int> _stochasticSignalLength;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _stopLossMultiplier;
private readonly StrategyParam<decimal> _takeProfitMultiplier;
private readonly StrategyParam<decimal> _minMargin;
private readonly StrategyParam<decimal> _maxSpreadStandard;
private readonly StrategyParam<decimal> _maxSpreadCent;
private readonly StrategyParam<decimal> _oversoldLevel;
private readonly StrategyParam<decimal> _overboughtLevel;
private readonly StrategyParam<DataType> _baseCandleType;
private readonly StrategyParam<DataType> _midCandleType;
private readonly StrategyParam<DataType> _highCandleType;
private decimal? _stochM1;
private decimal? _stochM5;
private decimal? _stochM15;
private decimal? _atrValue;
private decimal? _longStopPrice;
private decimal? _longTakeProfit;
private decimal? _shortStopPrice;
private decimal? _shortTakeProfit;
private decimal? _bestBid;
private decimal? _bestAsk;
/// <summary>
/// Trade volume used for market orders.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// %K lookback for the stochastic oscillator.
/// </summary>
public int StochasticLength
{
get => _stochasticLength.Value;
set => _stochasticLength.Value = value;
}
/// <summary>
/// Smoothing period applied to %K.
/// </summary>
public int StochasticSmoothing
{
get => _stochasticSmoothing.Value;
set => _stochasticSmoothing.Value = value;
}
/// <summary>
/// %D moving average length.
/// </summary>
public int StochasticSignalLength
{
get => _stochasticSignalLength.Value;
set => _stochasticSignalLength.Value = value;
}
/// <summary>
/// ATR lookback used for volatility-based exits.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Multiplier applied to ATR for stop-loss calculation.
/// </summary>
public decimal StopLossMultiplier
{
get => _stopLossMultiplier.Value;
set => _stopLossMultiplier.Value = value;
}
/// <summary>
/// Multiplier applied to ATR for take-profit calculation.
/// </summary>
public decimal TakeProfitMultiplier
{
get => _takeProfitMultiplier.Value;
set => _takeProfitMultiplier.Value = value;
}
/// <summary>
/// Minimum portfolio value required before placing trades.
/// </summary>
public decimal MinMargin
{
get => _minMargin.Value;
set => _minMargin.Value = value;
}
/// <summary>
/// Maximum spread (in price units) tolerated on standard accounts.
/// </summary>
public decimal MaxSpreadStandard
{
get => _maxSpreadStandard.Value;
set => _maxSpreadStandard.Value = value;
}
/// <summary>
/// Maximum spread (in price units) tolerated on cent accounts.
/// </summary>
public decimal MaxSpreadCent
{
get => _maxSpreadCent.Value;
set => _maxSpreadCent.Value = value;
}
/// <summary>
/// Threshold for oversold conditions.
/// </summary>
public decimal OversoldLevel
{
get => _oversoldLevel.Value;
set => _oversoldLevel.Value = value;
}
/// <summary>
/// Threshold for overbought conditions.
/// </summary>
public decimal OverboughtLevel
{
get => _overboughtLevel.Value;
set => _overboughtLevel.Value = value;
}
/// <summary>
/// Primary timeframe used for signal execution.
/// </summary>
public DataType BaseCandleType
{
get => _baseCandleType.Value;
set => _baseCandleType.Value = value;
}
/// <summary>
/// Intermediate timeframe used for stochastic confirmation.
/// </summary>
public DataType MidCandleType
{
get => _midCandleType.Value;
set => _midCandleType.Value = value;
}
/// <summary>
/// Higher timeframe used for stochastic confirmation and ATR calculation.
/// </summary>
public DataType HighCandleType
{
get => _highCandleType.Value;
set => _highCandleType.Value = value;
}
public RmStochasticBandStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Volume of each market order", "Trading");
_stochasticLength = Param(nameof(StochasticLength), 5)
.SetGreaterThanZero()
.SetDisplay("Stochastic Length", "%K lookback period", "Indicators")
.SetOptimize(3, 15, 1);
_stochasticSmoothing = Param(nameof(StochasticSmoothing), 3)
.SetGreaterThanZero()
.SetDisplay("Stochastic Smoothing", "Smoothing period applied to %K", "Indicators")
.SetOptimize(1, 7, 1);
_stochasticSignalLength = Param(nameof(StochasticSignalLength), 3)
.SetGreaterThanZero()
.SetDisplay("Stochastic Signal", "%D moving average length", "Indicators")
.SetOptimize(1, 10, 1);
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Lookback for ATR volatility filter", "Indicators")
.SetOptimize(7, 30, 1);
_stopLossMultiplier = Param(nameof(StopLossMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("SL Multiplier", "ATR multiplier for stop-loss", "Risk")
.SetOptimize(0.5m, 3m, 0.25m);
_takeProfitMultiplier = Param(nameof(TakeProfitMultiplier), 3m)
.SetGreaterThanZero()
.SetDisplay("TP Multiplier", "ATR multiplier for take-profit", "Risk")
.SetOptimize(1m, 6m, 0.5m);
_minMargin = Param(nameof(MinMargin), 100m)
.SetGreaterThanZero()
.SetDisplay("Minimum Margin", "Required portfolio value before trading", "Risk");
_maxSpreadStandard = Param(nameof(MaxSpreadStandard), 3m)
.SetGreaterThanZero()
.SetDisplay("Max Spread Standard", "Maximum spread allowed for standard accounts", "Filters");
_maxSpreadCent = Param(nameof(MaxSpreadCent), 10m)
.SetGreaterThanZero()
.SetDisplay("Max Spread Cent", "Maximum spread allowed for cent accounts", "Filters");
_oversoldLevel = Param(nameof(OversoldLevel), 20m)
.SetDisplay("Oversold Level", "Threshold that defines oversold conditions", "Signals")
.SetOptimize(5m, 40m, 5m);
_overboughtLevel = Param(nameof(OverboughtLevel), 80m)
.SetDisplay("Overbought Level", "Threshold that defines overbought conditions", "Signals")
.SetOptimize(60m, 95m, 5m);
_baseCandleType = Param(nameof(BaseCandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Base Timeframe", "Primary execution timeframe", "General");
_midCandleType = Param(nameof(MidCandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Mid Timeframe", "Secondary confirmation timeframe", "General");
_highCandleType = Param(nameof(HighCandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("High Timeframe", "Higher confirmation timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return
[
(Security, BaseCandleType),
(Security, MidCandleType),
(Security, HighCandleType)
];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_stochM1 = null;
_stochM5 = null;
_stochM15 = null;
_atrValue = null;
_longStopPrice = null;
_longTakeProfit = null;
_shortStopPrice = null;
_shortTakeProfit = null;
_bestBid = null;
_bestAsk = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var baseStochastic = CreateStochastic();
var midStochastic = CreateStochastic();
var highStochastic = CreateStochastic();
var atr = new AverageTrueRange { Length = AtrPeriod };
var baseSubscription = SubscribeCandles(BaseCandleType);
baseSubscription.BindEx(baseStochastic, ProcessBaseCandle).Start();
SubscribeCandles(MidCandleType)
.BindEx(midStochastic, ProcessMidCandle)
.Start();
SubscribeCandles(HighCandleType)
.BindEx(highStochastic, atr, ProcessHighCandle)
.Start();
// Level1 removed for backtest compatibility
}
private StochasticOscillator CreateStochastic()
{
return new StochasticOscillator
{
K = { Length = StochasticLength },
D = { Length = StochasticSignalLength }
};
}
private void ProcessLevel1(Level1ChangeMessage message)
{
if (message.Changes.TryGetValue(Level1Fields.BestBidPrice, out var bidValue))
_bestBid = (decimal)bidValue;
if (message.Changes.TryGetValue(Level1Fields.BestAskPrice, out var askValue))
_bestAsk = (decimal)askValue;
}
private void ProcessMidCandle(ICandleMessage candle, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!stochValue.IsFinal)
return;
var stochastic = (StochasticOscillatorValue)stochValue;
if (stochastic.K is decimal kValue)
_stochM5 = kValue;
}
private void ProcessHighCandle(ICandleMessage candle, IIndicatorValue stochValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!stochValue.IsFinal || !atrValue.IsFinal)
return;
var stochastic = (StochasticOscillatorValue)stochValue;
if (stochastic.K is decimal kValue)
_stochM15 = kValue;
_atrValue = atrValue.ToDecimal();
}
private void ProcessBaseCandle(ICandleMessage candle, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!stochValue.IsFinal)
return;
var stochastic = (StochasticOscillatorValue)stochValue;
if (stochastic.K is not decimal kValue)
return;
_stochM1 = kValue;
ManageOpenPosition(candle);
TryEnterPosition(candle);
}
private void ManageOpenPosition(ICandleMessage candle)
{
if (Position == 0)
{
_longStopPrice = null;
_longTakeProfit = null;
_shortStopPrice = null;
_shortTakeProfit = null;
return;
}
if (Position > 0)
{
if (_longStopPrice is decimal stop && candle.LowPrice <= stop)
{
SellMarket(Position);
_longStopPrice = null;
_longTakeProfit = null;
return;
}
if (_longTakeProfit is decimal target && candle.HighPrice >= target)
{
SellMarket(Position);
_longStopPrice = null;
_longTakeProfit = null;
}
}
else if (Position < 0)
{
var shortVolume = Math.Abs(Position);
if (_shortStopPrice is decimal stop && candle.HighPrice >= stop)
{
BuyMarket(shortVolume);
_shortStopPrice = null;
_shortTakeProfit = null;
return;
}
if (_shortTakeProfit is decimal target && candle.LowPrice <= target)
{
BuyMarket(shortVolume);
_shortStopPrice = null;
_shortTakeProfit = null;
}
}
}
private void TryEnterPosition(ICandleMessage candle)
{
if (!HasSufficientMargin())
return;
if (Position != 0)
return;
if (HasActiveOrders())
return;
if (_stochM1 is not decimal stochFast ||
_stochM5 is not decimal stochMid ||
_stochM15 is not decimal stochSlow ||
_atrValue is not decimal atr)
{
return;
}
var oversold = OversoldLevel;
var overbought = OverboughtLevel;
if (stochFast < oversold && stochMid < oversold && stochSlow < oversold)
{
EnterLong(candle.ClosePrice, atr);
}
else if (stochFast > overbought && stochMid > overbought && stochSlow > overbought)
{
EnterShort(candle.ClosePrice, atr);
}
}
private bool HasSufficientMargin()
{
var currentValue = Portfolio?.CurrentValue ?? 0m;
return currentValue >= MinMargin;
}
private bool IsSpreadAcceptable()
{
if (_bestBid is not decimal bid || _bestAsk is not decimal ask)
return false;
var spread = ask - bid;
if (spread <= 0m)
return true;
var limit = spread > MaxSpreadStandard ? MaxSpreadCent : MaxSpreadStandard;
return spread <= limit;
}
private bool HasActiveOrders()
{
foreach (var order in Orders)
{
if (!order.State.IsFinal())
return true;
}
return false;
}
private void EnterLong(decimal price, decimal atr)
{
var volume = OrderVolume;
if (volume <= 0m)
return;
BuyMarket(volume);
_longStopPrice = price - atr * StopLossMultiplier;
_longTakeProfit = price + atr * TakeProfitMultiplier;
_shortStopPrice = null;
_shortTakeProfit = null;
}
private void EnterShort(decimal price, decimal atr)
{
var volume = OrderVolume;
if (volume <= 0m)
return;
SellMarket(volume);
_shortStopPrice = price + atr * StopLossMultiplier;
_shortTakeProfit = price - atr * TakeProfitMultiplier;
_longStopPrice = null;
_longTakeProfit = 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, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import StochasticOscillator, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class rm_stochastic_band_strategy(Strategy):
"""Multi-timeframe stochastic oscillator strategy with ATR-based stop-loss and take-profit."""
def __init__(self):
super(rm_stochastic_band_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 0.1) \
.SetGreaterThanZero() \
.SetDisplay("Order Volume", "Volume of each market order", "Trading")
self._stochastic_length = self.Param("StochasticLength", 5) \
.SetGreaterThanZero() \
.SetDisplay("Stochastic Length", "%K lookback period", "Indicators")
self._stochastic_smoothing = self.Param("StochasticSmoothing", 3) \
.SetGreaterThanZero() \
.SetDisplay("Stochastic Smoothing", "Smoothing period applied to %K", "Indicators")
self._stochastic_signal_length = self.Param("StochasticSignalLength", 3) \
.SetGreaterThanZero() \
.SetDisplay("Stochastic Signal", "%D moving average length", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("ATR Period", "Lookback for ATR volatility filter", "Indicators")
self._stop_loss_multiplier = self.Param("StopLossMultiplier", 1.5) \
.SetGreaterThanZero() \
.SetDisplay("SL Multiplier", "ATR multiplier for stop-loss", "Risk")
self._take_profit_multiplier = self.Param("TakeProfitMultiplier", 3.0) \
.SetGreaterThanZero() \
.SetDisplay("TP Multiplier", "ATR multiplier for take-profit", "Risk")
self._oversold_level = self.Param("OversoldLevel", 20.0) \
.SetDisplay("Oversold Level", "Threshold that defines oversold conditions", "Signals")
self._overbought_level = self.Param("OverboughtLevel", 80.0) \
.SetDisplay("Overbought Level", "Threshold that defines overbought conditions", "Signals")
self._base_candle_type = self.Param("BaseCandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Base Timeframe", "Primary execution timeframe", "General")
self._mid_candle_type = self.Param("MidCandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Mid Timeframe", "Secondary confirmation timeframe", "General")
self._high_candle_type = self.Param("HighCandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("High Timeframe", "Higher confirmation timeframe", "General")
self._stoch_m1 = None
self._stoch_m5 = None
self._stoch_m15 = None
self._atr_value = None
self._long_stop_price = None
self._long_take_profit = None
self._short_stop_price = None
self._short_take_profit = None
@property
def CandleType(self):
return self._base_candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._base_candle_type.Value = value
@property
def OrderVolume(self):
return self._order_volume.Value
@property
def StochasticLength(self):
return self._stochastic_length.Value
@property
def StochasticSignalLength(self):
return self._stochastic_signal_length.Value
@property
def AtrPeriod(self):
return self._atr_period.Value
@property
def StopLossMultiplier(self):
return self._stop_loss_multiplier.Value
@property
def TakeProfitMultiplier(self):
return self._take_profit_multiplier.Value
@property
def OversoldLevel(self):
return self._oversold_level.Value
@property
def OverboughtLevel(self):
return self._overbought_level.Value
@property
def BaseCandleType(self):
return self._base_candle_type.Value
@property
def MidCandleType(self):
return self._mid_candle_type.Value
@property
def HighCandleType(self):
return self._high_candle_type.Value
def OnReseted(self):
super(rm_stochastic_band_strategy, self).OnReseted()
self._stoch_m1 = None
self._stoch_m5 = None
self._stoch_m15 = None
self._atr_value = None
self._long_stop_price = None
self._long_take_profit = None
self._short_stop_price = None
self._short_take_profit = None
def _create_stochastic(self):
stoch = StochasticOscillator()
stoch.K.Length = self.StochasticLength
stoch.D.Length = self.StochasticSignalLength
return stoch
def OnStarted2(self, time):
super(rm_stochastic_band_strategy, self).OnStarted2(time)
base_stochastic = self._create_stochastic()
mid_stochastic = self._create_stochastic()
high_stochastic = self._create_stochastic()
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
base_subscription = self.SubscribeCandles(self.BaseCandleType)
base_subscription.BindEx(base_stochastic, self._process_base_candle).Start()
self.SubscribeCandles(self.MidCandleType) \
.BindEx(mid_stochastic, self._process_mid_candle).Start()
self.SubscribeCandles(self.HighCandleType) \
.BindEx(high_stochastic, atr, self._process_high_candle).Start()
def _process_mid_candle(self, candle, stoch_value):
if candle.State != CandleStates.Finished:
return
if not stoch_value.IsFinal:
return
k = stoch_value.K
if k is not None:
self._stoch_m5 = float(k)
def _process_high_candle(self, candle, stoch_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not stoch_value.IsFinal or not atr_value.IsFinal:
return
k = stoch_value.K
if k is not None:
self._stoch_m15 = float(k)
self._atr_value = float(atr_value)
def _process_base_candle(self, candle, stoch_value):
if candle.State != CandleStates.Finished:
return
if not stoch_value.IsFinal:
return
k = stoch_value.K
if k is None:
return
self._stoch_m1 = float(k)
self._manage_open_position(candle)
self._try_enter_position(candle)
def _manage_open_position(self, candle):
if self.Position == 0:
self._long_stop_price = None
self._long_take_profit = None
self._short_stop_price = None
self._short_take_profit = None
return
if self.Position > 0:
if self._long_stop_price is not None and float(candle.LowPrice) <= self._long_stop_price:
self.SellMarket(self.Position)
self._long_stop_price = None
self._long_take_profit = None
return
if self._long_take_profit is not None and float(candle.HighPrice) >= self._long_take_profit:
self.SellMarket(self.Position)
self._long_stop_price = None
self._long_take_profit = None
elif self.Position < 0:
short_volume = abs(self.Position)
if self._short_stop_price is not None and float(candle.HighPrice) >= self._short_stop_price:
self.BuyMarket(short_volume)
self._short_stop_price = None
self._short_take_profit = None
return
if self._short_take_profit is not None and float(candle.LowPrice) <= self._short_take_profit:
self.BuyMarket(short_volume)
self._short_stop_price = None
self._short_take_profit = None
def _try_enter_position(self, candle):
if self.Position != 0:
return
if self._stoch_m1 is None or self._stoch_m5 is None or self._stoch_m15 is None or self._atr_value is None:
return
stoch_fast = self._stoch_m1
stoch_mid = self._stoch_m5
stoch_slow = self._stoch_m15
atr = self._atr_value
oversold = float(self.OversoldLevel)
overbought = float(self.OverboughtLevel)
if stoch_fast < oversold and stoch_mid < oversold and stoch_slow < oversold:
self._enter_long(float(candle.ClosePrice), atr)
elif stoch_fast > overbought and stoch_mid > overbought and stoch_slow > overbought:
self._enter_short(float(candle.ClosePrice), atr)
def _enter_long(self, price, atr):
volume = float(self.OrderVolume)
if volume <= 0:
return
self.BuyMarket(volume)
self._long_stop_price = price - atr * float(self.StopLossMultiplier)
self._long_take_profit = price + atr * float(self.TakeProfitMultiplier)
self._short_stop_price = None
self._short_take_profit = None
def _enter_short(self, price, atr):
volume = float(self.OrderVolume)
if volume <= 0:
return
self.SellMarket(volume)
self._short_stop_price = price + atr * float(self.StopLossMultiplier)
self._short_take_profit = price - atr * float(self.TakeProfitMultiplier)
self._long_stop_price = None
self._long_take_profit = None
def CreateClone(self):
return rm_stochastic_band_strategy()