在 GitHub 上查看
My System 策略
概览
My System 策略 是 MetaTrader 4 智能交易系统 MySystem.mq4(位于 MQL/9601)的 StockSharp 版本。原始脚本通过 Bulls Power 与 Bears Power 指标计算合成动能信号,当动能翻转时建立反转方向仓位。本 C# 版本完整还原这一决策流程,引入显式的风险管理状态,并将所有可调常量暴露为策略参数,便于在 StockSharp 中优化。
与 MQL 程序直接在每根柱子上以不同价格模式调用 iBullsPower/iBearsPower 不同,StockSharp 版本从配置好的蜡烛序列中获取指标值,并在内部保存前一根的合成动能值。转换保持默认的 15 分钟周期、相同的止盈/止损距离以及源代码中的移动止损逻辑。
交易逻辑
- 订阅所配置的蜡烛序列(默认 15 分钟),仅在蜡烛收盘后处理。
- 对每根完成的蜡烛获取最新的 Bulls Power 与 Bears Power,计算它们的平均值
((bulls + bears) / 2)。
- 使用
_previousAveragePower 保存上一个平均值,对应 MQL 中带偏移的指标调用。
- 开仓规则(仅在无持仓时生效):
- 做空:若上一平均值大于当前平均值且当前平均值仍为正,对应 MQL 条件
pos1pre > pos2cur && pos2cur > 0。
- 做多:若当前平均值跌至负值 (
pos2cur < 0),说明 Bears Power 占优。
- 每根蜡烛都会先执行离场管理,再评估新的入场:
- 检查在开仓时记录的固定止盈、止损价格。
- 应用原 EA 的移动止损:多头在动能减弱(
pos1pre > pos2cur)且价格已上涨到指定距离时离场;空头在合成动能转为负值且价格已下跌指定距离时离场。
- 一旦触发离场,调用
ClosePosition() 平仓,随后等待下一根蜡烛再次评估信号。
参数
| 名称 |
说明 |
默认值 |
备注 |
TakeProfitPoints |
止盈距离(价格步长)。 |
86 |
对应 MQL 输入 TakeProfit。设为 0 可关闭止盈。 |
StopLossPoints |
止损距离(价格步长)。 |
60 |
对应 MQL 输入 StopLoss。设为 0 可关闭止损。 |
TrailingStopPoints |
移动止损距离(价格步长)。 |
10 |
设为 0 时禁用移动止损逻辑。 |
OrderVolume |
每次入场的下单量。 |
8.3 |
对应 EA 中的 Lots。 |
PowerPeriod |
Bulls/Bears Power 指标周期。 |
13 |
复刻原始参数。 |
CandleType |
驱动指标计算的蜡烛类型。 |
15m |
修改即可在其他周期运行策略。 |
全部参数均通过 Param() 定义,可用于界面绑定与批量优化。
风险管理
- 在
OnPositionChanged 检测到新仓位时保存保护价位。距离通过 PriceStep(对 3/5 位外汇品种做了 10 倍修正)折算为绝对价格,模拟 MetaTrader 的 Point 行为。
- 当止盈、止损或移动止损满足条件时调用
ClosePosition(),以一次市价指令完成离场,避免重复提交。
- 策略始终保持单一仓位,仿照 MQL 中
OrdersTotal() < 1 的限制,不执行对冲或分批平仓。
转换说明
- MetaTrader 中
PRICE_WEIGHTED 与 PRICE_CLOSE 的差异通过保存上一根合成动能值来近似,无需额外创建带不同价格源的指标实例,保留了原始意图。
- 原 EA 的移动止损部分含有错误的
OrderSelect 调用。移植版本根据逻辑目标实现:当价格行进到指定距离且动能条件成立时确定性地平仓。
- 为模拟盘中触价,移动止损使用蜡烛高/低价判断,因为 StockSharp 默认处理收盘数据。
- 下单量、止盈止损距离与指标周期保持原始默认值,可直接复现既有优化结果。
使用建议
- 绑定到具备
PriceStep 与 Decimals 信息的交易品种;若缺失,辅助函数会退化为 1 点大小。
- 根据合约规模与最小跳动值调整
OrderVolume、TakeProfitPoints、StopLossPoints。
- 更换周期时需要同步修改
CandleType,并建议重新优化移动止损距离,较短周期更容易触发。
- 结合
DrawCandles、DrawIndicator、DrawOwnTrades 检查 Bulls/Bears Power 触发阈值时的交易点位。
文件
CS/MySystemStrategy.cs – 使用 StockSharp 高级 API 编写的策略实现。
README.md, README_zh.md, README_ru.md – 该策略的多语言文档。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// My System: RSI momentum with EMA filter and ATR stops.
/// </summary>
public class MySystemStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevRsi;
private decimal _entryPrice;
public MySystemStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_rsiLength = Param(nameof(RsiLength), 13)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
_emaLength = Param(nameof(EmaLength), 20)
.SetDisplay("EMA Length", "Trend filter.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRsi = 0; _entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevRsi = 0; _entryPrice = 0;
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var ema = new ExponentialMovingAverage { Length = EmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(rsi, ema, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, ema); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal rsiVal, decimal emaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
if (_prevRsi == 0 || atrVal <= 0) { _prevRsi = rsiVal; return; }
var close = candle.ClosePrice;
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 2.5m || close <= _entryPrice - atrVal * 1.5m || rsiVal > 75) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 2.5m || close >= _entryPrice + atrVal * 1.5m || rsiVal < 25) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (rsiVal > 55 && _prevRsi <= 55 && close > emaVal) { _entryPrice = close; BuyMarket(); }
else if (rsiVal < 45 && _prevRsi >= 45 && close < emaVal) { _entryPrice = close; SellMarket(); }
}
_prevRsi = rsiVal;
}
}
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 RelativeStrengthIndex, ExponentialMovingAverage, AverageTrueRange
class my_system_strategy(Strategy):
def __init__(self):
super(my_system_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._rsi_length = self.Param("RsiLength", 13) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "Trend filter.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_rsi = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(my_system_strategy, self).OnStarted2(time)
self._prev_rsi = 0.0
self._entry_price = 0.0
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._rsi, self._ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, rsi_val, ema_val, atr_val):
if candle.State != CandleStates.Finished:
return
rv = float(rsi_val)
ev = float(ema_val)
av = float(atr_val)
if self._prev_rsi == 0 or av <= 0:
self._prev_rsi = rv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if close >= self._entry_price + av * 2.5 or close <= self._entry_price - av * 1.5 or rv > 75:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - av * 2.5 or close >= self._entry_price + av * 1.5 or rv < 25:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if rv > 55 and self._prev_rsi <= 55 and close > ev:
self._entry_price = close
self.BuyMarket()
elif rv < 45 and self._prev_rsi >= 45 and close < ev:
self._entry_price = close
self.SellMarket()
self._prev_rsi = rv
def OnReseted(self):
super(my_system_strategy, self).OnReseted()
self._prev_rsi = 0.0
self._entry_price = 0.0
def CreateClone(self):
return my_system_strategy()