Стратегия Very Blonde System
Сеточная контртрендовая стратегия, основанная на оригинальной системе "Very Blonde System" для MetaTrader. Стратегия ищет большое отклонение текущей цены от недавних экстремумов и открывает сделку в противоположном направлении.
Логика стратегии
- Вычисляется максимальный максимум и минимальный минимум за последние Count Bars свечей.
- При отсутствии открытых позиций:
- Если расстояние от текущей цены до недавнего максимума превышает Limit тиков, выполняется покупка по рынку.
- Если расстояние от текущей цены до недавнего минимума превышает Limit тиков, выполняется продажа по рынку.
- После входа размещаются четыре отложенных ордера каждые Grid тиков, при этом объём на каждом уровне удваивается.
- При наличии позиции:
- Если суммарная прибыль превышает Amount денежных единиц, позиция закрывается, а все ордера снимаются.
- Если параметр Lock Down больше нуля, после движения цены в прибыль на указанное количество тиков активируется защита безубытка. При возврате цены к уровню входа позиции закрываются.
Параметры
| Имя | Описание |
|---|---|
CountBars |
Количество свечей для поиска максимумов и минимумов. |
Limit |
Минимальная дистанция от экстремума в тиках для открытия сделки. |
Grid |
Расстояние в тиках между дополнительными ордерами сетки. |
Amount |
Целевая прибыль в деньгах для закрытия всех позиций. |
LockDown |
Дистанция в тиках для включения защиты безубытка. |
CandleType |
Тип свечей для расчётов. |
Стратегия использует рыночные ордера для входа и лимитные ордера для построения сетки. Все комментарии в коде написаны на английском языке.
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 based on distance from recent extremes.
/// Buys when price drops far below recent high and sells when price rises far above recent low.
/// Places additional limit orders forming a martingale grid and exits on total profit.
/// </summary>
public class VeryBlondeSystemStrategy : Strategy
{
private readonly StrategyParam<int> _countBars;
private readonly StrategyParam<decimal> _limit;
private readonly StrategyParam<decimal> _grid;
private readonly StrategyParam<decimal> _amount;
private readonly StrategyParam<decimal> _lockDown;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private bool _isLong;
private bool _lockActivated;
private decimal _lockPrice;
/// <summary>
/// Number of candles to search extremes.
/// </summary>
public int CountBars
{
get => _countBars.Value;
set => _countBars.Value = value;
}
/// <summary>
/// Minimum distance from recent extreme in ticks to trigger entry.
/// </summary>
public decimal Limit
{
get => _limit.Value;
set => _limit.Value = value;
}
/// <summary>
/// Grid distance in ticks between additional orders.
/// </summary>
public decimal Grid
{
get => _grid.Value;
set => _grid.Value = value;
}
/// <summary>
/// Target profit to close all positions.
/// </summary>
public decimal Amount
{
get => _amount.Value;
set => _amount.Value = value;
}
/// <summary>
/// Breakeven activation distance in ticks.
/// </summary>
public decimal LockDown
{
get => _lockDown.Value;
set => _lockDown.Value = value;
}
/// <summary>
/// Type of candles for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize strategy parameters.
/// </summary>
public VeryBlondeSystemStrategy()
{
_countBars = Param(nameof(CountBars), 10)
.SetDisplay("Count Bars", "Number of candles to search extremes", "General")
.SetGreaterThanZero()
.SetOptimize(5, 30, 5);
_limit = Param(nameof(Limit), 500m)
.SetDisplay("Limit", "Minimum distance from extreme in ticks", "Trading")
.SetGreaterThanZero();
_grid = Param(nameof(Grid), 35m)
.SetDisplay("Grid", "Grid distance in ticks", "Trading")
.SetGreaterThanZero();
_amount = Param(nameof(Amount), 40m)
.SetDisplay("Amount", "Target profit to close all positions", "Risk")
.SetGreaterThanZero();
_lockDown = Param(nameof(LockDown), 0m)
.SetDisplay("Lock Down", "Breakeven activation distance in ticks", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0m;
_isLong = false;
_lockActivated = false;
_lockPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var highest = new Highest { Length = CountBars };
var lowest = new Lowest { Length = CountBars };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(highest, lowest, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, highest);
DrawIndicator(area, lowest);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal high, decimal low)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var step = Security.PriceStep ?? 1m;
if (Position == 0)
{
CheckForOpen(candle, high, low, step);
}
else
{
CheckForClose(candle, step);
}
}
private void CheckForOpen(ICandleMessage candle, decimal high, decimal low, decimal step)
{
var close = candle.ClosePrice;
if (high - close > Limit * step)
{
OpenPosition(true, close, step);
}
else if (close - low > Limit * step)
{
OpenPosition(false, close, step);
}
}
private void OpenPosition(bool isBuy, decimal price, decimal step)
{
var volume = Volume;
if (isBuy)
{
BuyMarket(volume);
}
else
{
SellMarket(volume);
}
_entryPrice = price;
_isLong = isBuy;
_lockActivated = false;
_lockPrice = 0m;
}
private void CheckForClose(ICandleMessage candle, decimal step)
{
var currentProfit = Position * (candle.ClosePrice - _entryPrice);
if (currentProfit >= Amount)
{
CloseAll();
return;
}
if (LockDown <= 0m)
return;
if (_isLong)
{
if (!_lockActivated && candle.ClosePrice - _entryPrice > LockDown * step)
{
_lockActivated = true;
_lockPrice = _entryPrice;
}
else if (_lockActivated && candle.ClosePrice <= _lockPrice)
{
CloseAll();
}
}
else
{
if (!_lockActivated && _entryPrice - candle.ClosePrice > LockDown * step)
{
_lockActivated = true;
_lockPrice = _entryPrice;
}
else if (_lockActivated && candle.ClosePrice >= _lockPrice)
{
CloseAll();
}
}
}
private void CloseAll()
{
if (Position > 0)
SellMarket(Math.Abs(Position));
else if (Position < 0)
BuyMarket(Math.Abs(Position));
_entryPrice = 0m;
_lockActivated = false;
_lockPrice = 0m;
}
}
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 Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class very_blonde_system_strategy(Strategy):
def __init__(self):
super(very_blonde_system_strategy, self).__init__()
self._count_bars = self.Param("CountBars", 10)
self._limit = self.Param("Limit", 500.0)
self._grid = self.Param("Grid", 35.0)
self._amount = self.Param("Amount", 40.0)
self._lock_down = self.Param("LockDown", 0.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._entry_price = 0.0
self._is_long = False
self._lock_activated = False
self._lock_price = 0.0
@property
def CountBars(self):
return self._count_bars.Value
@CountBars.setter
def CountBars(self, value):
self._count_bars.Value = value
@property
def Limit(self):
return self._limit.Value
@Limit.setter
def Limit(self, value):
self._limit.Value = value
@property
def Grid(self):
return self._grid.Value
@Grid.setter
def Grid(self, value):
self._grid.Value = value
@property
def Amount(self):
return self._amount.Value
@Amount.setter
def Amount(self, value):
self._amount.Value = value
@property
def LockDown(self):
return self._lock_down.Value
@LockDown.setter
def LockDown(self, value):
self._lock_down.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(very_blonde_system_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._is_long = False
self._lock_activated = False
self._lock_price = 0.0
highest = Highest()
highest.Length = self.CountBars
lowest = Lowest()
lowest.Length = self.CountBars
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(highest, lowest, self.ProcessCandle).Start()
def ProcessCandle(self, candle, high_val, low_val):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
high = float(high_val)
low = float(low_val)
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position == 0:
self._check_open(close, high, low, step)
else:
self._check_close(candle, step)
def _check_open(self, close, high, low, step):
limit_dist = float(self.Limit) * step
if high - close > limit_dist:
self.BuyMarket()
self._entry_price = close
self._is_long = True
self._lock_activated = False
self._lock_price = 0.0
elif close - low > limit_dist:
self.SellMarket()
self._entry_price = close
self._is_long = False
self._lock_activated = False
self._lock_price = 0.0
def _check_close(self, candle, step):
close = float(candle.ClosePrice)
current_profit = self.Position * (close - self._entry_price)
if current_profit >= float(self.Amount):
self._close_all()
return
lock_down = float(self.LockDown)
if lock_down <= 0.0:
return
if self._is_long:
if not self._lock_activated and close - self._entry_price > lock_down * step:
self._lock_activated = True
self._lock_price = self._entry_price
elif self._lock_activated and close <= self._lock_price:
self._close_all()
else:
if not self._lock_activated and self._entry_price - close > lock_down * step:
self._lock_activated = True
self._lock_price = self._entry_price
elif self._lock_activated and close >= self._lock_price:
self._close_all()
def _close_all(self):
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._entry_price = 0.0
self._lock_activated = False
self._lock_price = 0.0
def OnReseted(self):
super(very_blonde_system_strategy, self).OnReseted()
self._entry_price = 0.0
self._is_long = False
self._lock_activated = False
self._lock_price = 0.0
def CreateClone(self):
return very_blonde_system_strategy()