在 GitHub 上查看

I Trend 策略

概述

I Trend 策略来源于 MQL5 专家 Exp_i_Trend,属于趋势跟随型算法。该策略结合移动平均线和布林带来识别动量变化,当自定义的 iTrend 数值与信号线发生交叉时执行交易。

工作原理

  1. 指标初始化
    • 计算可调周期的指数移动平均线 (EMA)。
    • 构建具有指定周期和标准差的布林带。
    • iTrend 值定义为选定价格与所选布林带线(上轨、下轨或中轨)之间的差值。
    • 信号线公式:2 * MA - (High + Low)。
  2. 信号生成
    • 当 iTrend 向上突破 信号线时,策略平掉空头仓位并开多头。
    • 当 iTrend 向下跌破 信号线时,策略平掉多头仓位并开空头。
  3. 订单执行
    • 所有进出场均按市价成交。
    • 仓位大小由参数 Volume 控制。

参数

名称 说明
MaPeriod 移动平均线周期。
BbPeriod 布林带周期。
BbDeviation 布林带标准差。
PriceType 计算 iTrend 使用的价格类型(收盘价、开盘价、最高价、最低价、中价、典型价等)。
BbMode 使用的布林带线(上轨、下轨或中轨)。
CandleType 策略使用的K线时间框架。
Volume 下单手数。

注意事项

  • 策略仅处理已完成的K线,未完成的K线会被忽略。
  • 本示例仅供学习使用,实际交易前需根据市场情况调整。
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>
/// i_Trend strategy built on Bollinger Bands and Moving Average.
/// Generates buy/sell signals when the iTrend value crosses the signal line.
/// </summary>
public class ITrendStrategy : Strategy
{
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<int> _bbPeriod;
	private readonly StrategyParam<decimal> _bbDeviation;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevInd;
	private decimal _prevSign;
	private bool _isInitialized;
	private readonly StrategyParam<int> _priceType, _bbMode;
	private Order _pending;

	public int MaPeriod { get => _maPeriod.Value; set => _maPeriod.Value = value; }
	public int BbPeriod { get => _bbPeriod.Value; set => _bbPeriod.Value = value; }
	public decimal BbDeviation { get => _bbDeviation.Value; set => _bbDeviation.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public int PriceType { get => _priceType.Value; set => _priceType.Value = value; }
	public int BbMode { get => _bbMode.Value; set => _bbMode.Value = value; }
	public ITrendStrategy()
	{
		_priceType = Param(nameof(PriceType), 0).SetRange(0, 5).SetDisplay("Price Type", "0 Close, 1 Open, 2 High, 3 Low, 4 Median, 5 Typical", "Indicator");
		_bbMode = Param(nameof(BbMode), 0).SetRange(0, 2).SetDisplay("BB Mode", "0 Upper, 1 Lower, 2 Middle", "Indicator");
		_maPeriod = Param(nameof(MaPeriod), 13)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Moving average length", "Indicator");

		_bbPeriod = Param(nameof(BbPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("BB Period", "Bollinger Bands period", "Indicator");

		_bbDeviation = Param(nameof(BbDeviation), 2.0m)
			.SetDisplay("BB Deviation", "Standard deviation for Bollinger Bands", "Indicator");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles used", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevInd = 0m;
		_prevSign = 0m;
		_isInitialized = false;
		_pending = null;
	}

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

		var ma = new ExponentialMovingAverage { Length = MaPeriod };
		var bb = new BollingerBands { Length = BbPeriod, Width = BbDeviation };

		Indicators.Add(ma);

		var subscription = SubscribeCandles(CandleType);

		subscription
			.BindEx(bb, (candle, bbValue) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!bbValue.IsFormed)
					return;

				var maResult = ma.Process(candle.ClosePrice, candle.OpenTime, true);
				if (!maResult.IsFormed)
					return;

				var maVal = maResult.ToDecimal();
				var bbVal = (BollingerBandsValue)bbValue;
				if (bbVal.UpBand is not decimal upperBand || bbVal.LowBand is not decimal lowerBand)
					return;
				var band = BbMode switch { 1 => lowerBand, 2 => (upperBand + lowerBand) / 2m, _ => upperBand };
				ProcessCandle(candle, maVal, band);
			})
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal maValue, decimal band)
	{
		var price = PriceType switch { 1 => candle.OpenPrice, 2 => candle.HighPrice, 3 => candle.LowPrice,
			4 => (candle.HighPrice + candle.LowPrice) / 2m,
			5 => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
			_ => candle.ClosePrice };

		var ind = price - band;
		var sign = 2m * maValue - (candle.LowPrice + candle.HighPrice);

		if (!_isInitialized)
		{
			_prevInd = ind;
			_prevSign = sign;
			_isInitialized = true;
			return;
		}

		var crossUp = _prevInd <= _prevSign && ind > sign;
		var crossDown = _prevInd >= _prevSign && ind < sign;

		if (IsFormedAndOnlineAndAllowTrading() && (_pending is null || _pending.State is OrderStates.Done or OrderStates.Failed))
		{
			if (crossUp && Position <= 0m) _pending = BuyMarket(Volume - Math.Min(0m, Position));
			else if (crossDown && Position >= 0m) _pending = SellMarket(Volume + Math.Max(0m, Position));
		}

		_prevInd = ind;
		_prevSign = sign;
	}
}