在 GitHub 上查看

Marneni Money Tree 策略

该策略将 MQL 专家顾问 "Marneni Money Tree" 移植到 StockSharp。 它使用 40 周期简单移动平均线(SMA)及其两个偏移值来判断趋势方向。 当向前偏移四根柱的 SMA 位于当前值与向前偏移三十根柱的值之间时:

  • 发送一笔按趋势方向的市价单;
  • 同时按照 Order2PipsOrder9Pips 指定的点差依次挂出八笔限价单。

做多时在当前价格下方挂买单,做空时在当前价格上方挂卖单。 当上述 SMA 关系反转时,平仓并取消所有剩余挂单。

参数

  • Order2PipsOrder9Pips:第 2 到第 9 笔限价单距市价的点差。
  • CandleType:用于计算的 K 线周期。

基础下单量固定为 2,可在运行前修改 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>
/// SMA momentum strategy with shifted MA comparison.
/// Buys when current SMA is above shifted SMA, sells when below.
/// </summary>
public class MarneniMoneyTreeStrategy : Strategy
{
	private readonly StrategyParam<int> _smaPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private readonly decimal[] _smaBuffer = new decimal[31];
	private int _bufferIndex;
	private int _valuesCount;

	public int SmaPeriod { get => _smaPeriod.Value; set => _smaPeriod.Value = value; }
	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public MarneniMoneyTreeStrategy()
	{
		_smaPeriod = Param(nameof(SmaPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("SMA Period", "SMA length", "Indicators");
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR for stops", "Indicators");
		_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();
		Array.Clear(_smaBuffer, 0, _smaBuffer.Length);
		_bufferIndex = 0;
		_valuesCount = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var sma = new SimpleMovingAverage { Length = SmaPeriod };
		var atr = new StandardDeviation { Length = AtrPeriod };

		SubscribeCandles(CandleType).Bind(sma, atr, ProcessCandle).Start();
	}

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

		_smaBuffer[_bufferIndex] = smaValue;
		_bufferIndex = (_bufferIndex + 1) % _smaBuffer.Length;
		if (_valuesCount < _smaBuffer.Length) _valuesCount++;

		if (_valuesCount < _smaBuffer.Length) return;
		if (atrValue <= 0) return;

		var idxCurrent = (_bufferIndex - 1 + _smaBuffer.Length) % _smaBuffer.Length;
		var idxShift4 = (_bufferIndex - 5 + _smaBuffer.Length) % _smaBuffer.Length;
		var idxShift30 = _bufferIndex % _smaBuffer.Length;

		var ma = _smaBuffer[idxShift4];
		var ma1 = _smaBuffer[idxCurrent];
		var ma2 = _smaBuffer[idxShift30];

		if (Position == 0)
		{
			if (ma > ma1 && ma < ma2)
				SellMarket();
			else if (ma < ma1 && ma > ma2)
				BuyMarket();
		}
		else if (Position > 0 && ma > ma1)
			SellMarket();
		else if (Position < 0 && ma < ma1)
			BuyMarket();
	}
}