在 GitHub 上查看

Pipso 策略

概述

Pipso 是从 MetaTrader 专家顾问 Pipso.mq4 移植而来的夜盘突破策略。该算法跟踪上一批已经完成的 K 线最高价 和最低价,一旦价格突破该区间便立即反向建仓:向上突破时平掉所有多头并建立空头,向下突破时平掉空头并 建立多头。止损距离根据区间宽度动态计算,因此会随市场波动自动调整。

工作流程

  1. 订阅配置的时间框架(默认 15 分钟)并等待指标完成历史初始化。
  2. 在每根收盘 K 线上计算之前 BreakoutPeriod 根已完成 K 线的最高价和最低价。当前 K 线不会参与运算,与原 EA 中调用 iHighest(..., shift = 1) 排除工作棒的做法一致。
  3. 按公式 (high - low) * StopLossMultiplier 重新计算止损距离,同时应用 MinStopDistance 所设定的最小距离。
  4. 根据 SessionStartHourSessionLengthHours 维护交易时段。如果时段跨越周五午夜,则自动延长 48 小时, 以保证周末期间的持仓行为与 MetaTrader 完全一致。
  5. 当当前 K 线的最高价突破已记录的区间上沿时:
    • 平掉任何持有的多头仓位,并在允许交易的情况下以 OrderVolume 的数量建立新的空头。
    • 使用计算出的距离把止损设置在入场价之上。
  6. 当当前 K 线的最低价跌破区间下沿时:
    • 平掉任何持有的空头仓位,并在允许交易的情况下以 OrderVolume 的数量建立新的多头。
    • 使用计算出的距离把止损设置在入场价之下。
  7. 每根收盘 K 线都会检查保护性止损。如果多头的最低价触及止损,或者空头的最高价触及止损,仓位立即平仓。

交易时段逻辑

  • SessionStartHour 使用交易所当地时间表示,SessionLengthHours 控制持续时长。
  • 若交易时段跨越 24 小时且当前日期为周五,则会把结束时间向后推迟 48 小时,以便在周一重新允许开仓,这与 MQL4 原版的周末处理方式相同。
  • 当不在交易时段内时策略只会平仓,不会建立新仓,直到新的时段开始。

参数

名称 说明 默认值
CandleType 用于生成信号的 K 线类型。 15 分钟
OrderVolume 每次市价单使用的固定手数。 1
SessionStartHour 突破窗口开始的小时数。 21
SessionLengthHours 交易时段持续的小时数。 9
BreakoutPeriod 构成突破区间的已完成 K 线数量。 36
StopLossMultiplier 区间宽度乘数,用于计算止损距离(取值 3 对应原参数 SLpp = 300)。 3
MinStopDistance 止损与入场价之间的最小绝对距离,用来模拟 MetaTrader 的 StopLevel 限制。 0

说明

  • 策略仅使用市价单,没有固定止盈,退出完全依赖保护性止损或反向突破信号。
  • 在多空转换时,策略会发送一笔市价单同时平掉旧仓并建立新仓,这与原始 EA 先调用 OrderClose 再调用 OrderSend 的逻辑等价。
  • 策略会在图表上绘制突破高低轨迹线以及成交记录,便于观察运行情况。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Range breakout strategy that uses Highest/Lowest channel.
/// Enters on breakouts above/below the channel and exits on reversion.
/// </summary>
public class PipsoNightBreakoutStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _breakoutPeriod;

	private decimal _entryPrice;
	private decimal _prevHighest;
	private decimal _prevLowest;
	private bool _hasPrev;

	public PipsoNightBreakoutStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for analysis.", "General");

		_breakoutPeriod = Param(nameof(BreakoutPeriod), 36)
			.SetDisplay("Breakout Period", "Period for Highest/Lowest channel.", "Indicators");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int BreakoutPeriod
	{
		get => _breakoutPeriod.Value;
		set => _breakoutPeriod.Value = value;
	}

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

		_entryPrice = 0;
		_prevHighest = 0;
		_prevLowest = 0;
		_hasPrev = false;
	}

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

		_entryPrice = 0;
		_prevHighest = 0;
		_prevLowest = 0;
		_hasPrev = false;

		var highest = new Highest { Length = BreakoutPeriod };
		var lowest = new Lowest { Length = BreakoutPeriod };
		var ema = new ExponentialMovingAverage { Length = BreakoutPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(highest, lowest, ema, ProcessCandle)
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal highestValue, decimal lowestValue, decimal emaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var close = candle.ClosePrice;
		var mid = (highestValue + lowestValue) / 2m;

		// Exit conditions
		if (Position > 0)
		{
			// Exit when price reverts to middle or stop-loss
			if (close < mid || (_entryPrice > 0 && close < _entryPrice * 0.98m))
			{
				SellMarket();
			}
		}
		else if (Position < 0)
		{
			if (close > mid || (_entryPrice > 0 && close > _entryPrice * 1.02m))
			{
				BuyMarket();
			}
		}

		// Entry conditions: breakout above previous highest or below previous lowest
		if (Position == 0 && _hasPrev)
		{
			if (close > _prevHighest && close > emaValue)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (close < _prevLowest && close < emaValue)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevHighest = highestValue;
		_prevLowest = lowestValue;
		_hasPrev = true;
	}
}