在 GitHub 上查看
Lock Strategy 策略 (中文)
Lock Strategy 复刻了 MetaTrader 上常见的“锁单”专家顾问:系统始终保持一多一空的对冲仓位,并不断循环,直到满足锁定利润的条件为止。该算法适用于点值较小、可以使用固定点差止盈的交易品种。
运行流程
- 建立初始对冲:一旦有行情数据,策略立即以相同手数买入和卖出。如果两笔订单都成功成交,则下一次对冲所用的手数乘以
LotExponential 系数。
- 止盈管理:每条腿记录自己的进场价。当收盘价相对进场价移动了
TakeProfitPips(换算成最小跳动)时,该腿通过市价单平仓;另一条腿继续持有,保持 MQL 版本的锁仓特性。
- 重新锁仓:如果当前仅剩一条腿或完全没有持仓,策略会立即再开出一组多空。当没有任何腿时,下一次下单前会把手数重置为
LotSize。
- 手数控制:
AdjustVolume 方法会按照交易所规则调整手数——对齐到 VolumeStep,并限制在 MinVolume 与 MaxVolume 之间;如果结果为零,则取消放大。
利润锁定条件
原始 MQL 脚本比较账户余额与权益:当余额比权益高出 ExcessBalanceOverEquity,并且权益相对上一次锁定时至少增加 MinProfit,策略就会平掉全部仓位。C# 实现通过记录空仓时的权益来模拟余额,一旦触发条件,所有腿都会被平仓,基准权益被更新,然后循环重新从 LotSize 开始。
参数说明
LotSize:第一轮锁仓使用的基础手数(默认 0.1m)。
TakeProfitPips:每条腿的止盈距离,单位为点(默认 100,0 表示不自动平仓)。
LotExponential:两条腿都成交后,下一个周期的放大倍数(默认 2m)。
ExcessBalanceOverEquity:允许的余额与权益差值阈值,超过后触发锁盈(默认 3000m)。
MinProfit:触发全平前所需的额外权益收益(默认 500m)。
CandleType:驱动策略逻辑的 K 线周期(默认 1 分钟)。
实现细节
- 点值根据
Security.PriceStep 与 Security.Decimals 自动计算,可兼容 3/5 位的外汇合约以及常见的期货、股票。
- 策略使用市价单进出场,模拟原专家顾问发送市价单并由券商处理止盈的行为。
- 系统保存所有打开的腿,因此可以在每个方向叠加多笔仓位,与源代码的行为保持一致。
namespace StockSharp.Samples.Strategies;
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Lock strategy (simplified).
/// Uses fast and slow EMA crossover with momentum confirmation.
/// </summary>
public class LockStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public LockStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Source candles", "General");
_fastPeriod = Param(nameof(FastPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
decimal prevFast = 0, prevSlow = 0;
bool hasPrev = false;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, (ICandleMessage candle, decimal fastValue, decimal slowValue) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!hasPrev)
{
prevFast = fastValue;
prevSlow = slowValue;
hasPrev = true;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
prevFast = fastValue;
prevSlow = slowValue;
return;
}
// Buy on golden cross
if (prevFast <= prevSlow && fastValue > slowValue && Position <= 0)
{
BuyMarket();
}
// Sell on death cross
else if (prevFast >= prevSlow && fastValue < slowValue && Position >= 0)
{
SellMarket();
}
prevFast = fastValue;
prevSlow = slowValue;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class lock_strategy(Strategy):
def __init__(self):
super(lock_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Source candles", "General")
self._fast_period = self.Param("FastPeriod", 10) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 30) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
def OnReseted(self):
super(lock_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(lock_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.FastPeriod
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.Bind(fast_ema, slow_ema, self._on_process) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if not self._has_prev:
self._prev_fast = fv
self._prev_slow = sv
self._has_prev = True
return
if self._prev_fast <= self._prev_slow and fv > sv and self.Position <= 0:
self.BuyMarket()
elif self._prev_fast >= self._prev_slow and fv < sv and self.Position >= 0:
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def CreateClone(self):
return lock_strategy()