Maximus vX Lite-Strategie
Portierung des MetaTrader 5-Expertenberaters „maximus_vX lite" auf die StockSharp High-Level-API. Die Strategie sucht nach Konsolidierungszonen ober- und unterhalb des aktuellen Preises und wartet, bis sich der Preis um eine konfigurierbare Anzahl von Punkten von diesen Zonen entfernt, bevor sie einsteigt. Die Positionsgröße wird aus einem optionalen Risikoprozent-Budget bestimmt, und schwebende Gewinne können eine erzwungene Liquidation aller offenen Exposition auslösen.
Strategielogik
- Historischer Scan – bei jeder abgeschlossenen Kerze hält die Strategie bis zu
HistoryDepthKerzen und verwendet ein gleitendesRangeLookback-Fenster, um kompakte Hochs und Tiefs zu erkennen, die Konsolidierungsbereiche bilden. - Oberer Kanal – wenn ein gültiger oberer Block erkannt wird, wird der Kanal um den aktuellen Schluss mit einer Breite von
RangePointsverankert. Wenn kein historischer Block die Anforderungen erfüllt, fällt der Kanal auf die gleiche Breite zurück, die am aktuellen Preis ausgerichtet ist. - Unterer Kanal – der untere Block wird entweder direkt aus historischen Hochs/Tiefs genommen, die die Bedingungen erfüllen, oder,
wenn keine vorhanden sind, aus einem synthetischen Niveau um den aktuellen Schluss minus
RangePoints. - Long-Einstiege – zwei Long-Setups sind erlaubt:
- Ausbruch über die untere Konsolidierung: Der Preis muss
_lowerMaxumDistancePointsüberschreiten und der obere Kanal muss verfügbar sein. Das Take Profit verwendet zwei Drittel der Distanz zwischen_lowerMaxund_upperMin, mit einem Minimum vonRangePoints. - Ausbruch über den oberen Kanal: Der Preis muss
_upperMaxumDistancePointsüberschreiten. Das Take Profit wird auf2 * RangePointsgesetzt.
- Ausbruch über die untere Konsolidierung: Der Preis muss
- Short-Einstiege – symmetrische Logik wird ausgelöst, wenn der Preis um
DistancePointsunter_upperMinoder_lowerMinfällt. Das primäre Short-Setup verwendet ebenfalls das dynamische Zweidrittel-Ziel, das sekundäre verwendet2 * RangePoints. - Stops und Ausstiege –
StopLossPointsdefiniert einen festen Schutz-Stop, wenn größer als null.MinProfitPercentüberwacht schwebende Kapital gegenüber dem letzten Flat-Balance und schließt alle Positionen, sobald der Schwellenwert überschritten wird. Manuelle Stop-/Ziel-Prüfungen emulieren das ursprüngliche Expertenberater-Verhalten innerhalb der Strategie. - Positionsgröße – wenn
RiskPercentgrößer als null ist und ein Stop definiert ist, wird das Ordervolumen aus dem Portfolio-Wert und der Stop-Distanz berechnet. Andernfalls verwendet die Strategie dieVolume-Eigenschaft erneut.
Parameter
DelayOpen(Standard2) – Anzahl der Zeitrahmenbars, während derer das Hinzufügen zur gleichen Seite erlaubt ist.DistancePoints(Standard850) – Mindestabstand von einer Konsolidierungsgrenze vor dem Einstieg.RangePoints(Standard500) – Breite der Konsolidierungsboxen.HistoryDepth(Standard1000) – Anzahl der im Speicher gehaltenen Kerzen für historische Scans.RangeLookback(Standard40) – Fensterlänge zur Berechnung lokaler Maxima und Minima.CandleType(StandardTimeSpan.FromMinutes(15).TimeFrame()) – Zeitrahmen für Berechnungen.RiskPercent(Standard5m) – Prozentsatz des Portfolio-Werts, der pro Trade riskiert wird. Auf null setzen, um festes Volumen zu verwenden.StopLossPoints(Standard1000) – Schutz-Stop-Distanz; null deaktiviert den Stop.MinProfitPercent(Standard1m) – schwebender Gewinnprozentsatz, der alle Positionen zum Schließen zwingt.
Details
- Long/Short: Beide Richtungen
- Ausstiegskriterien: Fester Stop oder Take Profit, Kapitalsperre via
MinProfitPercent - Stops: Optionaler fester Stop aus
StopLossPoints - Indikatoren: Keine (reiner Price Action mit Gleitfenster-Analyse)
- Zeitrahmen: Konfigurierbar via
CandleType(Standard 15 Minuten) - Komplexität: Mittel (kombiniert History-Scanning, dynamische Ziele und Risikosizing)
- Risikolevel: Hoch bei Verwendung des Risikoprozentsatzes aufgrund der Ausbruchsnatur
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()