在 GitHub 上查看

加密货币斐波那契均线(StockSharp 版本)

概述

本策略将 MetaTrader 智能交易系统 “Cryptocurrency Fibonacci MAs” 移植到 StockSharp 高级 API。策略通过 8/13/21/55 周期的斐波那契指数均线判断趋势,同时在更高周期上检查 14 周期动量偏离程度,并结合月度 MACD 过滤方向。所有运算均基于完成的 K 线,并通过 Bind / BindEx 管道更新指标。

与原版相比,移植过程中做出了以下调整:

  • 移除以金额为单位的止盈、权益回撤止损、逐根 K 线移动止损以及保本移动逻辑,改用基于点数的 StartProtection 止损/止盈。
  • 交易量采用净头寸模式限制为单向仓位总量,反手信号会先平掉反向仓位再建立新仓。
  • 多周期数据通过额外的 K 线订阅获取,而不是在收到信号时即时读取指标。

交易逻辑

多头条件

  1. 主周期 EMA 排列:EMA(8) > EMA(13) > EMA(21) > EMA(55)。
  2. 更高周期动量:14 周期 Momentum 相对 100 的绝对偏离在最近三根高周期 K 线中至少有一根超过买入阈值。
  3. 月度 MACD:MACD 主线高于信号线。
  4. 仓位限制:当前净头寸需为空仓或空头,且绝对量低于 MaxPositions × TradeVolume

空头条件

  1. 主周期 EMA 排列:EMA(8) < EMA(13) < EMA(21) < EMA(55)。
  2. 更高周期动量偏离超过卖出阈值。
  3. MACD 主线低于信号线。
  4. 当前净头寸需为空仓或多头,且在最大仓位限制内。

离场逻辑

  • StartProtection 按点数距离挂出止损和止盈,没有实现额外的追踪或保本功能。
  • 反向信号会以相反方向的市价单实现平仓或反手。

多周期映射

动量使用的上级周期遵循原始 EA 的映射关系:

主周期 动量周期
1 分钟 15 分钟
5 分钟 30 分钟
15 分钟 1 小时
30 分钟 4 小时
1 小时 1 天
4 小时 1 周
1 天 1 个月
1 周 1 个月
1 个月 1 个月

MACD 始终基于 30 天的月度近似周期计算。

参数

名称 说明 默认值
TradeVolume 下单手数。 0.1
StopLossPips 止损点数。 20
TakeProfitPips 止盈点数。 50
MomentumBuyThreshold 做多所需的最小动量偏离。 0.3
MomentumSellThreshold 做空所需的最小动量偏离。 0.3
MaxPositions 同方向允许的最大净仓位(按 TradeVolume 倍数)。 1
CandleType 主周期 K 线类型。 1 小时

使用提示

  1. 在图表上加载策略,并通过 CandleType 选择主周期。
  2. 数据源需同时提供主周期、映射后的动量周期以及月度周期数据。
  3. 根据标的的最小报价单位调整止损止盈点数,帮助函数会按照 Security.PriceStep 转换为实际价格步长。
  4. 可利用参数优化功能对动量阈值与止损止盈距离进行回测调优。
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 CryptocurrencyFibonacciMAsStrategy : 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 CryptocurrencyFibonacciMAsStrategy()
	{
		_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;
	}
}