Estrategia de Absorción
La estrategia replica el asesor experto Absorption para MetaTrader. Busca velas "envolventes" que absorben el rango de la barra anterior y forman un extremo dentro de un corto período de búsqueda. Cuando aparece dicha barra de absorción, el algoritmo coloca órdenes stop a ambos lados del mercado y gestiona la posición resultante con una combinación de objetivos fijos, lógica de breakeven y un trailing stop.
Lógica de Trading
- Detección de patrones
- Se inspeccionan las últimas dos velas completadas.
- Se trata una vela como barra de absorción cuando su máximo está por encima del máximo de la vela anterior y su mínimo está por debajo del mínimo de la vela anterior.
- Se valida la barra comprobando si su máximo o mínimo es el valor más extremo dentro de las últimas
MaxSearchvelas. - Se da prioridad a la vela más antigua (dos barras atrás). Si ambas barras satisfacen la condición de absorción, se usa la barra más antigua; de lo contrario, la barra más reciente puede activar la configuración.
- Colocación de órdenes
- Se coloca una orden de compra stop en el máximo de la barra más el
Indentconfigurado. - Se coloca una orden de venta stop en el mínimo de la barra menos el mismo
Indent. - Ambas órdenes usan el volumen de estrategia común.
- Cada orden pendiente almacena su propio nivel de stop protector y objetivo de take profit opcional. Las órdenes expiran automáticamente después de
OrderExpirationHourssi permanecen sin ejecutar.
- Se coloca una orden de compra stop en el máximo de la barra más el
- Gestión de posiciones
- Cuando se ejecuta un lado, la orden pendiente opuesta se cancela.
- El stop inicial se sitúa en el lado opuesto de la vela de absorción menos/más el indent.
- Un take profit fijo opcional cierra la operación una vez alcanzada la distancia configurada en pasos de precio.
- El módulo de breakeven mueve el stop-loss a
Entrada + Breakeven(largo) oEntrada - Breakeven(corto) después de que el precio avanzaBreakevenProfitpasos. - El trailing stop mantiene el stop-loss a distancia
TrailingStopdel mejor precio, actualizándose solo cuando el precio se mueve al menosTrailingSteppasos en la dirección rentable.
Parámetros
| Parámetro | Descripción |
|---|---|
CandleType |
Tipo de datos de vela para suscribirse (por defecto: marco temporal de 1 hora). |
MaxSearch |
Número de velas recientes usadas para confirmar extremos altos/bajos. |
TakeProfitBuy |
Distancia en pasos de precio para la orden de take profit largo. 0 deshabilita el objetivo. |
TakeProfitSell |
Distancia en pasos de precio para la orden de take profit corto. 0 deshabilita el objetivo. |
TrailingStop |
Distancia del trailing stop en pasos de precio. 0 deshabilita el trailing. |
TrailingStep |
Movimiento mínimo adelante requerido antes de avanzar el trailing stop. Debe ser positivo cuando el trailing está habilitado. |
Indent |
Desplazamiento en pasos de precio que se añade por encima/debajo de la barra de absorción para definir los niveles de entrada stop. |
OrderExpirationHours |
Tiempo de vida de las órdenes pendientes. Después de este período las órdenes se cancelan si no se activan. |
Breakeven |
Desplazamiento aplicado al stop-loss cuando se activa la regla de breakeven. 0 deshabilita el breakeven. |
BreakevenProfit |
Umbral de beneficio (en pasos de precio) que debe alcanzarse antes de mover el stop-loss a breakeven. |
Todas las entradas basadas en distancia se expresan como múltiplos del paso de precio del instrumento. El volumen de estrategia predeterminado se establece en 0.1.
Gestión de Riesgo
La estrategia usa solo órdenes de mercado para las salidas. Las reglas de stop-loss, take-profit, breakeven y trailing monitorean los máximos y mínimos de las velas para detectar tocamientos de nivel dentro de la barra. Una vez que se envía una orden de salida, no se generan solicitudes de salida adicionales hasta que la posición actual esté plana.
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>
/// Absorption outside bar breakout strategy.
/// Detects engulfing candles near recent extremes and places stop orders around the pattern.
/// </summary>
public class AbsorptionStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _maxSearch;
private readonly StrategyParam<decimal> _takeProfitBuy;
private readonly StrategyParam<decimal> _takeProfitSell;
private readonly StrategyParam<decimal> _trailingStop;
private readonly StrategyParam<decimal> _trailingStep;
private readonly StrategyParam<decimal> _indent;
private readonly StrategyParam<int> _orderExpirationHours;
private readonly StrategyParam<decimal> _breakeven;
private readonly StrategyParam<decimal> _breakevenProfit;
private Highest _highest;
private Lowest _lowest;
private ICandleMessage _prev1;
private ICandleMessage _prev2;
private bool _hasActiveOrders;
private decimal _pendingHigh;
private decimal _pendingLow;
private decimal _pendingBuyPrice;
private decimal _pendingSellPrice;
private decimal _pendingBuyStopLoss;
private decimal _pendingSellStopLoss;
private decimal _pendingBuyTakeProfit;
private decimal _pendingSellTakeProfit;
private DateTimeOffset? _ordersExpiry;
private decimal _entryPrice;
private decimal _stopLoss;
private decimal _takeProfit;
private decimal _prevPosition;
private bool _exitRequestActive;
/// <summary>
/// Candle type to analyze.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Number of candles to inspect for extreme prices.
/// </summary>
public int MaxSearch
{
get => _maxSearch.Value;
set => _maxSearch.Value = value;
}
/// <summary>
/// Take profit distance for long trades in price steps.
/// </summary>
public decimal TakeProfitBuy
{
get => _takeProfitBuy.Value;
set => _takeProfitBuy.Value = value;
}
/// <summary>
/// Take profit distance for short trades in price steps.
/// </summary>
public decimal TakeProfitSell
{
get => _takeProfitSell.Value;
set => _takeProfitSell.Value = value;
}
/// <summary>
/// Trailing stop distance in price steps.
/// </summary>
public decimal TrailingStop
{
get => _trailingStop.Value;
set => _trailingStop.Value = value;
}
/// <summary>
/// Minimal step for trailing stop updates in price steps.
/// </summary>
public decimal TrailingStep
{
get => _trailingStep.Value;
set => _trailingStep.Value = value;
}
/// <summary>
/// Indent distance around the reference candle in price steps.
/// </summary>
public decimal Indent
{
get => _indent.Value;
set => _indent.Value = value;
}
/// <summary>
/// Pending order expiration in hours.
/// </summary>
public int OrderExpirationHours
{
get => _orderExpirationHours.Value;
set => _orderExpirationHours.Value = value;
}
/// <summary>
/// Distance to move stop-loss to breakeven in price steps.
/// </summary>
public decimal Breakeven
{
get => _breakeven.Value;
set => _breakeven.Value = value;
}
/// <summary>
/// Profit needed before breakeven activation in price steps.
/// </summary>
public decimal BreakevenProfit
{
get => _breakevenProfit.Value;
set => _breakevenProfit.Value = value;
}
/// <summary>
/// Initializes <see cref="AbsorptionStrategy"/>.
/// </summary>
public AbsorptionStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to process", "General");
_maxSearch = Param(nameof(MaxSearch), 10)
.SetGreaterThanZero()
.SetDisplay("Search Depth", "Bars to inspect for extremes", "Pattern");
_takeProfitBuy = Param(nameof(TakeProfitBuy), 10m)
.SetNotNegative()
.SetDisplay("Long TP", "Take profit for long trades (steps)", "Risk");
_takeProfitSell = Param(nameof(TakeProfitSell), 10m)
.SetNotNegative()
.SetDisplay("Short TP", "Take profit for short trades (steps)", "Risk");
_trailingStop = Param(nameof(TrailingStop), 5m)
.SetNotNegative()
.SetDisplay("Trailing Stop", "Trailing stop distance (steps)", "Risk");
_trailingStep = Param(nameof(TrailingStep), 5m)
.SetNotNegative()
.SetDisplay("Trailing Step", "Minimal move to update trailing stop (steps)", "Risk");
_indent = Param(nameof(Indent), 1m)
.SetNotNegative()
.SetDisplay("Indent", "Offset from high/low for entries (steps)", "Pattern");
_orderExpirationHours = Param(nameof(OrderExpirationHours), 8)
.SetGreaterThanZero()
.SetDisplay("Order Expiration", "Validity of pending orders in hours", "Pattern");
_breakeven = Param(nameof(Breakeven), 1m)
.SetNotNegative()
.SetDisplay("Breakeven", "Stop offset once breakeven triggers (steps)", "Risk");
_breakevenProfit = Param(nameof(BreakevenProfit), 10m)
.SetNotNegative()
.SetDisplay("Breakeven Profit", "Profit needed before moving to breakeven (steps)", "Risk");
Volume = 0.1m;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prev1 = null;
_prev2 = null;
_hasActiveOrders = false;
_ordersExpiry = null;
_pendingHigh = 0m;
_pendingLow = 0m;
_pendingBuyPrice = 0m;
_pendingSellPrice = 0m;
_pendingBuyStopLoss = 0m;
_pendingSellStopLoss = 0m;
_pendingBuyTakeProfit = 0m;
_pendingSellTakeProfit = 0m;
_entryPrice = 0m;
_stopLoss = 0m;
_takeProfit = 0m;
_prevPosition = 0m;
_exitRequestActive = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (TrailingStop > 0m && TrailingStep <= 0m)
throw new InvalidOperationException("Trailing step must be positive when trailing stop is enabled.");
if (Breakeven > 0m)
{
if (BreakevenProfit <= 0m)
throw new InvalidOperationException("Breakeven profit must be positive when breakeven is enabled.");
if (BreakevenProfit <= Breakeven)
throw new InvalidOperationException("Breakeven profit must exceed breakeven distance.");
}
_highest = new Highest { Length = MaxSearch };
_lowest = new Lowest { Length = MaxSearch };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandleRaw)
.Start();
}
private void ProcessCandleRaw(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var highResult = _highest.Process(candle);
var lowResult = _lowest.Process(candle);
if (highResult.IsEmpty || lowResult.IsEmpty || !_highest.IsFormed || !_lowest.IsFormed)
{
UpdatePreviousCandles(candle);
_prevPosition = Position;
return;
}
var highestValue = highResult.ToDecimal();
var lowestValue = lowResult.ToDecimal();
ManageActivePosition(candle);
// Check if pending breakout orders should be triggered
if (_hasActiveOrders)
{
if (_ordersExpiry.HasValue && candle.CloseTime >= _ordersExpiry.Value)
{
ClearPendingOrders();
}
else
{
TryTriggerPendingOrders(candle);
}
}
if (Position == 0 && !_hasActiveOrders && _prev1 != null && _prev2 != null)
{
TryPlaceOrders(candle, highestValue, lowestValue);
}
UpdatePreviousCandles(candle);
if (Position != 0 && _hasActiveOrders)
{
ClearPendingOrders();
}
_prevPosition = Position;
}
private void TryTriggerPendingOrders(ICandleMessage candle)
{
if (Position != 0)
return;
// Check if price has broken above the pending buy level
if (_pendingBuyPrice > 0 && candle.HighPrice >= _pendingBuyPrice)
{
BuyMarket(Volume);
_entryPrice = _pendingBuyPrice;
_stopLoss = _pendingBuyStopLoss;
_takeProfit = _pendingBuyTakeProfit;
_exitRequestActive = false;
ClearPendingOrders();
return;
}
// Check if price has broken below the pending sell level
if (_pendingSellPrice > 0 && candle.LowPrice <= _pendingSellPrice)
{
SellMarket(Volume);
_entryPrice = _pendingSellPrice;
_stopLoss = _pendingSellStopLoss;
_takeProfit = _pendingSellTakeProfit;
_exitRequestActive = false;
ClearPendingOrders();
}
}
private void TryPlaceOrders(ICandleMessage candle, decimal highestValue, decimal lowestValue)
{
var prev2Outside = _prev2.HighPrice > _prev1.HighPrice && _prev2.LowPrice < _prev1.LowPrice;
var prev1Outside = _prev1.HighPrice > _prev2.HighPrice && _prev1.LowPrice < _prev2.LowPrice;
var prev2IsExtreme = IsLowestBar(_prev2, _prev1, candle, lowestValue) || IsHighestBar(_prev2, _prev1, candle, highestValue);
var prev1IsExtreme = IsLowestBar(_prev1, _prev2, candle, lowestValue) || IsHighestBar(_prev1, _prev2, candle, highestValue);
if (prev2Outside && prev2IsExtreme)
{
PlaceEntryOrders(_prev2, candle);
}
else if (prev1Outside && prev1IsExtreme)
{
PlaceEntryOrders(_prev1, candle);
}
}
private void PlaceEntryOrders(ICandleMessage patternCandle, ICandleMessage currentCandle)
{
var volume = Volume;
if (volume <= 0m)
return;
var indent = GetPriceOffset(Indent);
var step = Security?.PriceStep ?? 0.0001m;
var buyPrice = patternCandle.HighPrice + indent;
var sellPrice = patternCandle.LowPrice - indent;
if (sellPrice <= 0m)
sellPrice = step;
var buyStopLoss = Math.Max(patternCandle.LowPrice - indent, step);
var sellStopLoss = patternCandle.HighPrice + indent;
var buyTakeOffset = GetPriceOffset(TakeProfitBuy);
var sellTakeOffset = GetPriceOffset(TakeProfitSell);
var buyTakeProfit = buyTakeOffset > 0m ? buyPrice + buyTakeOffset : 0m;
var sellTakeProfit = sellTakeOffset > 0m ? sellPrice - sellTakeOffset : 0m;
// Store pending breakout levels (will be triggered on next candle)
_hasActiveOrders = true;
_pendingHigh = patternCandle.HighPrice;
_pendingLow = patternCandle.LowPrice;
_pendingBuyPrice = buyPrice;
_pendingSellPrice = sellPrice;
_pendingBuyStopLoss = buyStopLoss;
_pendingSellStopLoss = sellStopLoss;
_pendingBuyTakeProfit = buyTakeProfit;
_pendingSellTakeProfit = sellTakeProfit;
_exitRequestActive = false;
_ordersExpiry = OrderExpirationHours > 0
? currentCandle.CloseTime + TimeSpan.FromHours(OrderExpirationHours)
: null;
}
private void ManageActivePosition(ICandleMessage candle)
{
if (_exitRequestActive)
return;
if (Position > 0)
{
UpdateBreakevenLong(candle);
UpdateTrailingLong(candle);
if (_stopLoss > 0m && candle.LowPrice <= _stopLoss)
{
SellMarket(Math.Abs(Position));
_exitRequestActive = true;
return;
}
if (_takeProfit > 0m && candle.HighPrice >= _takeProfit)
{
SellMarket(Math.Abs(Position));
_exitRequestActive = true;
}
}
else if (Position < 0)
{
UpdateBreakevenShort(candle);
UpdateTrailingShort(candle);
if (_stopLoss > 0m && candle.HighPrice >= _stopLoss)
{
BuyMarket(Math.Abs(Position));
_exitRequestActive = true;
return;
}
if (_takeProfit > 0m && candle.LowPrice <= _takeProfit)
{
BuyMarket(Math.Abs(Position));
_exitRequestActive = true;
}
}
}
private void UpdateBreakevenLong(ICandleMessage candle)
{
if (Breakeven <= 0m || BreakevenProfit <= 0m)
return;
if (_stopLoss >= _entryPrice + GetPriceOffset(Breakeven))
return;
if (candle.HighPrice - _entryPrice >= GetPriceOffset(BreakevenProfit))
_stopLoss = _entryPrice + GetPriceOffset(Breakeven);
}
private void UpdateBreakevenShort(ICandleMessage candle)
{
if (Breakeven <= 0m || BreakevenProfit <= 0m)
return;
if (_stopLoss <= _entryPrice - GetPriceOffset(Breakeven))
return;
if (_entryPrice - candle.LowPrice >= GetPriceOffset(BreakevenProfit))
_stopLoss = _entryPrice - GetPriceOffset(Breakeven);
}
private void UpdateTrailingLong(ICandleMessage candle)
{
if (TrailingStop <= 0m)
return;
var trailing = GetPriceOffset(TrailingStop);
var step = GetPriceOffset(TrailingStep);
var current = candle.HighPrice;
if (current - _entryPrice <= trailing + step)
return;
if (_stopLoss < current - (trailing + step))
_stopLoss = Math.Max(_stopLoss, current - trailing);
}
private void UpdateTrailingShort(ICandleMessage candle)
{
if (TrailingStop <= 0m)
return;
var trailing = GetPriceOffset(TrailingStop);
var step = GetPriceOffset(TrailingStep);
var current = candle.LowPrice;
if (_entryPrice - current <= trailing + step)
return;
if (_stopLoss == 0m || _stopLoss > current + trailing + step)
_stopLoss = current + trailing;
}
private void UpdatePreviousCandles(ICandleMessage candle)
{
_prev2 = _prev1;
_prev1 = candle;
}
private void ClearPendingOrders()
{
_hasActiveOrders = false;
_ordersExpiry = null;
_pendingHigh = 0m;
_pendingLow = 0m;
_pendingBuyPrice = 0m;
_pendingSellPrice = 0m;
_pendingBuyStopLoss = 0m;
_pendingSellStopLoss = 0m;
_pendingBuyTakeProfit = 0m;
_pendingSellTakeProfit = 0m;
}
private bool IsLowestBar(ICandleMessage candidate, ICandleMessage other, ICandleMessage current, decimal lowestValue)
{
if (!AreClose(candidate.LowPrice, lowestValue))
return false;
return candidate.LowPrice < other.LowPrice && candidate.LowPrice < current.LowPrice;
}
private bool IsHighestBar(ICandleMessage candidate, ICandleMessage other, ICandleMessage current, decimal highestValue)
{
if (!AreClose(candidate.HighPrice, highestValue))
return false;
return candidate.HighPrice > other.HighPrice && candidate.HighPrice > current.HighPrice;
}
private decimal GetPriceOffset(decimal value)
{
var step = Security?.PriceStep ?? 0.0001m;
return value * step;
}
private bool AreClose(decimal first, decimal second)
{
var tolerance = (Security?.PriceStep ?? 0.0001m) / 2m;
return Math.Abs(first - second) <= tolerance;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Indicators import Highest, Lowest, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan, Math
class absorption_strategy(Strategy):
def __init__(self):
super(absorption_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._max_search = self.Param("MaxSearch", 10)
self._take_profit_buy = self.Param("TakeProfitBuy", 10.0)
self._take_profit_sell = self.Param("TakeProfitSell", 10.0)
self._trailing_stop = self.Param("TrailingStop", 5.0)
self._trailing_step = self.Param("TrailingStep", 5.0)
self._indent = self.Param("Indent", 1.0)
self._order_expiration_hours = self.Param("OrderExpirationHours", 8)
self._breakeven = self.Param("Breakeven", 1.0)
self._breakeven_profit = self.Param("BreakevenProfit", 10.0)
self._highest = None
self._lowest = None
self._prev1 = None
self._prev2 = None
self._has_active_orders = False
self._pending_high = 0.0
self._pending_low = 0.0
self._pending_buy_price = 0.0
self._pending_sell_price = 0.0
self._pending_buy_stop_loss = 0.0
self._pending_sell_stop_loss = 0.0
self._pending_buy_take_profit = 0.0
self._pending_sell_take_profit = 0.0
self._orders_expiry = None
self._entry_price = 0.0
self._stop_loss = 0.0
self._take_profit = 0.0
self._prev_position = 0.0
self._exit_request_active = False
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(absorption_strategy, self).OnStarted2(time)
self._highest = Highest()
self._highest.Length = self._max_search.Value
self._lowest = Lowest()
self._lowest.Length = self._max_search.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
civ_h = CandleIndicatorValue(self._highest, candle)
civ_h.IsFinal = True
high_result = self._highest.Process(civ_h)
civ_l = CandleIndicatorValue(self._lowest, candle)
civ_l.IsFinal = True
low_result = self._lowest.Process(civ_l)
if high_result.IsEmpty or low_result.IsEmpty or not self._highest.IsFormed or not self._lowest.IsFormed:
self._update_previous_candles(candle)
self._prev_position = self.Position
return
highest_value = float(high_result.Value)
lowest_value = float(low_result.Value)
self._manage_active_position(candle)
if self._has_active_orders:
if self._orders_expiry is not None and candle.CloseTime >= self._orders_expiry:
self._clear_pending_orders()
else:
self._try_trigger_pending_orders(candle)
if self.Position == 0 and not self._has_active_orders and self._prev1 is not None and self._prev2 is not None:
self._try_place_orders(candle, highest_value, lowest_value)
self._update_previous_candles(candle)
if self.Position != 0 and self._has_active_orders:
self._clear_pending_orders()
self._prev_position = self.Position
def _try_trigger_pending_orders(self, candle):
if self.Position != 0:
return
if self._pending_buy_price > 0 and float(candle.HighPrice) >= self._pending_buy_price:
self.BuyMarket(self.Volume)
self._entry_price = self._pending_buy_price
self._stop_loss = self._pending_buy_stop_loss
self._take_profit = self._pending_buy_take_profit
self._exit_request_active = False
self._clear_pending_orders()
return
if self._pending_sell_price > 0 and float(candle.LowPrice) <= self._pending_sell_price:
self.SellMarket(self.Volume)
self._entry_price = self._pending_sell_price
self._stop_loss = self._pending_sell_stop_loss
self._take_profit = self._pending_sell_take_profit
self._exit_request_active = False
self._clear_pending_orders()
def _try_place_orders(self, candle, highest_value, lowest_value):
prev2_high = float(self._prev2.HighPrice)
prev2_low = float(self._prev2.LowPrice)
prev1_high = float(self._prev1.HighPrice)
prev1_low = float(self._prev1.LowPrice)
cur_high = float(candle.HighPrice)
cur_low = float(candle.LowPrice)
prev2_outside = prev2_high > prev1_high and prev2_low < prev1_low
prev1_outside = prev1_high > prev2_high and prev1_low < prev2_low
prev2_is_extreme = (self._is_lowest_bar(prev2_low, prev1_low, cur_low, lowest_value) or
self._is_highest_bar(prev2_high, prev1_high, cur_high, highest_value))
prev1_is_extreme = (self._is_lowest_bar(prev1_low, prev2_low, cur_low, lowest_value) or
self._is_highest_bar(prev1_high, prev2_high, cur_high, highest_value))
if prev2_outside and prev2_is_extreme:
self._place_entry_orders(self._prev2, candle)
elif prev1_outside and prev1_is_extreme:
self._place_entry_orders(self._prev1, candle)
def _place_entry_orders(self, pattern_candle, current_candle):
volume = float(self.Volume)
if volume <= 0:
return
indent = self._get_price_offset(self._indent.Value)
step = self._get_price_step()
buy_price = float(pattern_candle.HighPrice) + indent
sell_price = float(pattern_candle.LowPrice) - indent
if sell_price <= 0:
sell_price = step
buy_stop_loss = max(float(pattern_candle.LowPrice) - indent, step)
sell_stop_loss = float(pattern_candle.HighPrice) + indent
buy_take_offset = self._get_price_offset(self._take_profit_buy.Value)
sell_take_offset = self._get_price_offset(self._take_profit_sell.Value)
buy_take_profit = buy_price + buy_take_offset if buy_take_offset > 0 else 0.0
sell_take_profit = sell_price - sell_take_offset if sell_take_offset > 0 else 0.0
self._has_active_orders = True
self._pending_high = float(pattern_candle.HighPrice)
self._pending_low = float(pattern_candle.LowPrice)
self._pending_buy_price = buy_price
self._pending_sell_price = sell_price
self._pending_buy_stop_loss = buy_stop_loss
self._pending_sell_stop_loss = sell_stop_loss
self._pending_buy_take_profit = buy_take_profit
self._pending_sell_take_profit = sell_take_profit
self._exit_request_active = False
if self._order_expiration_hours.Value > 0:
self._orders_expiry = current_candle.CloseTime + TimeSpan.FromHours(self._order_expiration_hours.Value)
else:
self._orders_expiry = None
def _manage_active_position(self, candle):
if self._exit_request_active:
return
if self.Position > 0:
self._update_breakeven_long(candle)
self._update_trailing_long(candle)
if self._stop_loss > 0 and float(candle.LowPrice) <= self._stop_loss:
self.SellMarket(abs(self.Position))
self._exit_request_active = True
return
if self._take_profit > 0 and float(candle.HighPrice) >= self._take_profit:
self.SellMarket(abs(self.Position))
self._exit_request_active = True
elif self.Position < 0:
self._update_breakeven_short(candle)
self._update_trailing_short(candle)
if self._stop_loss > 0 and float(candle.HighPrice) >= self._stop_loss:
self.BuyMarket(abs(self.Position))
self._exit_request_active = True
return
if self._take_profit > 0 and float(candle.LowPrice) <= self._take_profit:
self.BuyMarket(abs(self.Position))
self._exit_request_active = True
def _update_breakeven_long(self, candle):
if self._breakeven.Value <= 0 or self._breakeven_profit.Value <= 0:
return
be_offset = self._get_price_offset(self._breakeven.Value)
if self._stop_loss >= self._entry_price + be_offset:
return
be_profit_offset = self._get_price_offset(self._breakeven_profit.Value)
if float(candle.HighPrice) - self._entry_price >= be_profit_offset:
self._stop_loss = self._entry_price + be_offset
def _update_breakeven_short(self, candle):
if self._breakeven.Value <= 0 or self._breakeven_profit.Value <= 0:
return
be_offset = self._get_price_offset(self._breakeven.Value)
if self._stop_loss <= self._entry_price - be_offset:
return
be_profit_offset = self._get_price_offset(self._breakeven_profit.Value)
if self._entry_price - float(candle.LowPrice) >= be_profit_offset:
self._stop_loss = self._entry_price - be_offset
def _update_trailing_long(self, candle):
if self._trailing_stop.Value <= 0:
return
trailing = self._get_price_offset(self._trailing_stop.Value)
step = self._get_price_offset(self._trailing_step.Value)
current = float(candle.HighPrice)
if current - self._entry_price <= trailing + step:
return
if self._stop_loss < current - (trailing + step):
self._stop_loss = max(self._stop_loss, current - trailing)
def _update_trailing_short(self, candle):
if self._trailing_stop.Value <= 0:
return
trailing = self._get_price_offset(self._trailing_stop.Value)
step = self._get_price_offset(self._trailing_step.Value)
current = float(candle.LowPrice)
if self._entry_price - current <= trailing + step:
return
if self._stop_loss == 0 or self._stop_loss > current + trailing + step:
self._stop_loss = current + trailing
def _update_previous_candles(self, candle):
self._prev2 = self._prev1
self._prev1 = candle
def _clear_pending_orders(self):
self._has_active_orders = False
self._orders_expiry = None
self._pending_high = 0.0
self._pending_low = 0.0
self._pending_buy_price = 0.0
self._pending_sell_price = 0.0
self._pending_buy_stop_loss = 0.0
self._pending_sell_stop_loss = 0.0
self._pending_buy_take_profit = 0.0
self._pending_sell_take_profit = 0.0
def _is_lowest_bar(self, candidate_low, other_low, current_low, lowest_value):
if not self._are_close(candidate_low, lowest_value):
return False
return candidate_low < other_low and candidate_low < current_low
def _is_highest_bar(self, candidate_high, other_high, current_high, highest_value):
if not self._are_close(candidate_high, highest_value):
return False
return candidate_high > other_high and candidate_high > current_high
def _get_price_offset(self, value):
step = self._get_price_step()
return value * step
def _get_price_step(self):
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 0.0001
return step if step > 0 else 0.0001
def _are_close(self, first, second):
tolerance = self._get_price_step() / 2.0
return abs(first - second) <= tolerance
def OnReseted(self):
super(absorption_strategy, self).OnReseted()
self._highest = None
self._lowest = None
self._prev1 = None
self._prev2 = None
self._has_active_orders = False
self._orders_expiry = None
self._pending_high = 0.0
self._pending_low = 0.0
self._pending_buy_price = 0.0
self._pending_sell_price = 0.0
self._pending_buy_stop_loss = 0.0
self._pending_sell_stop_loss = 0.0
self._pending_buy_take_profit = 0.0
self._pending_sell_take_profit = 0.0
self._entry_price = 0.0
self._stop_loss = 0.0
self._take_profit = 0.0
self._prev_position = 0.0
self._exit_request_active = False
def CreateClone(self):
return absorption_strategy()