VR Setka 3 策略
VR Setka 3 策略 采用网格交易方法,在当前价格上下对称地放置买入和卖出限价单。订单成交后,根据当前方向所有持仓的平均价格重新计算止盈水平。新的网格订单会以更大的间距下单,并可选择性地增加手数(马丁格尔)。
参数
- Start Offset – 首次放置买卖限价单距离当前价格的偏移量。
- Take Profit – 从平均建仓价计算的获利平仓距离。
- Grid Distance – 网格层之间的基础间距。
- Step Distance – 每增加一层所附加的额外间距。
- Use Martingale – 启用后,每层新的网格订单按乘数放大手数。
- Martingale Multiplier – 马丁格尔模式下的手数乘数。
- Volume – 第一层订单的基础手数。
- Candle Type – 用于同步策略操作的K线周期。
算法
- 启动时,在当前价格下方放置买入限价单,在上方放置卖出限价单。
- 一旦某一方向成交,另一方向的订单会被取消。
- 根据平均建仓价 ± Take Profit 计算新的统一止盈价位。
- 若价格向持仓不利方向移动,则在距离平均价 Grid Distance + Step Distance × 层数 的位置放置新的限价单。若启用马丁格尔,手数会相应增加。
- 当价格触及止盈价位时,平掉该方向所有持仓并重置网格。
注意事项
- 策略不会同时在两个方向持仓。
- 马丁格尔会迅速扩大仓位,需谨慎控制风险。
- 只要所选 K 线类型可用,策略可应用于 StockSharp 支持的任意品种。
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>
/// Grid strategy inspired by VR SETKA 3.
/// Places limit orders at grid levels. When filled, places next level.
/// Closes position on take profit.
/// </summary>
public class VRSetka3Strategy : Strategy
{
private readonly StrategyParam<decimal> _startOffset;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _gridDistance;
private readonly StrategyParam<decimal> _stepDistance;
private readonly StrategyParam<DataType> _candleType;
private decimal _buyAvgPrice;
private decimal _buyVolume;
private decimal _sellAvgPrice;
private decimal _sellVolume;
private int _buyCount;
private int _sellCount;
private bool _hasBuyPending;
private bool _hasSellPending;
public decimal StartOffset
{
get => _startOffset.Value;
set => _startOffset.Value = value;
}
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
public decimal GridDistance
{
get => _gridDistance.Value;
set => _gridDistance.Value = value;
}
public decimal StepDistance
{
get => _stepDistance.Value;
set => _stepDistance.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public VRSetka3Strategy()
{
_startOffset = Param(nameof(StartOffset), 100m)
.SetGreaterThanZero()
.SetDisplay("Start Offset", "Offset for first limit orders", "Parameters");
_takeProfit = Param(nameof(TakeProfit), 300m)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Distance for profit taking", "Parameters");
_gridDistance = Param(nameof(GridDistance), 300m)
.SetGreaterThanZero()
.SetDisplay("Grid Distance", "Base distance between grid levels", "Parameters");
_stepDistance = Param(nameof(StepDistance), 50m)
.SetGreaterThanZero()
.SetDisplay("Step Distance", "Additional distance for next levels", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var price = candle.ClosePrice;
// Check take profit for long grid
if (_buyVolume > 0 && price >= _buyAvgPrice + TakeProfit)
{
SellMarket();
ResetState();
return;
}
// Check take profit for short grid
if (_sellVolume > 0 && price <= _sellAvgPrice - TakeProfit)
{
BuyMarket();
ResetState();
return;
}
// Place grid orders
if (_buyVolume > 0 && !_hasBuyPending)
{
var level = _buyAvgPrice - (GridDistance + StepDistance * _buyCount);
if (level > 0)
{
BuyLimit(level);
_hasBuyPending = true;
}
}
else if (_sellVolume > 0 && !_hasSellPending)
{
var level = _sellAvgPrice + (GridDistance + StepDistance * _sellCount);
SellLimit(level);
_hasSellPending = true;
}
else if (_buyVolume == 0 && _sellVolume == 0)
{
if (!_hasBuyPending)
{
var buyPrice = price - StartOffset;
if (buyPrice > 0)
{
BuyLimit(buyPrice);
_hasBuyPending = true;
}
}
if (!_hasSellPending)
{
var sellPrice = price + StartOffset;
SellLimit(sellPrice);
_hasSellPending = true;
}
}
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (trade.Order.Side == Sides.Buy)
{
_buyAvgPrice = (_buyAvgPrice * _buyVolume + trade.Trade.Price * trade.Trade.Volume) / (_buyVolume + trade.Trade.Volume);
_buyVolume += trade.Trade.Volume;
_buyCount++;
_hasBuyPending = false;
}
else if (trade.Order.Side == Sides.Sell)
{
_sellAvgPrice = (_sellAvgPrice * _sellVolume + trade.Trade.Price * trade.Trade.Volume) / (_sellVolume + trade.Trade.Volume);
_sellVolume += trade.Trade.Volume;
_sellCount++;
_hasSellPending = false;
}
}
private void ResetState()
{
_buyAvgPrice = _sellAvgPrice = 0;
_buyVolume = _sellVolume = 0;
_buyCount = _sellCount = 0;
_hasBuyPending = _hasSellPending = false;
}
}
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, Sides
from StockSharp.Algo.Strategies import Strategy
class vr_setka3_strategy(Strategy):
def __init__(self):
super(vr_setka3_strategy, self).__init__()
self._start_offset = self.Param("StartOffset", 100.0)
self._take_profit = self.Param("TakeProfit", 300.0)
self._grid_distance = self.Param("GridDistance", 300.0)
self._step_distance = self.Param("StepDistance", 50.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._buy_avg_price = 0.0
self._buy_volume = 0.0
self._sell_avg_price = 0.0
self._sell_volume = 0.0
self._buy_count = 0
self._sell_count = 0
self._has_buy_pending = False
self._has_sell_pending = False
@property
def StartOffset(self):
return self._start_offset.Value
@StartOffset.setter
def StartOffset(self, value):
self._start_offset.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def GridDistance(self):
return self._grid_distance.Value
@GridDistance.setter
def GridDistance(self, value):
self._grid_distance.Value = value
@property
def StepDistance(self):
return self._step_distance.Value
@StepDistance.setter
def StepDistance(self, value):
self._step_distance.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(vr_setka3_strategy, self).OnStarted2(time)
self._reset_state()
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
price = float(candle.ClosePrice)
if self._buy_volume > 0.0 and price >= self._buy_avg_price + float(self.TakeProfit):
self.SellMarket()
self._reset_state()
return
if self._sell_volume > 0.0 and price <= self._sell_avg_price - float(self.TakeProfit):
self.BuyMarket()
self._reset_state()
return
if self._buy_volume > 0.0 and not self._has_buy_pending:
level = self._buy_avg_price - (float(self.GridDistance) + float(self.StepDistance) * self._buy_count)
if level > 0.0:
self.BuyLimit(level)
self._has_buy_pending = True
elif self._sell_volume > 0.0 and not self._has_sell_pending:
level = self._sell_avg_price + (float(self.GridDistance) + float(self.StepDistance) * self._sell_count)
self.SellLimit(level)
self._has_sell_pending = True
elif self._buy_volume == 0.0 and self._sell_volume == 0.0:
if not self._has_buy_pending:
buy_price = price - float(self.StartOffset)
if buy_price > 0.0:
self.BuyLimit(buy_price)
self._has_buy_pending = True
if not self._has_sell_pending:
sell_price = price + float(self.StartOffset)
self.SellLimit(sell_price)
self._has_sell_pending = True
def OnOwnTradeReceived(self, trade):
super(vr_setka3_strategy, self).OnOwnTradeReceived(trade)
trade_price = float(trade.Trade.Price)
trade_vol = float(trade.Trade.Volume)
if trade.Order.Side == Sides.Buy:
self._buy_avg_price = (self._buy_avg_price * self._buy_volume + trade_price * trade_vol) / (self._buy_volume + trade_vol)
self._buy_volume += trade_vol
self._buy_count += 1
self._has_buy_pending = False
elif trade.Order.Side == Sides.Sell:
self._sell_avg_price = (self._sell_avg_price * self._sell_volume + trade_price * trade_vol) / (self._sell_volume + trade_vol)
self._sell_volume += trade_vol
self._sell_count += 1
self._has_sell_pending = False
def _reset_state(self):
self._buy_avg_price = 0.0
self._sell_avg_price = 0.0
self._buy_volume = 0.0
self._sell_volume = 0.0
self._buy_count = 0
self._sell_count = 0
self._has_buy_pending = False
self._has_sell_pending = False
def OnReseted(self):
super(vr_setka3_strategy, self).OnReseted()
self._reset_state()
def CreateClone(self):
return vr_setka3_strategy()