Estratégia de Absorção
A estratégia replica o expert advisor Absorption para MetaTrader. Procura velas "engulfing" que absorvem o range da barra anterior e formam um extremo dentro de um curto período de pesquisa. Quando tal barra de absorção aparece, o algoritmo posiciona ordens stop dos dois lados do mercado e gerencia a posição resultante com uma combinação de objetivos fixos, lógica de breakeven e um trailing stop.
Lógica de Trading
- Detecção de padrão
- As últimas duas velas completadas são inspecionadas.
- Uma vela é tratada como barra de absorção quando sua máxima está acima da máxima da vela anterior e sua mínima está abaixo da mínima da vela anterior.
- A barra é validada verificando se sua máxima ou mínima é o valor mais extremo dentro das últimas
MaxSearchvelas. - Prioridade é dada à vela mais antiga (duas barras atrás). Se ambas as barras satisfazem a condição de absorção, a barra mais antiga é usada; caso contrário, a barra mais recente pode acionar a configuração.
- Colocação de ordens
- Uma ordem de compra stop é colocada na máxima da barra mais o
Indentconfigurado. - Uma ordem de venda stop é colocada na mínima da barra menos o mesmo
Indent. - Ambas as ordens usam o volume de estratégia comum.
- Cada ordem pendente armazena seu próprio nível de stop protetor e objetivo de take profit opcional. As ordens expiram automaticamente após
OrderExpirationHoursse permanecerem não preenchidas.
- Uma ordem de compra stop é colocada na máxima da barra mais o
- Gestão de posições
- Quando um lado é preenchido, a ordem pendente oposta é cancelada.
- O stop inicial está localizado no lado oposto da vela de absorção menos/mais o indent.
- Um take profit fixo opcional fecha a operação assim que a distância configurada em passos de preço é alcançada.
- O módulo de breakeven move o stop-loss para
Entrada + Breakeven(comprado) ouEntrada - Breakeven(vendido) após o preço avançarBreakevenProfitpassos. - O trailing stop mantém o stop-loss a distância
TrailingStopdo melhor preço, atualizando apenas quando o preço se move pelo menosTrailingSteppassos na direção rentável.
Parâmetros
| Parâmetro | Descrição |
|---|---|
CandleType |
Tipo de dados de vela para assinar (padrão: período de 1 hora). |
MaxSearch |
Número de velas recentes usadas para confirmar extremos de máxima/mínima. |
TakeProfitBuy |
Distância em passos de preço para a ordem de take profit comprado. 0 desabilita o objetivo. |
TakeProfitSell |
Distância em passos de preço para a ordem de take profit vendido. 0 desabilita o objetivo. |
TrailingStop |
Distância do trailing stop em passos de preço. 0 desabilita o trailing. |
TrailingStep |
Movimento mínimo para frente necessário antes de avançar o trailing stop. Deve ser positivo quando o trailing está habilitado. |
Indent |
Deslocamento em passos de preço adicionado acima/abaixo da barra de absorção para definir os níveis de entrada stop. |
OrderExpirationHours |
Tempo de vida de ordens pendentes. Após este período as ordens são canceladas se não acionadas. |
Breakeven |
Deslocamento aplicado ao stop-loss quando a regra de breakeven é acionada. 0 desabilita o breakeven. |
BreakevenProfit |
Limiar de lucro (em passos de preço) que deve ser alcançado antes de mover o stop-loss para breakeven. |
Todas as entradas baseadas em distância são expressas como múltiplos do passo de preço do instrumento. O volume de estratégia padrão está definido em 0.1.
Gerenciamento de Risco
A estratégia usa apenas ordens de mercado para saídas. As regras de stop-loss, take-profit, breakeven e trailing monitoram as máximas e mínimas das velas para detectar toques de nível dentro da barra. Uma vez que uma ordem de saída é enviada, nenhuma solicitação de saída adicional é gerada até que a posição atual esteja flat.
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()