Zakryvator 策略
Zakryvator 策略用于风险管理:它监控当前持仓,一旦浮亏超过预设阈值就立即平仓。允许的亏损根据持仓量而变化,复现了原始 MQL 脚本中“不同手数对应不同最大回撤”的思想。
该策略本身不产生入场信号。仓位应由人工或其他策略开立,Zakryvator 仅负责在亏损达到限制时自动退出,以保护账户。
细节
- 入场条件:无;仅管理已存在的仓位。
- 出场条件:当亏损达到当前持仓量对应的阈值时平仓。
- 多空方向:支持多头与空头。
- 止损:根据仓位大小设置的固定货币损失上限。
- 过滤器:无。
参数
| 参数 | 说明 |
|---|---|
Min001002 |
适用于持仓量 ≤ 0.02 手的最大亏损。 |
Min002005 |
适用于持仓量 0.02–0.05 手的最大亏损。 |
Min00501 |
适用于持仓量 0.05–0.10 手的最大亏损。 |
Min0103 |
适用于持仓量 0.10–0.30 手的最大亏损。 |
Min0305 |
适用于持仓量 0.30–0.50 手的最大亏损。 |
Min051 |
适用于持仓量 0.50–1 手的最大亏损。 |
MinFrom1 |
适用于持仓量大于 1 手的最大亏损。 |
行为
- 策略订阅逐笔成交以获取实时价格。
- 每个成交都会根据当前价格与平均入场价计算浮动盈亏。
- 若亏损超过对应阈值,则以市价立即平仓。
因此,Zakryvator 提供了一种基于仓位大小限制回撤的简单有效方案。
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that opens positions using SMA crossover and closes them
/// when unrealized loss exceeds a volume-based threshold ("Zakryvator" = position closer on loss).
/// </summary>
public class ZakryvatorStrategy : Strategy
{
private decimal _entryPrice;
private decimal _lastPrice;
private bool _prevShortAboveLong;
private readonly SimpleMovingAverage _smaShort = new() { Length = 50 };
private readonly SimpleMovingAverage _smaLong = new() { Length = 150 };
private readonly StrategyParam<int> _shortPeriod;
private readonly StrategyParam<int> _longPeriod;
private readonly StrategyParam<decimal> _lossThreshold;
/// <summary>Short SMA period.</summary>
public int ShortPeriod { get => _shortPeriod.Value; set => _shortPeriod.Value = value; }
/// <summary>Long SMA period.</summary>
public int LongPeriod { get => _longPeriod.Value; set => _longPeriod.Value = value; }
/// <summary>Maximum unrealized loss before closing position.</summary>
public decimal LossThreshold { get => _lossThreshold.Value; set => _lossThreshold.Value = value; }
/// <summary>Constructor.</summary>
public ZakryvatorStrategy()
{
_shortPeriod = Param(nameof(ShortPeriod), 50)
.SetDisplay("Short SMA", "Short SMA period for entry signal", "Entry");
_longPeriod = Param(nameof(LongPeriod), 150)
.SetDisplay("Long SMA", "Long SMA period for entry signal", "Entry");
_lossThreshold = Param(nameof(LossThreshold), 500m)
.SetDisplay("Loss Threshold", "Max unrealized loss before closing position", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, TimeSpan.FromMinutes(5).TimeFrame())];
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_smaShort.Length = ShortPeriod;
_smaLong.Length = LongPeriod;
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription
.Bind(_smaShort, _smaLong, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal shortSma, decimal longSma)
{
if (candle.State != CandleStates.Finished)
return;
if (!_smaShort.IsFormed || !_smaLong.IsFormed)
return;
_lastPrice = candle.ClosePrice;
var shortAboveLong = shortSma > longSma;
// Check loss threshold for open position
if (Position != 0 && _entryPrice != 0m)
{
var openPnL = Position * (_lastPrice - _entryPrice);
if (openPnL <= -LossThreshold)
{
// Close on loss
if (Position > 0)
SellMarket();
else
BuyMarket();
_entryPrice = 0m;
_prevShortAboveLong = shortAboveLong;
return;
}
}
// SMA crossover entry/exit logic
var crossUp = shortAboveLong && !_prevShortAboveLong;
var crossDown = !shortAboveLong && _prevShortAboveLong;
if (crossUp)
{
if (Position < 0)
{
BuyMarket();
_entryPrice = 0m;
}
if (Position == 0)
{
BuyMarket();
_entryPrice = _lastPrice;
}
}
else if (crossDown)
{
if (Position > 0)
{
SellMarket();
_entryPrice = 0m;
}
if (Position == 0)
{
SellMarket();
_entryPrice = _lastPrice;
}
}
_prevShortAboveLong = shortAboveLong;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0m;
_lastPrice = 0m;
_prevShortAboveLong = false;
_smaShort.Reset();
_smaLong.Reset();
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class zakryvator_strategy(Strategy):
def __init__(self):
super(zakryvator_strategy, self).__init__()
self._entry_price = 0.0
self._last_price = 0.0
self._prev_short_above_long = False
self._sma_short = SimpleMovingAverage()
self._sma_long = SimpleMovingAverage()
self._short_period = self.Param("ShortPeriod", 50) \
.SetDisplay("Short SMA", "Short SMA period for entry signal", "Entry")
self._long_period = self.Param("LongPeriod", 150) \
.SetDisplay("Long SMA", "Long SMA period for entry signal", "Entry")
self._loss_threshold = self.Param("LossThreshold", 500.0) \
.SetDisplay("Loss Threshold", "Max unrealized loss before closing position", "Risk")
@property
def ShortPeriod(self):
return self._short_period.Value
@property
def LongPeriod(self):
return self._long_period.Value
@property
def LossThreshold(self):
return self._loss_threshold.Value
def GetWorkingSecurities(self):
return [(self.Security, DataType.TimeFrame(TimeSpan.FromMinutes(5)))]
def OnStarted2(self, time):
super(zakryvator_strategy, self).OnStarted2(time)
self._sma_short.Length = self.ShortPeriod
self._sma_long.Length = self.LongPeriod
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._sma_short, self._sma_long, self.process_candle).Start()
def process_candle(self, candle, short_sma, long_sma):
if candle.State != CandleStates.Finished:
return
if not self._sma_short.IsFormed or not self._sma_long.IsFormed:
return
self._last_price = float(candle.ClosePrice)
short_above_long = float(short_sma) > float(long_sma)
# Check loss threshold for open position
if self.Position != 0 and self._entry_price != 0.0:
open_pnl = float(self.Position) * (self._last_price - self._entry_price)
if open_pnl <= -float(self.LossThreshold):
# Close on loss
if self.Position > 0:
self.SellMarket()
else:
self.BuyMarket()
self._entry_price = 0.0
self._prev_short_above_long = short_above_long
return
# SMA crossover entry/exit logic
cross_up = short_above_long and not self._prev_short_above_long
cross_down = not short_above_long and self._prev_short_above_long
if cross_up:
if self.Position < 0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
self.BuyMarket()
self._entry_price = self._last_price
elif cross_down:
if self.Position > 0:
self.SellMarket()
self._entry_price = 0.0
if self.Position == 0:
self.SellMarket()
self._entry_price = self._last_price
self._prev_short_above_long = short_above_long
def OnReseted(self):
super(zakryvator_strategy, self).OnReseted()
self._entry_price = 0.0
self._last_price = 0.0
self._prev_short_above_long = False
self._sma_short.Reset()
self._sma_long.Reset()
def CreateClone(self):
return zakryvator_strategy()