在 GitHub 上查看

AfterEffects 策略

AfterEffects 策略基于价格序列可能存在的滞后效应。 它利用当前收盘价以及 p2p 根之前的开盘价计算信号:

signal = Close - 2 * Open[p] + Open[2p]

信号为正时开多,为负时做空。Random 参数会取反该信号。

进场后策略在入场价附近 StopLoss 点设置止损。 当价格向有利方向移动 2 * StopLoss 点时:

  • 如果信号反转,策略以双倍仓位反向开仓;
  • 否则,将止损跟随价格移动。

详情

  • 入场条件signal > 0 做多,signal < 0 做空。
  • 多空方向:双向。
  • 退出条件:反向信号或止损。
  • 止损:跟踪止损。
  • 默认值:
    • StopLoss = 500
    • Period = 3
    • Random = false
    • Volume = 1
    • CandleType = TimeSpan.FromMinutes(1)
  • 过滤器:
    • 类型: 趋势
    • 方向: 双向
    • 指标: 自定义公式
    • 止损: 跟踪
    • 复杂度: 基础
    • 时间框架: 日内 (1m)
    • 季节性: 无
    • 神经网络: 无
    • 背离: 无
    • 风险等级: 中
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>
/// Strategy based on aftereffects in price series.
/// It evaluates a custom signal from historical opens and the current close.
/// </summary>
public class AfterEffectsStrategy : Strategy
{
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<int> _period;
	private readonly StrategyParam<bool> _random;
	private readonly StrategyParam<DataType> _candleType;

	private readonly Queue<decimal> _pQueue = new();
	private readonly Queue<decimal> _twoPQueue = new();
	private decimal _openP;
	private decimal _open2P;
	private decimal _stopPrice;

	public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
	public int Period { get => _period.Value; set => _period.Value = value; }
	public bool Random { get => _random.Value; set => _random.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public AfterEffectsStrategy()
	{
		_stopLoss = Param(nameof(StopLoss), 500m)
			.SetDisplay("Stop Loss", "Stop Loss distance", "General");
		_period = Param(nameof(Period), 8)
			.SetDisplay("Bar Period", "Period of bars for signal", "General");
		_random = Param(nameof(Random), false)
			.SetDisplay("Random Range", "Invert signal", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle Type", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_pQueue.Clear();
		_twoPQueue.Clear();
		_openP = 0m;
		_open2P = 0m;
		_stopPrice = 0m;
	}

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

		_pQueue.Clear();
		_twoPQueue.Clear();
		_openP = 0m;
		_open2P = 0m;
		_stopPrice = 0m;

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();

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

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

		_pQueue.Enqueue(candle.OpenPrice);

		if (_pQueue.Count > Period)
		{
			_openP = _pQueue.Dequeue();
			_twoPQueue.Enqueue(_openP);

			if (_twoPQueue.Count > Period)
				_open2P = _twoPQueue.Dequeue();
		}

		if (_twoPQueue.Count < Period)
			return;

		var signal = candle.ClosePrice - 2m * _openP + _open2P;

		if (Random)
			signal = -signal;

		if (Position == 0)
		{
			if (signal > 0m)
			{
				BuyMarket();
				_stopPrice = candle.ClosePrice - StopLoss;
			}
			else
			{
				SellMarket();
				_stopPrice = candle.ClosePrice + StopLoss;
			}
			return;
		}

		if (Position > 0)
		{
			if (candle.ClosePrice <= _stopPrice)
			{
				if (signal < 0m)
				{
					// Reverse to short
					SellMarket();
					SellMarket();
					_stopPrice = candle.ClosePrice + StopLoss;
				}
				else
				{
					// Just exit
					SellMarket();
				}
			}
			else
			{
				_stopPrice = Math.Max(_stopPrice, candle.ClosePrice - StopLoss);
			}
		}
		else if (Position < 0)
		{
			if (candle.ClosePrice >= _stopPrice)
			{
				if (signal > 0m)
				{
					// Reverse to long
					BuyMarket();
					BuyMarket();
					_stopPrice = candle.ClosePrice - StopLoss;
				}
				else
				{
					// Just exit
					BuyMarket();
				}
			}
			else
			{
				_stopPrice = Math.Min(_stopPrice, candle.ClosePrice + StopLoss);
			}
		}
	}
}