Cross Line Trader Strategie
Überblick
Die Strategie emuliert den originalen MetaTrader-Expert "Cross Line Trader", indem sie auf Preisinteraktionen mit benutzerdefinierten synthetischen Linien reagiert. Anstatt auf manuelle Chart-Objekte zu hören, empfängt die StockSharp-Version alle Linienbeschreibungen über einen einzigen Parameter, analysiert diese beim Start und überwacht kontinuierlich abgeschlossene Kerzen. Wenn sich ein Kerzenöffnungspreis durch eine aktive Linie bewegt, platziert die Strategie eine Marktorder in der entsprechenden Richtung und deaktiviert diese Linie, damit sie nicht erneut ausgelöst werden kann.
Handelslogik
- Die Strategie abonniert den im Parameter Candle Type ausgewählten Kerzentyp und verarbeitet nur Kerzen im
Finished-Zustand, um Intrabar-Rauschen zu vermeiden.
- Synthetische Linien werden aus dem Parameter Line Definitions erstellt. Jede Linie hält ihren eigenen Zustand (aktiv/abgelaufen, Anzahl der verarbeiteten Bars und Geometrie).
- Bei Trend- oder Horizontal-Linien vergleicht der Algorithmus die vorherige Kerzenöffnung mit der nächsten relativ zur Preisverlaufsbahn der Linie:
- Ein Long-Signal tritt auf, wenn die vorherige Öffnung unter der Linie liegt und die aktuelle Öffnung darüber wechselt.
- Ein Short-Signal tritt auf, wenn die vorherige Öffnung über der Linie liegt und die aktuelle Öffnung darunter wechselt.
- Vertical-Linien funktionieren wie zeitgesteuerte Auslöser. Sobald die konfigurierte Anzahl von Bars abgelaufen ist, eröffnet die Strategie sofort eine Position zum aktuellen Kerzenöffnungspreis.
- Die Richtung wird gemäß Direction Mode aufgelöst:
FromLabel vergleicht jede Linienbezeichnung mit Buy Label und Sell Label.
ForceBuy und ForceSell behandeln alle Linien als dieselbe Richtung unabhängig von Bezeichnungen.
- Jeder erfolgreiche Auslöser sendet eine Marktorder mit dem Volumen aus Trade Volume, protokolliert die Aktivierung und markiert die Linie als inaktiv.
- Optionale Stop-Loss- und Take-Profit-Abstände werden auf jeder neuen Kerze angewendet, indem der letzte Einstiegspreis gegen Kerzenhochs und -tiefs bewertet wird.
Der Line Definitions-String verwendet Semikolons zur Trennung von Einträgen. Jeder Eintrag muss folgendes Format haben:
Name|Type|Label|BasePrice|SlopePerBar|Length|Ray
- Name – In Protokollen angezeigter Bezeichner. Beliebige Zeichenkette ohne Semikolons.
- Type –
Horizontal, Trend oder Vertical (Groß-/Kleinschreibung ignoriert).
- Label – Freitext, der verwendet wird, wenn Direction Mode
FromLabel ist.
- BasePrice – Anfangspreis der Linie bei der ersten verarbeiteten Kerze. Erforderlich für jede nicht-vertikale Linie (dezimal, invariante Kultur).
- SlopePerBar – Preisänderung pro Kerze für eine Trendlinie. Verwenden Sie
0 für horizontale Linien.
- Length – Bedeutung hängt vom Linientyp ab:
- Bei Trend- oder horizontalen Linien ohne Ray definiert es, wie viele Bars der rechte Anker vom Start entfernt ist. Nach diesem Zähler läuft die Linie automatisch ab.
- Bei Ray-Linien wird der Wert ignoriert, da die Linie unbegrenzt weitergeht.
- Bei vertikalen Linien gibt es an, wie viele Bars gewartet werden soll, bevor ausgelöst wird. Der minimale akzeptierte Wert ist
1.
- Ray –
true hält die Linie unbegrenzt nach rechts aktiv, false beschränkt sie auf die angegebene Länge.
Beispiel:
TrendLine|Trend|Buy|1.1000|0.0005|8|false;HorizontalSell|Horizontal|Sell|1.1050|0|0|true;VerticalImpulse|Vertical|Buy|0|0|1|false
Das Beispiel erstellt eine steigende Kauf-Trendlinie, ein horizontales Verkaufsniveau, das nie abläuft, und einen einmaligen vertikalen Auslöser für die nächste Kerze.
Parameter
- Candle Type – Marktdatentyp für Berechnungen. Standard: 1-Minuten-Zeitrahmen.
- Trade Volume – Ordergröße für neue Einstiege. Muss positiv sein.
- Direction Mode – Bestimmt, wie die Einstiegsseite ausgewählt wird (
FromLabel, ForceBuy, ForceSell).
- Buy Label / Sell Label – Bezeichnungswerte zur Identifizierung von Linien, wenn Direction Mode
FromLabel ist.
- Line Definitions – Roher String, der jede synthetische Linie beschreibt (siehe Format oben).
- Stop Loss Offset – Abstand in Preiseinheiten für Schutzausstiege bei Long- und Short-Positionen (0 deaktiviert die Prüfung).
- Take Profit Offset – Preisabstand für Gewinnziele (0 deaktiviert die Prüfung).
Risikomanagement
Die Strategie platziert keine separaten Stop- oder Take-Profit-Orders. Stattdessen überwacht sie jede abgeschlossene Kerze:
- Long-Positionen werden geschlossen, wenn das Kerzentief
EntryPrice - StopLossOffset unterschreitet oder das Hoch EntryPrice + TakeProfitOffset überschreitet.
- Short-Positionen werden geschlossen, wenn das Kerzenhoch
EntryPrice + StopLossOffset überschreitet oder das Tief unter EntryPrice - TakeProfitOffset fällt.
Wenn beide Offsets null sind, wird die Position nur durch das entgegengesetzte Signal oder manuelle Intervention geschlossen.
Implementierungshinweise
- Alle Kommentare im Quellcode sind auf Englisch, um die Konsistenz mit den Projektrichtlinien zu wahren.
- Die Strategie ignoriert ungültige Liniendefinitionen stillschweigend; stellen Sie sicher, dass das Format korrekt ist, um fehlende Auslöser zu vermeiden.
- Das Neustarten der Strategie löscht den internen Zustand, sodass Linienzähler und Aktivierungstimer bei der ersten verarbeiteten Kerze neu beginnen.
- Der Ansatz konzentriert sich auf Kerzenöffnungspreise, genau wie der ursprüngliche EA, und reagiert nicht auf Intrabar-Berührungen.
Verwendung
- Das Handelsinstrument und den gewünschten Kerzentyp konfigurieren.
- Line Definitions anpassen, um jede manuelle Linie zu beschreiben, gegen die gehandelt werden soll.
- Direction Mode so einstellen, dass entweder Bezeichnungen verwendet werden oder einseitiger Handel erzwungen wird.
- Optional Stop-Loss- und Take-Profit-Offsets für automatische Ausstiege festlegen.
- Die Strategie starten und die Protokolle überwachen: Jede ausgelöste Linie wird zusammen mit ihrer Richtung und dem Aktivierungspreis gemeldet.
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;
using System.Globalization;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that opens positions when price crosses predefined synthetic lines.
/// It replicates the idea of the original MQL Cross Line Trader by checking finished candles.
/// </summary>
public class CrossLineTraderStrategy : Strategy
{
public enum LineDirectionModes
{
FromLabel,
ForceBuy,
ForceSell,
}
private enum LineTypes
{
Horizontal,
Trend,
Vertical,
}
private enum TradeDirections
{
Buy,
Sell,
}
private sealed class LineState
{
public LineState(string name, string label, LineTypes type, decimal basePrice, decimal slopePerBar, int length, bool ray)
{
Name = name.IsEmptyOrWhiteSpace() ? type.ToString() : name;
Label = label ?? string.Empty;
Type = type;
BasePrice = basePrice;
SlopePerBar = slopePerBar;
Length = Math.Max(0, length);
Ray = ray;
IsActive = true;
StepsProcessed = 0;
}
public string Name { get; }
public string Label { get; }
public LineTypes Type { get; }
public decimal BasePrice { get; }
public decimal SlopePerBar { get; }
public int Length { get; }
public bool Ray { get; }
public bool IsActive { get; set; }
public int StepsProcessed { get; set; }
public decimal GetPrice(int index)
{
if (Type == LineTypes.Vertical)
return 0m;
var clampedIndex = Math.Max(0, index);
if (!Ray && Length > 0)
clampedIndex = Math.Min(clampedIndex, Length);
return BasePrice + SlopePerBar * clampedIndex;
}
}
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<LineDirectionModes> _directionMode;
private readonly StrategyParam<string> _buyLabel;
private readonly StrategyParam<string> _sellLabel;
private readonly StrategyParam<string> _lineDefinitions;
private readonly StrategyParam<decimal> _stopLossOffset;
private readonly StrategyParam<decimal> _takeProfitOffset;
private List<LineState> _lines = new();
private decimal? _previousOpen;
private decimal _entryPrice;
/// <summary>
/// Candle type used for line intersection checks.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Volume sent with market orders.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Defines how the strategy resolves trade direction for every line.
/// </summary>
public LineDirectionModes DirectionMode
{
get => _directionMode.Value;
set => _directionMode.Value = value;
}
/// <summary>
/// Text label that identifies buy lines when DirectionMode is FromLabel.
/// </summary>
public string BuyLabel
{
get => _buyLabel.Value;
set => _buyLabel.Value = value;
}
/// <summary>
/// Text label that identifies sell lines when DirectionMode is FromLabel.
/// </summary>
public string SellLabel
{
get => _sellLabel.Value;
set => _sellLabel.Value = value;
}
/// <summary>
/// Raw definition of synthetic lines. Each line uses the format:
/// Name|Type|Label|BasePrice|SlopePerBar|Length|Ray
/// </summary>
public string LineDefinitions
{
get => _lineDefinitions.Value;
set => _lineDefinitions.Value = value;
}
/// <summary>
/// Protective stop distance in price units.
/// </summary>
public decimal StopLossOffset
{
get => _stopLossOffset.Value;
set => _stopLossOffset.Value = value;
}
/// <summary>
/// Protective take profit distance in price units.
/// </summary>
public decimal TakeProfitOffset
{
get => _takeProfitOffset.Value;
set => _takeProfitOffset.Value = value;
}
/// <summary>
/// Constructor that configures strategy parameters.
/// </summary>
public CrossLineTraderStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles used for intersections", "Data");
_tradeVolume = Param(nameof(TradeVolume), 1m)
.SetDisplay("Trade Volume", "Order volume used for entries", "Trading")
.SetGreaterThanZero();
_directionMode = Param(nameof(DirectionMode), LineDirectionModes.FromLabel)
.SetDisplay("Direction Mode", "How to pick trade direction for a line", "Trading");
_buyLabel = Param(nameof(BuyLabel), "Buy")
.SetDisplay("Buy Label", "Text that marks buy lines when mode uses labels", "Trading");
_sellLabel = Param(nameof(SellLabel), "Sell")
.SetDisplay("Sell Label", "Text that marks sell lines when mode uses labels", "Trading");
_lineDefinitions = Param(nameof(LineDefinitions),
"TrendLine|Trend|Buy|64000|50|20|false;HorizontalSell|Horizontal|Sell|68000|0|0|true;HorizontalBuy|Horizontal|Buy|62000|0|0|true")
.SetDisplay("Line Definitions", "Encoded collection of synthetic lines", "Lines")
;
_stopLossOffset = Param(nameof(StopLossOffset), 0m)
.SetDisplay("Stop Loss Offset", "Price distance for protective exit", "Risk")
.SetNotNegative();
_takeProfitOffset = Param(nameof(TakeProfitOffset), 0m)
.SetDisplay("Take Profit Offset", "Price distance for profit taking", "Risk")
.SetNotNegative();
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_lines = new List<LineState>();
_previousOpen = null;
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_lines = ParseLineDefinitions(LineDefinitions);
_previousOpen = null;
_entryPrice = 0m;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (TradeVolume <= 0)
return;
var currentOpen = candle.OpenPrice;
foreach (var line in _lines)
{
if (!line.IsActive)
continue;
var previousIndex = line.StepsProcessed;
var currentIndex = previousIndex + 1;
if (line.Type == LineTypes.Vertical)
{
if (currentIndex >= Math.Max(1, line.Length))
{
var direction = ResolveDirection(line);
if (direction != null && TryOpenPosition(direction.Value, line, candle))
line.IsActive = false;
}
line.StepsProcessed = currentIndex;
continue;
}
if (!line.Ray && line.Length > 0 && previousIndex >= line.Length)
{
line.IsActive = false;
continue;
}
if (_previousOpen is null)
{
line.StepsProcessed = currentIndex;
continue;
}
var previousLinePrice = line.GetPrice(previousIndex);
var currentLinePrice = line.GetPrice(currentIndex);
var directionForLine = ResolveDirection(line);
if (directionForLine is null)
{
line.StepsProcessed = currentIndex;
continue;
}
var crossUp = line.Type == LineTypes.Horizontal
? _previousOpen.Value <= previousLinePrice && currentOpen > previousLinePrice
: _previousOpen.Value <= previousLinePrice && currentOpen > currentLinePrice;
var crossDown = line.Type == LineTypes.Horizontal
? _previousOpen.Value >= previousLinePrice && currentOpen < previousLinePrice
: _previousOpen.Value >= previousLinePrice && currentOpen < currentLinePrice;
if (crossUp && directionForLine == TradeDirections.Buy && Position <= 0)
{
if (TryOpenPosition(TradeDirections.Buy, line, candle))
line.IsActive = false;
}
else if (crossDown && directionForLine == TradeDirections.Sell && Position >= 0)
{
if (TryOpenPosition(TradeDirections.Sell, line, candle))
line.IsActive = false;
}
line.StepsProcessed = currentIndex;
if (!line.Ray && line.Length > 0 && currentIndex >= line.Length)
line.IsActive = false;
}
ManageProtectiveExits(candle);
_previousOpen = currentOpen;
}
private TradeDirections? ResolveDirection(LineState line)
{
switch (DirectionMode)
{
case LineDirectionModes.ForceBuy:
return TradeDirections.Buy;
case LineDirectionModes.ForceSell:
return TradeDirections.Sell;
case LineDirectionModes.FromLabel:
if (!BuyLabel.IsEmptyOrWhiteSpace() &&
line.Label.EqualsIgnoreCase(BuyLabel))
return TradeDirections.Buy;
if (!SellLabel.IsEmptyOrWhiteSpace() &&
line.Label.EqualsIgnoreCase(SellLabel))
return TradeDirections.Sell;
break;
}
return null;
}
private bool TryOpenPosition(TradeDirections direction, LineState line, ICandleMessage candle)
{
if (direction == TradeDirections.Buy)
{
if (Position > 0)
return false;
BuyMarket(TradeVolume);
_entryPrice = candle.OpenPrice;
// BUY triggered
}
else
{
if (Position < 0)
return false;
SellMarket(TradeVolume);
_entryPrice = candle.OpenPrice;
// SELL triggered
}
return true;
}
private void ManageProtectiveExits(ICandleMessage candle)
{
if (Position > 0)
{
var volume = Math.Abs(Position);
if (StopLossOffset > 0m && candle.LowPrice <= _entryPrice - StopLossOffset)
{
SellMarket(volume);
return;
}
if (TakeProfitOffset > 0m && candle.HighPrice >= _entryPrice + TakeProfitOffset)
{
SellMarket(volume);
}
}
else if (Position < 0)
{
var volume = Math.Abs(Position);
if (StopLossOffset > 0m && candle.HighPrice >= _entryPrice + StopLossOffset)
{
BuyMarket(volume);
return;
}
if (TakeProfitOffset > 0m && candle.LowPrice <= _entryPrice - TakeProfitOffset)
{
BuyMarket(volume);
}
}
}
private List<LineState> ParseLineDefinitions(string raw)
{
var result = new List<LineState>();
if (raw.IsEmptyOrWhiteSpace())
return result;
var entries = raw.Split(new[] { '\n', '\r', ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var entry in entries)
{
var parts = entry.Split('|');
if (parts.Length < 7)
continue;
var name = parts[0].Trim();
var typeText = parts[1].Trim();
var label = parts[2].Trim();
var basePriceText = parts[3].Trim();
var slopeText = parts[4].Trim();
var lengthText = parts[5].Trim();
var rayText = parts[6].Trim();
if (!Enum.TryParse<LineTypes>(typeText, true, out var type))
continue;
if (!decimal.TryParse(basePriceText, NumberStyles.Number, CultureInfo.InvariantCulture, out var basePrice))
continue;
if (!decimal.TryParse(slopeText, NumberStyles.Number, CultureInfo.InvariantCulture, out var slope))
slope = 0m;
if (!int.TryParse(lengthText, NumberStyles.Integer, CultureInfo.InvariantCulture, out var length))
length = 0;
if (!bool.TryParse(rayText, out var ray))
ray = false;
if (type == LineTypes.Vertical && length <= 0)
length = 1;
result.Add(new LineState(name, label, type, basePrice, slope, length, ray));
}
return result;
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (Position == 0)
{
_entryPrice = 0m;
return;
}
_entryPrice = trade.Trade.Price;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class cross_line_trader_strategy(Strategy):
DIR_FROM_LABEL = 0
DIR_FORCE_BUY = 1
DIR_FORCE_SELL = 2
LINE_HORIZONTAL = 0
LINE_TREND = 1
LINE_VERTICAL = 2
def __init__(self):
super(cross_line_trader_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._trade_volume = self.Param("TradeVolume", 1.0)
self._direction_mode = self.Param("DirectionMode", self.DIR_FROM_LABEL)
self._buy_label = self.Param("BuyLabel", "Buy")
self._sell_label = self.Param("SellLabel", "Sell")
self._line_definitions = self.Param("LineDefinitions",
"TrendLine|Trend|Buy|64000|50|20|false;HorizontalSell|Horizontal|Sell|68000|0|0|true;HorizontalBuy|Horizontal|Buy|62000|0|0|true")
self._stop_loss_offset = self.Param("StopLossOffset", 0.0)
self._take_profit_offset = self.Param("TakeProfitOffset", 0.0)
self._lines = []
self._previous_open = None
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def TradeVolume(self):
return self._trade_volume.Value
@property
def DirectionMode(self):
return self._direction_mode.Value
@property
def BuyLabel(self):
return self._buy_label.Value
@property
def SellLabel(self):
return self._sell_label.Value
@property
def LineDefinitions(self):
return self._line_definitions.Value
@property
def StopLossOffset(self):
return self._stop_loss_offset.Value
@property
def TakeProfitOffset(self):
return self._take_profit_offset.Value
def OnStarted2(self, time):
super(cross_line_trader_strategy, self).OnStarted2(time)
self._lines = self._parse_line_definitions(self.LineDefinitions)
self._previous_open = None
self._entry_price = 0.0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def OnOwnTradeReceived(self, trade):
super(cross_line_trader_strategy, self).OnOwnTradeReceived(trade)
if trade is None or trade.Trade is None:
return
pos = float(self.Position)
if pos == 0:
self._entry_price = 0.0
return
self._entry_price = float(trade.Trade.Price)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if float(self.TradeVolume) <= 0:
return
current_open = float(candle.OpenPrice)
for line in self._lines:
if not line["is_active"]:
continue
previous_index = line["steps_processed"]
current_index = previous_index + 1
if line["type"] == self.LINE_VERTICAL:
if current_index >= max(1, line["length"]):
direction = self._resolve_direction(line)
if direction is not None and self._try_open_position(direction, candle):
line["is_active"] = False
line["steps_processed"] = current_index
continue
if not line["ray"] and line["length"] > 0 and previous_index >= line["length"]:
line["is_active"] = False
continue
if self._previous_open is None:
line["steps_processed"] = current_index
continue
prev_line_price = self._get_line_price(line, previous_index)
curr_line_price = self._get_line_price(line, current_index)
dir_for_line = self._resolve_direction(line)
if dir_for_line is None:
line["steps_processed"] = current_index
continue
if line["type"] == self.LINE_HORIZONTAL:
cross_up = self._previous_open <= prev_line_price and current_open > prev_line_price
cross_down = self._previous_open >= prev_line_price and current_open < prev_line_price
else:
cross_up = self._previous_open <= prev_line_price and current_open > curr_line_price
cross_down = self._previous_open >= prev_line_price and current_open < curr_line_price
pos = float(self.Position)
if cross_up and dir_for_line == 1 and pos <= 0:
if self._try_open_position(1, candle):
line["is_active"] = False
elif cross_down and dir_for_line == -1 and pos >= 0:
if self._try_open_position(-1, candle):
line["is_active"] = False
line["steps_processed"] = current_index
if not line["ray"] and line["length"] > 0 and current_index >= line["length"]:
line["is_active"] = False
self._manage_protective_exits(candle)
self._previous_open = current_open
def _resolve_direction(self, line):
mode = self.DirectionMode
if mode == self.DIR_FORCE_BUY:
return 1
elif mode == self.DIR_FORCE_SELL:
return -1
elif mode == self.DIR_FROM_LABEL:
buy_label = self.BuyLabel
sell_label = self.SellLabel
label = line["label"]
if buy_label and label.lower() == buy_label.lower():
return 1
if sell_label and label.lower() == sell_label.lower():
return -1
return None
def _try_open_position(self, direction, candle):
pos = float(self.Position)
if direction == 1:
if pos > 0:
return False
self.BuyMarket(float(self.TradeVolume))
self._entry_price = float(candle.OpenPrice)
else:
if pos < 0:
return False
self.SellMarket(float(self.TradeVolume))
self._entry_price = float(candle.OpenPrice)
return True
def _manage_protective_exits(self, candle):
pos = float(self.Position)
if pos > 0:
volume = abs(pos)
if float(self.StopLossOffset) > 0 and float(candle.LowPrice) <= self._entry_price - float(self.StopLossOffset):
self.SellMarket(volume)
return
if float(self.TakeProfitOffset) > 0 and float(candle.HighPrice) >= self._entry_price + float(self.TakeProfitOffset):
self.SellMarket(volume)
elif pos < 0:
volume = abs(pos)
if float(self.StopLossOffset) > 0 and float(candle.HighPrice) >= self._entry_price + float(self.StopLossOffset):
self.BuyMarket(volume)
return
if float(self.TakeProfitOffset) > 0 and float(candle.LowPrice) <= self._entry_price - float(self.TakeProfitOffset):
self.BuyMarket(volume)
def _get_line_price(self, line, index):
if line["type"] == self.LINE_VERTICAL:
return 0.0
clamped = max(0, index)
if not line["ray"] and line["length"] > 0:
clamped = min(clamped, line["length"])
return line["base_price"] + line["slope"] * clamped
def _parse_line_definitions(self, raw):
result = []
if raw is None or raw.strip() == "":
return result
entries = raw.replace("\n", ";").replace("\r", ";").split(";")
for entry in entries:
entry = entry.strip()
if not entry:
continue
parts = entry.split("|")
if len(parts) < 7:
continue
name = parts[0].strip()
type_text = parts[1].strip().lower()
label = parts[2].strip()
try:
base_price = float(parts[3].strip())
except:
continue
try:
slope = float(parts[4].strip())
except:
slope = 0.0
try:
length = int(parts[5].strip())
except:
length = 0
ray_text = parts[6].strip().lower()
ray = ray_text == "true"
if type_text == "horizontal":
line_type = self.LINE_HORIZONTAL
elif type_text == "trend":
line_type = self.LINE_TREND
elif type_text == "vertical":
line_type = self.LINE_VERTICAL
else:
continue
if line_type == self.LINE_VERTICAL and length <= 0:
length = 1
result.append({
"name": name if name else type_text,
"label": label,
"type": line_type,
"base_price": base_price,
"slope": slope,
"length": max(0, length),
"ray": ray,
"is_active": True,
"steps_processed": 0
})
return result
def OnReseted(self):
super(cross_line_trader_strategy, self).OnReseted()
self._lines = []
self._previous_open = None
self._entry_price = 0.0
def CreateClone(self):
return cross_line_trader_strategy()