在 GitHub 上查看
Up3x1 Investor 策略
概览
本策略为 MetaTrader 4 专家顾问 up3x1_Investor 的移植版本。策略在可配置的周期(默认 1 小时)上处理已收盘的 K 线,并使用 StockSharp 的高级 API 重现原始规则,同时提供明确的风险控制参数。
交易逻辑
- 检查最近一根已完成的 K 线:
- K 线振幅(最高价 − 最低价)大于
0.0060;
- K 线实体(|开盘价 − 收盘价|)大于
0.0050。
- 如果满足条件且 K 线收阳,则开多单;
- 如果满足条件且 K 线收阴,则开空单;
- 每逢周一完全停止交易,等同于原始 MQL 代码中的
DayOfWeek()==1 判断。
仓位管理
- 入场时根据参数计算内部目标:
TakeProfitPoints:获利目标距离;
StopLossPoints:止损距离;
TrailingStopPoints:用于移动止损的距离。
- 在每根收盘 K 线上执行以下检查:
- 价格触及目标价则平仓;
- 价格触及止损价则平仓;
- 当价格向有利方向移动超过追踪距离时,将止损向价格方向收紧。
- 另外,当 24 周期与 60 周期简单移动平均值在同一根 K 线上相等(允许一个最小跳动的误差)时,立即平仓,复刻原始 EA 中的关闭条件。
资金管理
BaseVolume:无法从资金计算时的基础手数。
MaximumRisk:复制 AccountFreeMargin()*MaximumRisk/1000 公式,若可获取组合价值,则手数 = 价值 * MaximumRisk / 1000,并保留一位小数。
DecreaseFactor:连续亏损超过一次时,按 亏损次数 / DecreaseFactor 比例降低手数。
MinimumVolume:手数下限,保持与原始脚本一致的 0.1 手。
参数说明
| 参数 |
默认值 |
说明 |
BaseVolume |
0.1 |
基础持仓手数。 |
MaximumRisk |
0.2 |
根据账户资金调整手数的风险系数。 |
DecreaseFactor |
3 |
连续亏损后的手数削减因子。 |
MinimumVolume |
0.1 |
最小手数限制。 |
TakeProfitPoints |
20 |
止盈距离(以最小跳动为单位)。 |
StopLossPoints |
50 |
止损距离(以最小跳动为单位)。 |
TrailingStopPoints |
10 |
追踪止损距离。 |
SkipMondays |
true |
是否跳过周一交易。 |
CandleType |
1 小时 |
订阅的 K 线周期。 |
其他说明
- 策略同一时间只持有一个方向的仓位,对应原始 EA 的
CalculateCurrentOrders 逻辑。
- 连续亏损的统计在策略内部完成,因为 StockSharp 不提供 MetaTrader 的历史订单数据。
- 所有交易均以市价单执行,调用
BuyMarket 或 SellMarket。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class Up3x1InvestorRangeFilterStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _atrPeriod;
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 int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public Up3x1InvestorRangeFilterStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 14).SetDisplay("EMA Period", "EMA lookback", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 14).SetDisplay("ATR Period", "ATR 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, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class up3x1_investor_range_filter_strategy(Strategy):
def __init__(self):
super(up3x1_investor_range_filter_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 14).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 ema_period(self): return self._ema_period.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(up3x1_investor_range_filter_strategy, self).OnReseted()
self._prev_close = 0.0; self._prev_ema = 0.0; self._has_prev = False; self._cooldown = 0
def OnStarted2(self, time):
super(up3x1_investor_range_filter_strategy, self).OnStarted2(time)
self._has_prev = False; self._cooldown = 0
ema = ExponentialMovingAverage(); ema.Length = self.ema_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, self.process_candle).Start()
def process_candle(self, candle, ema):
if candle.State != CandleStates.Finished: return
if not self.IsFormedAndOnlineAndAllowTrading(): 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 CreateClone(self): return up3x1_investor_range_filter_strategy()