Стратегия Maximus vX Lite
Конверсия советника MetaTrader 5 «maximus_vX lite» на высокоуровневый API StockSharp. Стратегия ищет зоны консолидации выше и ниже текущей цены и ждёт, пока цена не уйдёт на заданное число пунктов от границ зоны, прежде чем открыть сделку. Размер позиции рассчитывается по необязательному проценту риска, а при достижении плавающей прибыли все позиции могут быть принудительно закрыты.
Логика стратегии
- Исторический анализ – на каждой завершённой свече стратегия хранит до
HistoryDepthсвечей и с помощью окна длинойRangeLookbackнаходит компактные максимумы и минимумы, формирующие зоны консолидации. - Верхний канал – если найден допустимый верхний блок, канал центрируется вокруг текущего закрытия с шириной
RangePoints. Если подходящего блока нет, используется синтетический канал той же ширины, привязанный к текущей цене. - Нижний канал – нижний блок либо берётся напрямую из истории (если выполнены условия по диапазону), либо,
при отсутствии подходящих данных, формируется синтетически вокруг цены минус
RangePoints. - Входы в лонг – доступны два сценария:
- Пробой нижней консолидации: цена должна превысить
_lowerMaxна величинуDistancePoints, при этом верхний канал обязан существовать. Тейк-профит рассчитывается как две трети расстояния между_lowerMaxи_upperMin, но не меньшеRangePoints. - Пробой верхнего канала: цена должна превысить
_upperMaxнаDistancePoints. Тейк-профит равен2 * RangePoints.
- Пробой нижней консолидации: цена должна превысить
- Входы в шорт – зеркальная логика при падении цены ниже
_upperMinили_lowerMinна величинуDistancePoints. Основной шорт использует динамическую цель «две трети», вторичный — фиксированные2 * RangePoints. - Стопы и выходы – параметр
StopLossPointsзадаёт фиксированный стоп (если больше нуля).MinProfitPercentотслеживает разницу между текущей стоимостью портфеля и балансом в момент отсутствия позиций и закрывает сделки при превышении порога. Проверка стопов и целей выполняется вручную, повторяя поведение исходного советника. - Размер позиции – при
RiskPercent> 0 и наличии стопа объём рассчитывается от стоимости портфеля и расстояния до стопа. В противном случае используется базовое свойствоVolume.
Параметры
DelayOpen(по умолчанию2) – количество таймфреймов, в течение которых допускается усреднение в ту же сторону.DistancePoints(по умолчанию850) – минимальное расстояние от границы зоны консолидации для входа.RangePoints(по умолчанию500) – ширина прямоугольников консолидации.HistoryDepth(по умолчанию1000) – число сохраняемых свечей для анализа истории.RangeLookback(по умолчанию40) – длина окна для поиска локальных максимумов и минимумов.CandleType(по умолчаниюTimeSpan.FromMinutes(15).TimeFrame()) – рабочий таймфрейм.RiskPercent(по умолчанию5m) – процент капитала, используемый под риск на сделку. Ноль отключает расчёт риска.StopLossPoints(по умолчанию1000) – расстояние до стоп-лосса; ноль отключает стоп.MinProfitPercent(по умолчанию1m) – порог плавающей прибыли для принудительного закрытия всех позиций.
Дополнительная информация
- Направление: Лонг и шорт
- Выход: Фиксированный стоп/тейк, плюс закрытие по прибыли
MinProfitPercent - Стопы: Опциональный фиксированный стоп
StopLossPoints - Индикаторы: Нет (анализ чистой цены с использованием скользящего окна)
- Таймфрейм: Настраивается параметром
CandleType(по умолчанию 15 минут) - Сложность: Средняя (исторический анализ, динамические цели и риск-менеджмент)
- Уровень риска: Повышенный при использовании процентного риска из-за пробойной природы стратегии
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>
/// Implements the Maximus vX Lite consolidation breakout strategy.
/// </summary>
public class MaximusVXLiteStrategy : Strategy
{
private readonly StrategyParam<int> _delayOpen;
private readonly StrategyParam<int> _distancePoints;
private readonly StrategyParam<int> _rangePoints;
private readonly StrategyParam<int> _historyDepth;
private readonly StrategyParam<int> _rangeLookback;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<decimal> _minProfitPercent;
private readonly List<CandleInfo> _history = new();
private decimal _upperMax;
private decimal _upperMin;
private decimal _lowerMax;
private decimal _lowerMin;
private decimal _priceStep = 1m;
private decimal _extDistance;
private decimal _extRange;
private decimal _extStopLoss;
private DateTimeOffset? _lastBuyTime;
private DateTimeOffset? _lastSellTime;
private decimal _lastKnownBalance;
private decimal? _activeStop;
private decimal? _activeTake;
private readonly struct CandleInfo
{
public CandleInfo(decimal high, decimal low)
{
High = high;
Low = low;
}
public decimal High { get; }
public decimal Low { get; }
}
/// <summary>
/// Number of timeframe intervals allowed before averaging into the same direction.
/// </summary>
public int DelayOpen
{
get => _delayOpen.Value;
set => _delayOpen.Value = value;
}
/// <summary>
/// Minimum distance in points from the consolidation band before entering a trade.
/// </summary>
public int DistancePoints
{
get => _distancePoints.Value;
set => _distancePoints.Value = value;
}
/// <summary>
/// Size of the consolidation range in points.
/// </summary>
public int RangePoints
{
get => _rangePoints.Value;
set => _rangePoints.Value = value;
}
/// <summary>
/// Number of historical candles considered when searching for ranges.
/// </summary>
public int HistoryDepth
{
get => _historyDepth.Value;
set => _historyDepth.Value = value;
}
/// <summary>
/// Window length used to calculate local highs and lows.
/// </summary>
public int RangeLookback
{
get => _rangeLookback.Value;
set => _rangeLookback.Value = value;
}
/// <summary>
/// Candle type used for calculations and signals.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Risk budget per trade expressed as percent of portfolio value.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// Stop loss distance in points.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Floating profit percentage that triggers forced position close.
/// </summary>
public decimal MinProfitPercent
{
get => _minProfitPercent.Value;
set => _minProfitPercent.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="MaximusVXLiteStrategy"/> class.
/// </summary>
public MaximusVXLiteStrategy()
{
_delayOpen = Param(nameof(DelayOpen), 2)
.SetNotNegative()
.SetDisplay("Delay Open", "How many timeframe periods allow averaging in the same direction", "Trading Rules");
_distancePoints = Param(nameof(DistancePoints), 850)
.SetGreaterThanZero()
.SetDisplay("Distance", "Minimum distance from consolidation band before trading", "Trading Rules");
_rangePoints = Param(nameof(RangePoints), 500)
.SetGreaterThanZero()
.SetDisplay("Range", "Width of consolidation channel in points", "Trading Rules");
_historyDepth = Param(nameof(HistoryDepth), 200)
.SetGreaterThanZero()
.SetDisplay("History Depth", "Number of candles inspected for consolidation zones", "Data");
_rangeLookback = Param(nameof(RangeLookback), 40)
.SetGreaterThanZero()
.SetDisplay("Range Lookback", "Candles used to calculate local maxima and minima", "Data");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe used by the strategy", "General");
_riskPercent = Param(nameof(RiskPercent), 5m)
.SetNotNegative()
.SetDisplay("Risk Percent", "Portfolio percent risked per trade", "Risk");
_stopLossPoints = Param(nameof(StopLossPoints), 1000)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop loss distance in points", "Risk");
_minProfitPercent = Param(nameof(MinProfitPercent), 1m)
.SetNotNegative()
.SetDisplay("Min Profit Percent", "Floating profit percent required to close all positions", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_history.Clear();
_upperMax = _upperMin = _lowerMax = _lowerMin = 0m;
_priceStep = 0m;
_extDistance = 0m;
_extRange = 0m;
_extStopLoss = 0m;
_lastBuyTime = null;
_lastSellTime = null;
_activeStop = null;
_activeTake = null;
_lastKnownBalance = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
UpdateDerivedValues();
_lastKnownBalance = Portfolio?.CurrentValue ?? 0m;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(3, UnitTypes.Percent),
stopLoss: new Unit(2, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
UpdateDerivedValues();
try
{
UpdateHistory(candle);
}
catch (ArgumentOutOfRangeException)
{
return;
}
if (Position == 0m)
{
var balance = Portfolio?.CurrentValue;
if (balance.HasValue && balance.Value > 0m)
_lastKnownBalance = balance.Value;
_activeStop = null;
_activeTake = null;
}
try
{
FindHighLow(candle.ClosePrice);
}
catch (ArgumentOutOfRangeException)
{
return;
}
if (HandleStopsAndTargets(candle))
return;
TryEnterPositions(candle);
TryLockInProfit();
}
private void UpdateHistory(ICandleMessage candle)
{
// Store the latest completed candle at the front of the list.
_history.Insert(0, new CandleInfo(candle.HighPrice, candle.LowPrice));
while (_history.Count > HistoryDepth)
_history.RemoveAt(_history.Count - 1);
}
private void UpdateDerivedValues()
{
// Convert point-based parameters to actual price distances using the security step.
var step = Security?.PriceStep ?? 1m;
if (step <= 0m)
step = 1m;
_priceStep = step;
_extDistance = DistancePoints * step;
_extRange = RangePoints * step;
_extStopLoss = StopLossPoints * step;
}
private void FindHighLow(decimal currentClose)
{
if (_history.Count == 0)
return;
var recalc = currentClose - 100m * _priceStep > _lowerMax
|| currentClose + 100m * _priceStep < _lowerMin
|| currentClose - 100m * _priceStep > _upperMax
|| currentClose + 100m * _priceStep < _upperMin;
if (!recalc)
return;
var foundUpper = false;
for (var i = 0; i < _history.Count; i++)
{
var high = _history[i].High;
if (currentClose - _extRange <= high)
continue;
var (windowMax, windowMin) = GetRangeWindow(i);
if (windowMax == 0m && windowMin == 0m)
continue;
if (windowMax - windowMin <= _extRange && currentClose + _extRange > windowMax && currentClose + _extRange > windowMin)
{
foundUpper = true;
break;
}
}
var halfRange = RangePoints * 0.5m * _priceStep;
if (!foundUpper)
{
var baseValue = Math.Floor(currentClose + 100m * _priceStep);
_upperMax = baseValue + halfRange;
_upperMin = baseValue - halfRange;
}
else
{
var baseValue = Math.Floor((currentClose + 100m * _priceStep) * 100m) / 100m;
_upperMax = baseValue + halfRange;
_upperMin = baseValue - halfRange;
}
var lowerFound = false;
decimal lowerMax = 0m;
decimal lowerMin = 0m;
for (var i = 0; i < _history.Count; i++)
{
var high = _history[i].High;
if (currentClose - _extRange <= high)
continue;
var (windowMax, windowMin) = GetRangeWindow(i);
if (windowMax == 0m && windowMin == 0m)
continue;
if (windowMax - windowMin <= _extRange && currentClose - _extRange > windowMax && currentClose - _extRange > windowMin)
{
lowerMax = windowMax;
lowerMin = windowMin;
lowerFound = true;
break;
}
}
if (!lowerFound)
{
var baseValue = Math.Floor((currentClose - 100m * _priceStep) * 100m) / 100m;
lowerMax = baseValue + halfRange;
lowerMin = baseValue - halfRange;
}
_lowerMax = lowerMax;
_lowerMin = lowerMin;
}
private (decimal max, decimal min) GetRangeWindow(int startIndex)
{
// Replicates ArrayMaximum/ArrayMinimum over a sliding window.
var count = Math.Min(RangeLookback, _history.Count - startIndex);
if (count <= 0)
return (0m, 0m);
var max = decimal.MinValue;
var min = decimal.MaxValue;
for (var j = 0; j < count; j++)
{
var index = startIndex + j;
if (index >= _history.Count)
break;
var item = _history[index];
if (item.High > max)
max = item.High;
if (item.Low < min)
min = item.Low;
}
return (max, min);
}
private bool HandleStopsAndTargets(ICandleMessage candle)
{
// Manually exit positions when candle extremes touch the stored stop or target.
if (Position > 0m)
{
if (_activeStop.HasValue && candle.LowPrice <= _activeStop.Value)
{
SellMarket(Position);
ResetAfterExit();
return true;
}
if (_activeTake.HasValue && candle.HighPrice >= _activeTake.Value)
{
SellMarket(Position);
ResetAfterExit();
return true;
}
}
else if (Position < 0m)
{
var volume = Math.Abs(Position);
if (_activeStop.HasValue && candle.HighPrice >= _activeStop.Value)
{
BuyMarket(volume);
ResetAfterExit();
return true;
}
if (_activeTake.HasValue && candle.LowPrice <= _activeTake.Value)
{
BuyMarket(volume);
ResetAfterExit();
return true;
}
}
return false;
}
private void TryEnterPositions(ICandleMessage candle)
{
var price = candle.ClosePrice;
if (price <= 0m)
return;
var timeFrame = CandleType.Arg is TimeSpan span ? span : TimeSpan.Zero;
var delayDuration = TimeSpan.FromTicks(timeFrame.Ticks * DelayOpen);
var now = candle.CloseTime;
var hasLong = Position > 0m;
var hasShort = Position < 0m;
var allowAdditionalBuy = !hasLong;
if (DelayOpen > 0 && hasLong)
allowAdditionalBuy = _lastBuyTime.HasValue && (_lastBuyTime.Value + delayDuration) > now;
else if (DelayOpen == 0 && hasLong)
allowAdditionalBuy = false;
var allowAdditionalSell = !hasShort;
if (DelayOpen > 0 && hasShort)
allowAdditionalSell = _lastSellTime.HasValue && (_lastSellTime.Value + delayDuration) > now;
else if (DelayOpen == 0 && hasShort)
allowAdditionalSell = false;
var volume = CalculateOrderVolume();
if (volume <= 0m)
return;
var buyPrimary = _lowerMax != 0m && _upperMin != 0m && price - _extDistance > _lowerMax;
var buySecondary = _upperMax != 0m && price - _extDistance > _upperMax;
if (allowAdditionalBuy && (buyPrimary || buySecondary) && Position <= 0m)
{
var stopPrice = StopLossPoints == 0 ? (decimal?)null : price - _extStopLoss;
decimal? takePrice;
if (buyPrimary)
{
var diff = _upperMin - _lowerMax;
var tempTp = diff / 3m * 2m * _priceStep;
if (tempTp < _extRange)
tempTp = _extRange;
takePrice = price + tempTp;
}
else
{
takePrice = price + 2m * _extRange;
}
var orderVolume = volume + (Position < 0m ? Math.Abs(Position) : 0m);
BuyMarket(orderVolume);
_activeStop = stopPrice;
_activeTake = takePrice;
_lastBuyTime = now;
return;
}
var sellPrimary = _upperMin != 0m && price + _extDistance < _upperMin;
var sellSecondary = _lowerMin != 0m && price + _extDistance < _lowerMin;
if (allowAdditionalSell && (sellPrimary || sellSecondary) && Position >= 0m)
{
var stopPrice = StopLossPoints == 0 ? (decimal?)null : price + _extStopLoss;
decimal? takePrice;
if (sellPrimary)
{
var diff = _upperMin - _lowerMax;
var tempTp = diff / 3m * 2m * _priceStep;
if (tempTp < _extRange)
tempTp = _extRange;
takePrice = price - tempTp;
}
else
{
takePrice = price - 2m * _extRange;
}
var orderVolume = volume + (Position > 0m ? Math.Abs(Position) : 0m);
SellMarket(orderVolume);
_activeStop = stopPrice;
_activeTake = takePrice;
_lastSellTime = now;
}
}
private decimal CalculateOrderVolume()
{
// Use risk budget when a stop loss is available, otherwise fall back to base volume.
var baseVolume = Volume;
if (RiskPercent > 0m && _extStopLoss > 0m)
{
var capital = Portfolio?.CurrentValue ?? 0m;
if (capital > 0m)
{
var riskBudget = capital * (RiskPercent / 100m);
if (riskBudget > 0m)
{
var rawVolume = riskBudget / _extStopLoss;
if (rawVolume > 0m)
baseVolume = rawVolume;
}
}
}
var step = Security?.VolumeStep ?? 1m;
if (step <= 0m)
step = 1m;
var adjusted = Math.Floor(baseVolume / step) * step;
if (adjusted <= 0m)
adjusted = step;
return adjusted;
}
private void TryLockInProfit()
{
if (Position == 0m)
return;
var balance = _lastKnownBalance;
if (balance <= 0m)
return;
var equity = Portfolio?.CurrentValue ?? balance;
var profitPercent = (equity - balance) / balance * 100m;
if (profitPercent > MinProfitPercent)
{
ClosePosition();
ResetAfterExit();
}
}
private void ClosePosition()
{
if (Position > 0m)
SellMarket(Position);
else if (Position < 0m)
BuyMarket(Math.Abs(Position));
}
private void ResetAfterExit()
{
// Clear timers and protective orders so the next setup starts clean.
_activeStop = null;
_activeTake = null;
_lastBuyTime = null;
_lastSellTime = null;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan, Math
class maximus_vx_lite_strategy(Strategy):
def __init__(self):
super(maximus_vx_lite_strategy, self).__init__()
self._delay_open = self.Param("DelayOpen", 2)
self._distance_points = self.Param("DistancePoints", 850)
self._range_points = self.Param("RangePoints", 500)
self._history_depth = self.Param("HistoryDepth", 200)
self._range_lookback = self.Param("RangeLookback", 40)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._stop_loss_points = self.Param("StopLossPoints", 1000)
self._history = []
self._upper_max = 0.0
self._upper_min = 0.0
self._lower_max = 0.0
self._lower_min = 0.0
self._price_step = 1.0
self._ext_distance = 0.0
self._ext_range = 0.0
self._ext_stop_loss = 0.0
self._last_buy_time = None
self._last_sell_time = None
self._active_stop = None
self._active_take = None
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(maximus_vx_lite_strategy, self).OnStarted2(time)
self._update_derived_values()
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _update_derived_values(self):
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if step <= 0:
step = 1.0
self._price_step = step
self._ext_distance = self._distance_points.Value * step
self._ext_range = self._range_points.Value * step
self._ext_stop_loss = self._stop_loss_points.Value * step
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
self._update_derived_values()
self._update_history(candle)
if self.Position == 0:
self._active_stop = None
self._active_take = None
self._find_high_low(float(candle.ClosePrice))
if self._handle_stops_and_targets(candle):
return
self._try_enter_positions(candle)
def _update_history(self, candle):
self._history.insert(0, (float(candle.HighPrice), float(candle.LowPrice)))
while len(self._history) > self._history_depth.Value:
self._history.pop()
def _find_high_low(self, current_close):
if len(self._history) == 0:
return
recalc = (current_close - 100.0 * self._price_step > self._lower_max or
current_close + 100.0 * self._price_step < self._lower_min or
current_close - 100.0 * self._price_step > self._upper_max or
current_close + 100.0 * self._price_step < self._upper_min)
if not recalc:
return
half_range = self._range_points.Value * 0.5 * self._price_step
found_upper = False
for i in range(len(self._history)):
high = self._history[i][0]
if current_close - self._ext_range <= high:
continue
window_max, window_min = self._get_range_window(i)
if window_max == 0 and window_min == 0:
continue
if (window_max - window_min <= self._ext_range and
current_close + self._ext_range > window_max and
current_close + self._ext_range > window_min):
found_upper = True
break
base_value = Math.Floor(current_close + 100.0 * self._price_step)
if found_upper:
base_value = Math.Floor((current_close + 100.0 * self._price_step) * 100.0) / 100.0
self._upper_max = base_value + half_range
self._upper_min = base_value - half_range
lower_found = False
lower_max = 0.0
lower_min = 0.0
for i in range(len(self._history)):
high = self._history[i][0]
if current_close - self._ext_range <= high:
continue
window_max, window_min = self._get_range_window(i)
if window_max == 0 and window_min == 0:
continue
if (window_max - window_min <= self._ext_range and
current_close - self._ext_range > window_max and
current_close - self._ext_range > window_min):
lower_max = window_max
lower_min = window_min
lower_found = True
break
if not lower_found:
base_value = Math.Floor((current_close - 100.0 * self._price_step) * 100.0) / 100.0
lower_max = base_value + half_range
lower_min = base_value - half_range
self._lower_max = lower_max
self._lower_min = lower_min
def _get_range_window(self, start_index):
count = min(self._range_lookback.Value, len(self._history) - start_index)
if count <= 0:
return (0.0, 0.0)
max_val = -1e18
min_val = 1e18
for j in range(count):
index = start_index + j
if index >= len(self._history):
break
h, l = self._history[index]
if h > max_val:
max_val = h
if l < min_val:
min_val = l
return (max_val, min_val)
def _handle_stops_and_targets(self, candle):
if self.Position > 0:
if self._active_stop is not None and float(candle.LowPrice) <= self._active_stop:
self.SellMarket(self.Position)
self._reset_after_exit()
return True
if self._active_take is not None and float(candle.HighPrice) >= self._active_take:
self.SellMarket(self.Position)
self._reset_after_exit()
return True
elif self.Position < 0:
vol = abs(self.Position)
if self._active_stop is not None and float(candle.HighPrice) >= self._active_stop:
self.BuyMarket(vol)
self._reset_after_exit()
return True
if self._active_take is not None and float(candle.LowPrice) <= self._active_take:
self.BuyMarket(vol)
self._reset_after_exit()
return True
return False
def _try_enter_positions(self, candle):
price = float(candle.ClosePrice)
if price <= 0:
return
now = candle.CloseTime
has_long = self.Position > 0
has_short = self.Position < 0
allow_buy = not has_long
allow_sell = not has_short
if self._delay_open.Value == 0:
if has_long:
allow_buy = False
if has_short:
allow_sell = False
buy_primary = self._lower_max != 0 and self._upper_min != 0 and price - self._ext_distance > self._lower_max
buy_secondary = self._upper_max != 0 and price - self._ext_distance > self._upper_max
if allow_buy and (buy_primary or buy_secondary) and self.Position <= 0:
stop_price = price - self._ext_stop_loss if self._stop_loss_points.Value > 0 else None
if buy_primary:
diff = self._upper_min - self._lower_max
temp_tp = diff / 3.0 * 2.0 * self._price_step
if temp_tp < self._ext_range:
temp_tp = self._ext_range
take_price = price + temp_tp
else:
take_price = price + 2.0 * self._ext_range
order_volume = float(self.Volume) + (abs(self.Position) if self.Position < 0 else 0)
self.BuyMarket(order_volume)
self._active_stop = stop_price
self._active_take = take_price
self._last_buy_time = now
return
sell_primary = self._upper_min != 0 and price + self._ext_distance < self._upper_min
sell_secondary = self._lower_min != 0 and price + self._ext_distance < self._lower_min
if allow_sell and (sell_primary or sell_secondary) and self.Position >= 0:
stop_price = price + self._ext_stop_loss if self._stop_loss_points.Value > 0 else None
if sell_primary:
diff = self._upper_min - self._lower_max
temp_tp = diff / 3.0 * 2.0 * self._price_step
if temp_tp < self._ext_range:
temp_tp = self._ext_range
take_price = price - temp_tp
else:
take_price = price - 2.0 * self._ext_range
order_volume = float(self.Volume) + (abs(self.Position) if self.Position > 0 else 0)
self.SellMarket(order_volume)
self._active_stop = stop_price
self._active_take = take_price
self._last_sell_time = now
def _reset_after_exit(self):
self._active_stop = None
self._active_take = None
self._last_buy_time = None
self._last_sell_time = None
def OnReseted(self):
super(maximus_vx_lite_strategy, self).OnReseted()
self._history = []
self._upper_max = 0.0
self._upper_min = 0.0
self._lower_max = 0.0
self._lower_min = 0.0
self._price_step = 1.0
self._ext_distance = 0.0
self._ext_range = 0.0
self._ext_stop_loss = 0.0
self._last_buy_time = None
self._last_sell_time = None
self._active_stop = None
self._active_take = None
def CreateClone(self):
return maximus_vx_lite_strategy()