在 GitHub 上查看

IStochastic 交易策略

概述

IStochastic 交易策略是 MetaTrader 5 专家顾问 "IStochastic_Trading" 的 StockSharp 版本。策略使用随机指标识别超买与超卖区域,并按照马丁格尔思路逐级加仓,同时为每一笔仓位设置止损、止盈与跟踪止损。实现完全基于 StockSharp 的高级 API,在每根完成的 K 线之后决策,并仅发送市价单。

交易逻辑

  1. 构建带有可配置 %K 周期、%D 平滑周期和附加缓和系数的随机振荡指标。
  2. 当没有持仓时检查最新收盘的 K 线:
    • 如果 %K 高于 %D 且 %D 低于设定的买入区域,则开多单。
    • 如果 %K 低于 %D 且 %D 高于设定的卖出区域,则开空单。
  3. 已有仓位时跟踪最后一次加仓:
    • 当价格逆向运行达到设定的间隔(以点值计)时,并且尚未达到允许的最大仓位数,按同方向再开一笔仓位,手数为前一单的两倍。
  4. 对每笔仓位根据点值距离计算独立的止损和止盈价位,换算时使用合约的 PriceStep 和小数位数,以复现终端中的“调整点”逻辑。一旦收盘价触及止损或止盈,策略立即以市价平掉对应仓位。
  5. 在每根 K 线收盘后应用跟踪止损。当行情向有利方向移动超过设定的跟踪步长时,更新止损价,模拟终端对单笔仓位的逐步移动止损行为。

参数

名称 默认值 说明
OrderVolume 0.1 初始下单手数,之后的加仓按上一次手数翻倍。
TakeProfitPips 50 每笔仓位的止盈距离(点)。
StopLossPips 50 每笔仓位的止损距离(点)。
TrailingStopPips 10 跟踪止损距离(点),设为 0 可关闭。
TrailingStepPips 5 每次移动止损所需的最小有利运行(点)。
MaxPositions 3 同方向允许的最大仓位数量,设为 0 表示不限制。
GapPips 7 触发加倍加仓所需的逆向位移(点)。
KPeriod 5 计算 %K 所用的周期数。
DPeriod 3 %D 平滑平均的周期。
Slowing 3 额外应用于 %K 的平滑系数。
ZoneBuy 30 用于确认多头信号的 %D 阈值。
ZoneSell 70 用于确认空头信号的 %D 阈值。
CandleType 15 分钟 进行计算的 K 线时间框架。

实现说明

  • 点值换算基于 PriceStep。对于 3 位或 5 位报价,会额外乘以 10,以对齐 MetaTrader 的“调整点”规则。
  • 止损、止盈以及跟踪止损的触发依据收盘价判断,以确保回测时的确定性;若需要更细粒度的管理,可在实时环境中扩展处理。
  • 策略在任一时刻只维护一个方向的仓位梯队,必须全部平仓后才会反向建仓。
  • 根据需求未提供 Python 版本及其目录。
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Stochastic Oscillator-based strategy with zone filtering.
/// Goes long when %K crosses above %D in the oversold zone, short when %K crosses below %D in overbought zone.
/// </summary>
public class IStochasticTradingStrategy : Strategy
{
	private readonly StrategyParam<int> _kPeriod;
	private readonly StrategyParam<int> _dPeriod;
	private readonly StrategyParam<decimal> _zoneBuy;
	private readonly StrategyParam<decimal> _zoneSell;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _prevK;
	private decimal? _prevD;

	/// <summary>
	/// %K period.
	/// </summary>
	public int KPeriod
	{
		get => _kPeriod.Value;
		set => _kPeriod.Value = value;
	}

	/// <summary>
	/// %D smoothing period.
	/// </summary>
	public int DPeriod
	{
		get => _dPeriod.Value;
		set => _dPeriod.Value = value;
	}

	/// <summary>
	/// Buy zone threshold (oversold).
	/// </summary>
	public decimal ZoneBuy
	{
		get => _zoneBuy.Value;
		set => _zoneBuy.Value = value;
	}

	/// <summary>
	/// Sell zone threshold (overbought).
	/// </summary>
	public decimal ZoneSell
	{
		get => _zoneSell.Value;
		set => _zoneSell.Value = value;
	}

	/// <summary>
	/// Candle type used for analysis.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public IStochasticTradingStrategy()
	{
		_kPeriod = Param(nameof(KPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("K Period", "Number of bars for %K", "Indicators");

		_dPeriod = Param(nameof(DPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("D Period", "Smoothing period for %D", "Indicators");

		_zoneBuy = Param(nameof(ZoneBuy), 30m)
			.SetRange(0m, 100m)
			.SetDisplay("Buy Zone", "Upper boundary for bullish confirmation", "Signals");

		_zoneSell = Param(nameof(ZoneSell), 70m)
			.SetRange(0m, 100m)
			.SetDisplay("Sell Zone", "Lower boundary for bearish confirmation", "Signals");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe for the strategy", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevK = null;
		_prevD = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevK = null;
		_prevD = null;

		var stochastic = new StochasticOscillator();
		stochastic.K.Length = KPeriod;
		stochastic.D.Length = DPeriod;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(stochastic, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, stochastic);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue stochasticValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!stochasticValue.IsFinal)
			return;

		var stoch = (StochasticOscillatorValue)stochasticValue;

		if (stoch.K is not decimal kValue || stoch.D is not decimal dValue)
			return;

		if (_prevK is decimal prevK && _prevD is decimal prevD)
		{
			var crossedUp = prevK <= prevD && kValue > dValue;
			var crossedDown = prevK >= prevD && kValue < dValue;

			if (crossedUp && dValue < ZoneBuy && Position <= 0)
			{
				BuyMarket();
			}
			else if (crossedDown && dValue > ZoneSell && Position >= 0)
			{
				SellMarket();
			}
		}

		_prevK = kValue;
		_prevD = dValue;
	}
}