在 GitHub 上查看

VininI Trend LRMA 策略

跟踪已完成 K 线收盘价滚动线性回归的端点 L。默认 CandleType = 4小时、Period = 13、UpLevel = 10、DnLevel = -10、Mode = Breakdown。等待指标形成并允许交易。

Breakdown 的阈值用于相对振荡值 O = 100 × (Close − L) / L,单位为百分比。previousO <= UpLevel && O > UpLevel 时买入;previousO >= DnLevel && O < DnLevel 时卖出。阈值不是 LRMA 的绝对价格水平。

Twist 在回归端点斜率由负转正时买入,由正转负时卖出。平坦段保留此前的非零方向。

买入要求 Position <= 0,卖出要求 Position >= 0。先用市价单平掉相反的已成交持仓,再以 Volume 开新方向。同向信号不加仓。无止损或止盈。

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>
/// LRMA trend reversal with selectable percentage-deviation threshold crossing
/// or LRMA-slope reversal. Zero protection is intentional and documented.
/// </summary>
public class VininITrendLrmaStrategy : Strategy
{
	public enum SignalModes
	{
		Breakdown,
		Twist,
	}

	private readonly StrategyParam<int> _period;
	private readonly StrategyParam<decimal> _upLevel;
	private readonly StrategyParam<decimal> _dnLevel;
	private readonly StrategyParam<SignalModes> _mode;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _previousOscillator;
	private decimal? _previousLrma;
	private decimal? _previousSlope;

	public int Period { get => _period.Value; set => _period.Value = value; }
	public decimal UpLevel { get => _upLevel.Value; set => _upLevel.Value = value; }
	public decimal DnLevel { get => _dnLevel.Value; set => _dnLevel.Value = value; }
	public SignalModes Mode { get => _mode.Value; set => _mode.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public VininITrendLrmaStrategy()
	{
		_period = Param(nameof(Period), 13).SetGreaterThanZero()
			.SetDisplay("LRMA Period", "Linear regression lookback", "Indicators");
		_upLevel = Param(nameof(UpLevel), 10m)
			.SetDisplay("Upper Level", "Upper percent deviation trigger for Breakdown", "Signals");
		_dnLevel = Param(nameof(DnLevel), -10m)
			.SetDisplay("Lower Level", "Lower percent deviation trigger for Breakdown", "Signals");
		_mode = Param(nameof(Mode), SignalModes.Breakdown)
			.SetDisplay("Signal Mode", "Breakdown uses percent deviation; Twist uses LRMA slope reversal", "Signals");
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_previousOscillator = null;
		_previousLrma = null;
		_previousSlope = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_previousOscillator = null;
		_previousLrma = null;
		_previousSlope = null;
		var lrma = new LinearReg { Length = Period };
		SubscribeCandles(CandleType).Bind(lrma, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal lrma)
	{
		if (candle.State != CandleStates.Finished || !IsFormed || lrma == 0m)
			return;

		var oscillator = (candle.ClosePrice - lrma) / lrma * 100m;
		var slope = _previousLrma.HasValue ? lrma - _previousLrma.Value : (decimal?)null;
		var buy = false;
		var sell = false;

		switch (Mode)
		{
			case SignalModes.Breakdown:
				if (_previousOscillator is decimal prev)
				{
					buy = prev <= UpLevel && oscillator > UpLevel;
					sell = prev >= DnLevel && oscillator < DnLevel;
				}
				break;
			case SignalModes.Twist:
				if (_previousSlope is decimal old && slope is decimal current)
				{
					buy = old < 0m && current > 0m;
					sell = old > 0m && current < 0m;
				}
				break;
			default:
				throw new ArgumentOutOfRangeException(nameof(Mode), Mode, "Unsupported LRMA signal mode.");
		}

		_previousOscillator = oscillator;
		_previousLrma = lrma;
		if (slope is < 0m or > 0m)
			_previousSlope = slope;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var position = Position;
		if (buy && position <= 0m)
		{
			if (position < 0m)
				BuyMarket(-position);
			BuyMarket(Volume);
		}
		else if (sell && position >= 0m)
		{
			if (position > 0m)
				SellMarket(position);
			SellMarket(Volume);
		}
	}
}