在 GitHub 上查看

趋势是你的朋友策略

概述

“趋势是你的朋友”策略是受原始 MetaTrader 专家顾问启发的多周期趋势跟随系统。它将入场周期的动量与更高周期的 MACD 滤波相结合,并通过布林带离场、传统止损/止盈、可选保本锁定以及移动止损来控制风险。

策略在可配置的基准周期上运行(默认 1 小时),通过分析最近两根已完成 K 线来寻找短期动量形态:做多时要求前一根阴线之后出现更强的阳线,做空时则相反。在开仓之前,形态必须与均线趋势滤波以及月度 MACD 信号保持一致。

入场逻辑

  1. 在入场周期上计算快速 EMA 与慢速加权移动平均线(LWMA)。
  2. 跟踪最近两根完成的 K 线以构成动量形态:
    • 做多条件: 前两根 K 线中的较早一根为阴线,上一根为更大的阳线。
    • 做空条件: 前两根 K 线中的较早一根为阳线,上一根为更小的阴线。
  3. 使用均线趋势滤波确认形态(做多时快速均线在慢速均线上方,做空时在下方)。
  4. 使用更高周期(默认月线)计算的 MACD 信号确认长期趋势。做多需要 MACD 线在信号线之上,做空则相反。
  5. 当所有条件满足时,以配置的手数市价开仓。

离场逻辑

  • 布林带离场: 做多仓位在收盘价突破上轨时平仓;做空仓位在收盘价跌破下轨时平仓。
  • 止盈/止损: 可选的固定点数距离,程序通过品种的价格最小变动单位将点数转换为价格距离。
  • 保本: 可选功能,在达到设定盈利阈值后将保护性止损移动到入场价附近。
  • 移动止损: 可选功能,在达到设定盈利阈值后启动,并按固定点数距离跟随价格。移动止损与保本共用同一价格水平。

参数

名称 描述 默认值
Entry Candle 入场逻辑使用的周期 1 小时
MACD Candle MACD 滤波使用的高周期 30 天
Fast MA 快速 EMA 周期 8
Slow MA 慢速 LWMA 周期 20
Bollinger Length 布林带周期 20
Bollinger Width 布林带标准差倍数 2.0
Stop Loss (pips) 止损距离(点) 20
Take Profit (pips) 止盈距离(点) 50
Use Break-Even 启用保本功能 true
Break-Even Trigger 触发保本的盈利点数 10
Break-Even Offset 保本时使用的偏移点数 5
Use Trailing 启用移动止损 true
Trailing Activation 启动移动止损的盈利点数 40
Trailing Distance 移动止损保持的点数距离 40

说明

  • 策略仅存储最近两根完成的 K 线,以避免占用大量历史数据。
  • MACD 数据通过允许聚合的方式订阅指定的高周期,即使只有日线数据也能构造出月线信号。
  • 点数到价格的换算基于品种的价格步长,若品种使用不同的点值定义,需要相应调整参数。
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 TrendIsYourFriendStrategy : 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 TrendIsYourFriendStrategy()
	{
		_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;
	}
}