Maximus vX Lite 策略
将 MetaTrader 5 顾问 "maximus_vX lite" 转换为 StockSharp 高层 API 的实现。 策略会在当前价格上下寻找盘整区,等待价格离开该区间一定的点数后再进场。仓位大小可以根据 可选的风险百分比预算计算,当浮动利润达到阈值时会强制平掉所有仓位。
策略逻辑
- 历史扫描:每根收盘 K 线后保留最多
HistoryDepth根历史 K 线,通过RangeLookback长度的滑动窗口 搜索满足条件的局部高点和低点,从而构建盘整区间。 - 上方通道:当检测到合格的上方盘整块时,以当前收盘价为中心,使用
RangePoints的宽度构建上轨。 如果历史数据中没有满足条件的块,则退化为围绕当前价格的同宽度通道。 - 下方通道:如果历史中存在符合条件的块,则直接使用其高点/低点;否则在当前收盘价下方
RangePoints的位置构建一个合成通道。 - 多头入场:允许两种多头情形:
- 突破下方盘整:价格必须比
_lowerMax高出DistancePoints,并且上方通道已建立。止盈取_lowerMax与_upperMin之间距离的三分之二,且不低于RangePoints。 - 突破上方通道:价格比
_upperMax高出DistancePoints。止盈设置为2 * RangePoints。
- 突破下方盘整:价格必须比
- 空头入场:当价格比
_upperMin或_lowerMin低DistancePoints时触发,对应的止盈逻辑与多头对称, 主信号使用三分之二的动态目标,次信号使用2 * RangePoints。 - 止损与退出:
StopLossPoints>0 时启用固定止损。MinProfitPercent监控浮动权益与最近空仓时的余额, 一旦超过阈值立即平仓。策略内部手动检查止损/止盈,以复现原始 EA 的行为。 - 头寸规模:当设置了
RiskPercent并且存在止损时,根据组合价值和止损距离计算下单数量; 否则使用默认的Volume数量。
参数
DelayOpen(默认2):允许在同方向加仓的时间框数量。DistancePoints(默认850):距离盘整边界至少多少点后才允许进场。RangePoints(默认500):盘整区的宽度。HistoryDepth(默认1000):保存的历史 K 线数量。RangeLookback(默认40):计算局部高低点的窗口长度。CandleType(默认TimeSpan.FromMinutes(15).TimeFrame()):使用的时间框。RiskPercent(默认5m):单笔交易的风险占组合价值的百分比,设为 0 表示使用固定手数。StopLossPoints(默认1000):止损距离;设为 0 时不启用止损。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()