在 GitHub 上查看

随机指标卖出策略

概述

该策略移植自 MetaTrader 的 stochSell 专家顾问。策略只监听一种周期的蜡烛线,在满足三重随机指标确认以及波动率过滤后,先市价卖出,然后立即挂出一组卖出止损网格,以便在价格继续下跌时加仓。

交易逻辑

  • 波动率过滤:可配置周期的平均真实波动范围(ATR)必须低于设定阈值。
  • 慢速随机确认:最长周期的随机指标必须保持在长期超卖阈值以下,否则禁止交易。
  • 交叉确认:中速和快速随机指标必须在同一根完成的蜡烛上向下跌破超卖水平。
  • 仓位检查:只有当当前没有持仓且没有挂单时才允许开新仓。

当上述条件全部满足时,策略会按照设定手数发出市价卖单,并根据网格参数挂出若干卖出止损单。如果希望关闭加仓功能,只需把网格订单数量设为零。

平仓规则

  • 盈利目标:当整个空头组合的浮动盈利达到设定的点数时(根据加权平均开仓价计算),策略会市价买入平仓并撤销所有剩余挂单。
  • 挂单超时:每个网格挂单都有寿命限制,若在指定时间内未成交则自动撤单。
  • 完全离场:任何把仓位恢复为零的买入成交都会清空内部的入场统计并撤销网格挂单。

网格管理

  • 挂单价格 = 参考价 + 起始偏移 + 步长 × 序号,所有距离均以点数表示。
  • 网格挂单使用独立的成交量系数,可以与首笔市价单的手数不同。
  • 网格挂单的有效期以分钟计,设置为 0 表示无限期。

参数

参数 说明
CandleType 指标和交易使用的主周期。
AtrPeriod / AtrThreshold 控制波动率过滤的 ATR 周期和阈值。
FastKPeriod, FastDPeriod, FastSlowing 快速随机指标的参数。
MediumKPeriod, MediumDPeriod, MediumSlowing 中速随机指标的参数。
SlowKPeriod, SlowDPeriod, SlowSlowing 慢速随机指标的参数。
OversoldLevel 快速和中速随机指标需要向下突破的水平。
LongTermOversoldLevel 慢速随机指标在进场时允许的最大值。
ProfitTargetPips 平仓所需的利润点数。
GridOrdersCount 进场后挂出的卖出止损订单数量。
GridStartOffsetPips 第一张挂单相对于当前价的起始偏移。
GridStepPips 相邻挂单之间的距离。
GridVolume 每张挂单的成交量系数。
GridExpirationMinutes 网格挂单的有效期(分钟)。
MarketVolume 首笔市价卖单的手数。

备注

  • 策略通过高层 BindEx API 处理指标,只在蜡烛完成后做出决策。
  • 内部会维护加权平均开仓价,用于把利润目标从点数转换为价格。
  • 若只想做单次入场,可把网格订单数量设为 0,同时仍保留随机指标和 ATR 的过滤机制。
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;

public class StochSellStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public StochSellStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}