VR Steals 2 策略
该策略是 MetaTrader 5 专家顾问“VR---STEALS-2”的 StockSharp 版本,用于演示在没有指标的情况下如何管理持仓。
工作原理
- 启动后立即通过
BuyMarket买入并记录成交价。 - 通过
SubscribeCandles订阅蜡烛数据(默认 1 分钟)。 - 每根完成的蜡烛执行以下检查:
- 当价格向有利方向移动
Breakeven个价位后,止损移动到入场价上方BreakevenOffset个价位。 - 当价格达到入场价加
TakeProfit个价位时,通过SellMarket平仓。 - 如果价格跌至止损价(初始为入场价下方
StopLoss个价位,或已移动的保本止损),则平仓。
- 当价格向有利方向移动
- 平仓后策略不会再次入场。
参数
| 名称 | 说明 | 默认值 |
|---|---|---|
| TakeProfit | 到止盈水平的价位数。 | 50 |
| StopLoss | 初始止损距离的价位数。 | 50 |
| Breakeven | 激活保本止损所需的盈利价位数。 | 20 |
| BreakevenOffset | 保本止损相对于入场价的偏移。 | 9 |
| CandleType | 用于处理价格的蜡烛类型。 | 1 分钟 |
策略使用 StartProtection() 启动内置的持仓保护。
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>
/// Opens positions based on SMA trend direction, manages with fixed take profit,
/// stop loss and breakeven levels.
/// </summary>
public class VRSteals2Strategy : Strategy
{
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _breakeven;
private readonly StrategyParam<decimal> _breakevenOffset;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private decimal _stopPrice;
private bool _breakevenActivated;
private decimal _previousFast;
private decimal _previousSlow;
private bool _maInitialized;
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public decimal Breakeven { get => _breakeven.Value; set => _breakeven.Value = value; }
public decimal BreakevenOffset { get => _breakevenOffset.Value; set => _breakevenOffset.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public VRSteals2Strategy()
{
_takeProfit = Param(nameof(TakeProfit), 50m)
.SetDisplay("Take Profit", "Distance to take profit in steps", "General");
_stopLoss = Param(nameof(StopLoss), 50m)
.SetDisplay("Stop Loss", "Distance to stop loss in steps", "General");
_breakeven = Param(nameof(Breakeven), 20m)
.SetDisplay("Breakeven", "Distance to activate breakeven in steps", "General");
_breakevenOffset = Param(nameof(BreakevenOffset), 9m)
.SetDisplay("Breakeven Offset", "Offset applied when breakeven is triggered", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to process", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0m;
_stopPrice = 0m;
_breakevenActivated = false;
_previousFast = 0m;
_previousSlow = 0m;
_maInitialized = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0m;
_breakevenActivated = false;
var fastSma = new SimpleMovingAverage { Length = 8 };
var slowSma = new SimpleMovingAverage { Length = 34 };
var sub = SubscribeCandles(CandleType);
sub.Bind(fastSma, slowSma, (candle, fast, slow) =>
{
if (candle.State != CandleStates.Finished)
return;
var price = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
// Manage existing long position
if (Position > 0)
{
if (!_breakevenActivated && Breakeven > 0m && price >= _entryPrice + Breakeven * step)
{
_stopPrice = _entryPrice + BreakevenOffset * step;
_breakevenActivated = true;
}
if (TakeProfit > 0m && price >= _entryPrice + TakeProfit * step)
{
SellMarket();
_entryPrice = 0m;
_breakevenActivated = false;
return;
}
var stop = _breakevenActivated
? _stopPrice
: (StopLoss > 0m ? _entryPrice - StopLoss * step : decimal.MinValue);
if (price <= stop)
{
SellMarket();
_entryPrice = 0m;
_breakevenActivated = false;
return;
}
}
// Manage existing short position
else if (Position < 0)
{
if (!_breakevenActivated && Breakeven > 0m && price <= _entryPrice - Breakeven * step)
{
_stopPrice = _entryPrice - BreakevenOffset * step;
_breakevenActivated = true;
}
if (TakeProfit > 0m && price <= _entryPrice - TakeProfit * step)
{
BuyMarket();
_entryPrice = 0m;
_breakevenActivated = false;
return;
}
var stop = _breakevenActivated
? _stopPrice
: (StopLoss > 0m ? _entryPrice + StopLoss * step : decimal.MaxValue);
if (price >= stop)
{
BuyMarket();
_entryPrice = 0m;
_breakevenActivated = false;
return;
}
}
// Entry signals based on SMA cross
if (Position == 0)
{
if (_maInitialized && _previousFast <= _previousSlow && fast > slow)
{
BuyMarket();
_entryPrice = price;
_breakevenActivated = false;
}
else if (_maInitialized && _previousFast >= _previousSlow && fast < slow)
{
SellMarket();
_entryPrice = price;
_breakevenActivated = false;
}
}
_previousFast = fast;
_previousSlow = slow;
_maInitialized = true;
}).Start();
StartProtection(
new Unit(2000m, UnitTypes.Absolute),
new Unit(1000m, UnitTypes.Absolute));
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class vr_steals_2_strategy(Strategy):
"""SMA crossover (8/34) with breakeven/SL/TP management and StartProtection."""
def __init__(self):
super(vr_steals_2_strategy, self).__init__()
self._tp = self.Param("TakeProfit", 50).SetDisplay("Take Profit", "TP in steps", "General")
self._sl = self.Param("StopLoss", 50).SetDisplay("Stop Loss", "SL in steps", "General")
self._breakeven = self.Param("Breakeven", 20).SetDisplay("Breakeven", "Distance to activate breakeven", "General")
self._breakeven_offset = self.Param("BreakevenOffset", 9).SetDisplay("Breakeven Offset", "Offset when breakeven triggered", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(vr_steals_2_strategy, self).OnReseted()
self._entry_price = 0
self._stop_price = 0
self._be_activated = False
self._prev_fast = 0
self._prev_slow = 0
self._ma_init = False
def OnStarted2(self, time):
super(vr_steals_2_strategy, self).OnStarted2(time)
self._entry_price = 0
self._stop_price = 0
self._be_activated = False
self._prev_fast = 0
self._prev_slow = 0
self._ma_init = False
fast_sma = SimpleMovingAverage()
fast_sma.Length = 8
slow_sma = SimpleMovingAverage()
slow_sma.Length = 34
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(fast_sma, slow_sma, self.OnProcess).Start()
self.StartProtection(
Unit(2000, UnitTypes.Absolute),
Unit(1000, UnitTypes.Absolute))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, fast_sma)
self.DrawIndicator(area, slow_sma)
self.DrawOwnTrades(area)
def OnProcess(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
price = float(candle.ClosePrice)
fv = float(fast_val)
sv = float(slow_val)
step = 1.0
tp_val = self._tp.Value
sl_val = self._sl.Value
be_val = self._breakeven.Value
be_off = self._breakeven_offset.Value
# Manage long
if self.Position > 0:
if not self._be_activated and be_val > 0 and price >= self._entry_price + be_val * step:
self._stop_price = self._entry_price + be_off * step
self._be_activated = True
if tp_val > 0 and price >= self._entry_price + tp_val * step:
self.SellMarket()
self._entry_price = 0
self._be_activated = False
self._prev_fast = fv
self._prev_slow = sv
self._ma_init = True
return
if self._be_activated:
stop = self._stop_price
else:
stop = self._entry_price - sl_val * step if sl_val > 0 else -999999
if price <= stop:
self.SellMarket()
self._entry_price = 0
self._be_activated = False
self._prev_fast = fv
self._prev_slow = sv
self._ma_init = True
return
# Manage short
elif self.Position < 0:
if not self._be_activated and be_val > 0 and price <= self._entry_price - be_val * step:
self._stop_price = self._entry_price - be_off * step
self._be_activated = True
if tp_val > 0 and price <= self._entry_price - tp_val * step:
self.BuyMarket()
self._entry_price = 0
self._be_activated = False
self._prev_fast = fv
self._prev_slow = sv
self._ma_init = True
return
if self._be_activated:
stop = self._stop_price
else:
stop = self._entry_price + sl_val * step if sl_val > 0 else 999999
if price >= stop:
self.BuyMarket()
self._entry_price = 0
self._be_activated = False
self._prev_fast = fv
self._prev_slow = sv
self._ma_init = True
return
# Entry on SMA cross
if self.Position == 0:
if self._ma_init and self._prev_fast <= self._prev_slow and fv > sv:
self.BuyMarket()
self._entry_price = price
self._be_activated = False
elif self._ma_init and self._prev_fast >= self._prev_slow and fv < sv:
self.SellMarket()
self._entry_price = price
self._be_activated = False
self._prev_fast = fv
self._prev_slow = sv
self._ma_init = True
def CreateClone(self):
return vr_steals_2_strategy()