在 GitHub 上查看
Pipso 策略
概述
Pipso 是从 MetaTrader 专家顾问 Pipso.mq4 移植而来的夜盘突破策略。该算法跟踪上一批已经完成的 K 线最高价
和最低价,一旦价格突破该区间便立即反向建仓:向上突破时平掉所有多头并建立空头,向下突破时平掉空头并
建立多头。止损距离根据区间宽度动态计算,因此会随市场波动自动调整。
工作流程
- 订阅配置的时间框架(默认 15 分钟)并等待指标完成历史初始化。
- 在每根收盘 K 线上计算之前
BreakoutPeriod 根已完成 K 线的最高价和最低价。当前 K 线不会参与运算,与原
EA 中调用 iHighest(..., shift = 1) 排除工作棒的做法一致。
- 按公式
(high - low) * StopLossMultiplier 重新计算止损距离,同时应用 MinStopDistance 所设定的最小距离。
- 根据
SessionStartHour 与 SessionLengthHours 维护交易时段。如果时段跨越周五午夜,则自动延长 48 小时,
以保证周末期间的持仓行为与 MetaTrader 完全一致。
- 当当前 K 线的最高价突破已记录的区间上沿时:
- 平掉任何持有的多头仓位,并在允许交易的情况下以
OrderVolume 的数量建立新的空头。
- 使用计算出的距离把止损设置在入场价之上。
- 当当前 K 线的最低价跌破区间下沿时:
- 平掉任何持有的空头仓位,并在允许交易的情况下以
OrderVolume 的数量建立新的多头。
- 使用计算出的距离把止损设置在入场价之下。
- 每根收盘 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import Highest, Lowest, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class pipso_night_breakout_strategy(Strategy):
def __init__(self):
super(pipso_night_breakout_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
self._breakout_period = self.Param("BreakoutPeriod", 36).SetDisplay("Breakout Period", "Period for Highest/Lowest channel", "Indicators")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(pipso_night_breakout_strategy, self).OnReseted()
self._entry_price = 0
self._prev_highest = 0
self._prev_lowest = 0
self._has_prev = False
def OnStarted2(self, time):
super(pipso_night_breakout_strategy, self).OnStarted2(time)
self._entry_price = 0
self._prev_highest = 0
self._prev_lowest = 0
self._has_prev = False
highest = Highest()
highest.Length = self._breakout_period.Value
lowest = Lowest()
lowest.Length = self._breakout_period.Value
ema = ExponentialMovingAverage()
ema.Length = self._breakout_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(highest, lowest, ema, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def OnProcess(self, candle, highest_val, lowest_val, ema_val):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
mid = (highest_val + lowest_val) / 2.0
if self.Position > 0:
if close < mid or (self._entry_price > 0 and close < self._entry_price * 0.98):
self.SellMarket()
elif self.Position < 0:
if close > mid or (self._entry_price > 0 and close > self._entry_price * 1.02):
self.BuyMarket()
if self.Position == 0 and self._has_prev:
if close > self._prev_highest and close > ema_val:
self._entry_price = close
self.BuyMarket()
elif close < self._prev_lowest and close < ema_val:
self._entry_price = close
self.SellMarket()
self._prev_highest = highest_val
self._prev_lowest = lowest_val
self._has_prev = True
def CreateClone(self):
return pipso_night_breakout_strategy()