在 GitHub 上查看

简易枢轴反转策略

概述

本策略是对 MQL/7610/Simplepivot_www_forex-instruments_info.mq4 中 MetaTrader 4 EA 的高级 C# 改写。原版程序比较每根新蜡烛的开盘价与上一根蜡烛的高低范围,从而在多空头寸之间切换。移植后的 StockSharp 版本完全依赖高级 API(例如 SubscribeCandlesBindBuyMarketSellMarketClosePosition)来复现同样的流程。

策略步骤如下:

  1. 等待蜡烛收盘以获得开盘价、高点与低点。
  2. 通过上一根蜡烛的高低点计算中点枢轴。
  3. 当当前蜡烛开盘价位于上一根蜡烛下半区或直接跳空站上上一高点时建立多头。
  4. 当开盘价位于上一根蜡烛上半区时建立空头。
  5. 若方向发生变化,则先平掉已有仓位再按新的方向下市价单,模拟原 EA 只维护一张订单的做法。

原始 EA 没有实现止损或止盈,仓位仅会在下一根蜡烛给出相反信号时翻转。

参数

名称 默认值 说明
OrderVolume 1 进场时使用的市价单手数。
CandleType 1 分钟周期 订阅的数据蜡烛类型。

交易逻辑细节

  1. 首先缓存第一根已完成的蜡烛,只有在拥有完整的上一根蜡烛之后才会发出订单。
  2. 对每一根之后完成的蜡烛:
    • 计算 pivot = (previousHigh + previousLow) / 2
    • Open < previousHighOpen > pivot,则准备做空。
    • 否则准备做多(覆盖位于下半区、等于枢轴或跳空突破前高/前低的情况)。
  3. 如果当前已经持有与目标方向一致的仓位,则忽略信号,以避免像原策略一样在同一方向上重复开仓导致额外点差损失。
  4. 当方向需要翻转时,先调用 ClosePosition() 平掉原有仓位,再按照 OrderVolume 下新的市价单。
  5. 用最新蜡烛的高低点更新缓存,以驱动下一次判断。

风险控制

策略未包含止损或止盈,仓位大小仅由 OrderVolume 决定。请通过调整手数或借助账户层面的风控措施自行管理风险。

可视化

若创建了图表区域,策略会绘制所订阅的蜡烛以及成交记录,便于在回测中验证枢轴反转的准确性。

using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simple pivot-based strategy converted from the MetaTrader expert advisor in MQL/7610.
/// The strategy compares the current candle open with the previous candle range to decide
/// whether the next trade should be long or short.
/// </summary>
public class SimplePivotFlipStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _previousHigh;
	private decimal _previousLow;
	private bool _hasPreviousCandle;

	/// <summary>
	/// Order volume used for market entries.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Candle type used for price subscription.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="SimplePivotFlipStrategy"/> class.
	/// </summary>
	public SimplePivotFlipStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Order Volume", "Market order volume used for entries.", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromDays(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles used for pivot calculation.", "Data");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_previousHigh = 0m;
		_previousLow = 0m;
		_hasPreviousCandle = false;
	}

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

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

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

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

		

		if (!_hasPreviousCandle)
		{
			// Store the first completed candle to build the reference range.
			_previousHigh = candle.HighPrice;
			_previousLow = candle.LowPrice;
			_hasPreviousCandle = true;
			return;
		}

		// Calculate the pivot as the midpoint of the previous candle range.
		var pivot = (_previousHigh + _previousLow) / 2m;
		var desiredSide = Sides.Buy;

		// If the new candle opens inside the upper half of the previous range we go short.
		if (candle.OpenPrice < _previousHigh && candle.OpenPrice > pivot)
			desiredSide = Sides.Sell;

		// Skip re-entry if we already hold a position in the desired direction.
		if ((desiredSide == Sides.Buy && Position > 0) || (desiredSide == Sides.Sell && Position < 0))
		{
			_previousHigh = candle.HighPrice;
			_previousLow = candle.LowPrice;
			return;
		}

		if (Position > 0)
			SellMarket();
		else if (Position < 0)
			BuyMarket();

		if (desiredSide == Sides.Buy)
		{
			BuyMarket();
		}
		else
		{
			SellMarket();
		}

		// Update reference range for the next candle.
		_previousHigh = candle.HighPrice;
		_previousLow = candle.LowPrice;
	}
}