Стратегия AFStar
Стратегия AFStar отслеживает смену импульса, комбинируя поиск пересечений семейства быстрых и медленных EMA с фильтром на основе прорыва канала Williams %R. Торговый сигнал появляется только тогда, когда оба компонента совпадают по направлению.
Бычий сигнал формируется, если хотя бы одна быстрая EMA из диапазона
[Start Fast, End Fast] пересекает сверху совместимую медленную EMA из
[Start Slow, End Slow] при шаге Step Period, а осциллятор
Williams %R, рассчитанный для значений риска из [Start Risk, End Risk]
с шагом Risk Step, выходит вверх из нижней границы после пребывания в
нейтральной зоне. Медвежий сигнал работает зеркально. Исполнение заявок
откладывается на количество баров, указанное в параметре Signal Bar, что
полностью повторяет поведение исходного эксперта MetaTrader.
После открытия позиции стратегия при желании устанавливает защитный стоп-лосс и тейк-профит в шагах цены. Проверка срабатывания этих уровней происходит на каждой закрывшейся свече. Управление капиталом упрощено: используется постоянный объём Order Volume, поэтому сложные правила расчёта лота из версии MQL5 не переносились.
Условия входа
- Покупка:
- Быстрая EMA из заданного диапазона пересекает снизу медленную EMA.
- Williams %R фиксирует выход из нижнего канала после периода консолидации.
- При включённом Enable Sell Exits перед входом закрываются короткие позиции.
- Продажа:
- Зеркальные условия для медвежьего пересечения и прорыва верхнего канала.
- При активном Enable Buy Exits сначала закрываются длинные позиции.
Условия выхода
- Сигнал противоположного направления закрывает позицию, если соответствующий флаг выхода включён (покупка закрывает шорты, продажа — лонги).
- Дополнительно позиция может быть закрыта по стоп-лоссу или тейк-профиту, заданным в шагах цены.
Параметры
- Order Volume – объём рыночных заявок.
- Candle Type – используемый таймфрейм (по умолчанию 4 часа).
- Start Fast / End Fast / Step Period – диапазон быстрых EMA.
- Start Slow / End Slow – диапазон медленных EMA.
- Start Risk / End Risk / Risk Step – границы перебора параметра риска для Williams %R.
- Signal Bar – задержка в барах перед исполнением сигнала.
- Stop Loss (pips) – расстояние стоп-лосса в шагах цены.
- Take Profit (pips) – расстояние тейк-профита в шагах цены.
- Enable Buy Entries / Enable Sell Entries – включение лонгов или шортов.
- Enable Buy Exits / Enable Sell Exits – разрешение закрытия позиций сигналами противоположного направления.
Примечания
- Для расчётов хранится до 512 последних свечей.
- Если у инструмента отсутствует шаг цены, для расчёта защитных уровней используется значение 1.
- Очередь сигналов позволяет задать мгновенное исполнение при Signal Bar = 0 или задержку на указанное количество завершённых баров при больших значениях.
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 StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// AFStar strategy converted from MetaTrader 5 expert advisor.
/// It searches for fast and slow EMA crossovers across a configurable range
/// and confirms them with a dynamic Williams %R channel breakout.
/// </summary>
public class AfStarStrategy : Strategy
{
private readonly StrategyParam<int> _rangeLength;
private readonly StrategyParam<int> _maxHistory;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _startFast;
private readonly StrategyParam<decimal> _endFast;
private readonly StrategyParam<decimal> _startSlow;
private readonly StrategyParam<decimal> _endSlow;
private readonly StrategyParam<decimal> _stepPeriod;
private readonly StrategyParam<decimal> _startRisk;
private readonly StrategyParam<decimal> _endRisk;
private readonly StrategyParam<decimal> _stepRisk;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<bool> _enableBuyEntries;
private readonly StrategyParam<bool> _enableSellEntries;
private readonly StrategyParam<bool> _enableBuyExits;
private readonly StrategyParam<bool> _enableSellExits;
private readonly List<ICandleMessage> _candles = new();
private readonly List<decimal> _value2History = new();
private readonly List<AfStarSignal> _signalQueue = new();
private decimal _lastWpr;
private bool _prevBuy1;
private bool _prevSell1;
private bool _prevBuy2;
private bool _prevSell2;
private decimal? _longStopLevel;
private decimal? _longTakeLevel;
private decimal? _shortStopLevel;
private decimal? _shortTakeLevel;
/// <summary>
/// Initializes a new instance of <see cref="AfStarStrategy"/>.
/// </summary>
public AfStarStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Volume used for market orders", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Time frame for candles", "General");
_startFast = Param(nameof(StartFast), 3m)
.SetGreaterThanZero()
.SetDisplay("Start Fast", "Lower bound for fast EMA period", "Indicator");
_endFast = Param(nameof(EndFast), 3.5m)
.SetGreaterThanZero()
.SetDisplay("End Fast", "Upper bound for fast EMA period", "Indicator");
_startSlow = Param(nameof(StartSlow), 8m)
.SetGreaterThanZero()
.SetDisplay("Start Slow", "Lower bound for slow EMA period", "Indicator");
_endSlow = Param(nameof(EndSlow), 9m)
.SetGreaterThanZero()
.SetDisplay("End Slow", "Upper bound for slow EMA period", "Indicator");
_stepPeriod = Param(nameof(StepPeriod), 0.2m)
.SetGreaterThanZero()
.SetDisplay("Period Step", "Increment for scanning EMA periods", "Indicator");
_startRisk = Param(nameof(StartRisk), 1m)
.SetGreaterThanZero()
.SetDisplay("Start Risk", "Lower bound for risk scan", "Williams %R");
_endRisk = Param(nameof(EndRisk), 2.8m)
.SetGreaterThanZero()
.SetDisplay("End Risk", "Upper bound for risk scan", "Williams %R");
_stepRisk = Param(nameof(StepRisk), 0.5m)
.SetGreaterThanZero()
.SetDisplay("Risk Step", "Increment for risk parameter", "Williams %R");
_rangeLength = Param(nameof(RangeLength), 10)
.SetRange(1, 200)
.SetDisplay("Range Length", "Bars used to compute the average range filter", "Indicator");
_maxHistory = Param(nameof(MaxHistory), 512)
.SetRange(10, 5000)
.SetDisplay("Max History", "Maximum candles stored for calculations", "General");
_signalBar = Param(nameof(SignalBar), 1)
.SetRange(0, 10)
.SetDisplay("Signal Bar", "Delay in bars before executing a signal", "Trading");
_stopLossPips = Param(nameof(StopLossPips), 1000)
.SetRange(0, 100000)
.SetDisplay("Stop Loss (pips)", "Stop loss distance in price steps", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 2000)
.SetRange(0, 100000)
.SetDisplay("Take Profit (pips)", "Take profit distance in price steps", "Risk");
_enableBuyEntries = Param(nameof(BuyEntriesEnabled), true)
.SetDisplay("Enable Buy Entries", "Allow long entries on buy signals", "Trading");
_enableSellEntries = Param(nameof(SellEntriesEnabled), true)
.SetDisplay("Enable Sell Entries", "Allow short entries on sell signals", "Trading");
_enableBuyExits = Param(nameof(BuyExitsEnabled), true)
.SetDisplay("Enable Buy Exits", "Allow closing longs on sell signals", "Trading");
_enableSellExits = Param(nameof(SellExitsEnabled), true)
.SetDisplay("Enable Sell Exits", "Allow closing shorts on buy signals", "Trading");
}
/// <summary>
/// Trade volume used for market orders.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Lower bound for the fast EMA search.
/// </summary>
public decimal StartFast
{
get => _startFast.Value;
set => _startFast.Value = value;
}
/// <summary>
/// Upper bound for the fast EMA search.
/// </summary>
public decimal EndFast
{
get => _endFast.Value;
set => _endFast.Value = value;
}
/// <summary>
/// Lower bound for the slow EMA search.
/// </summary>
public decimal StartSlow
{
get => _startSlow.Value;
set => _startSlow.Value = value;
}
/// <summary>
/// Upper bound for the slow EMA search.
/// </summary>
public decimal EndSlow
{
get => _endSlow.Value;
set => _endSlow.Value = value;
}
/// <summary>
/// Step used when scanning EMA periods.
/// </summary>
public decimal StepPeriod
{
get => _stepPeriod.Value;
set => _stepPeriod.Value = value;
}
/// <summary>
/// Lower bound for the risk parameter scan.
/// </summary>
public decimal StartRisk
{
get => _startRisk.Value;
set => _startRisk.Value = value;
}
/// <summary>
/// Upper bound for the risk parameter scan.
/// </summary>
public decimal EndRisk
{
get => _endRisk.Value;
set => _endRisk.Value = value;
}
/// <summary>
/// Step for the risk parameter scan.
/// </summary>
public decimal StepRisk
{
get => _stepRisk.Value;
set => _stepRisk.Value = value;
}
/// <summary>
/// Number of bars used to calculate the average range filter.
/// </summary>
public int RangeLength
{
get => _rangeLength.Value;
set => _rangeLength.Value = value;
}
/// <summary>
/// Maximum number of stored candles for calculations.
/// </summary>
public int MaxHistory
{
get => _maxHistory.Value;
set
{
_maxHistory.Value = value;
TrimHistory();
}
}
/// <summary>
/// Bars to wait before executing a signal.
/// </summary>
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
/// <summary>
/// Stop loss distance in price steps.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take profit distance in price steps.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Allow long entries.
/// </summary>
public bool BuyEntriesEnabled
{
get => _enableBuyEntries.Value;
set => _enableBuyEntries.Value = value;
}
/// <summary>
/// Allow short entries.
/// </summary>
public bool SellEntriesEnabled
{
get => _enableSellEntries.Value;
set => _enableSellEntries.Value = value;
}
/// <summary>
/// Allow closing long positions on sell signals.
/// </summary>
public bool BuyExitsEnabled
{
get => _enableBuyExits.Value;
set => _enableBuyExits.Value = value;
}
/// <summary>
/// Allow closing short positions on buy signals.
/// </summary>
public bool SellExitsEnabled
{
get => _enableSellExits.Value;
set => _enableSellExits.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_candles.Clear();
_value2History.Clear();
_signalQueue.Clear();
_lastWpr = -50m;
_prevBuy1 = false;
_prevSell1 = false;
_prevBuy2 = false;
_prevSell2 = false;
_longStopLevel = null;
_longTakeLevel = null;
_shortStopLevel = null;
_shortTakeLevel = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = OrderVolume;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// manual indicators, no bound check needed
_candles.Add(candle);
_value2History.Add(0m);
if (_candles.Count > MaxHistory)
{
_candles.RemoveAt(0);
if (_value2History.Count > 0)
_value2History.RemoveAt(0);
}
var signal = ComputeSignal();
ApplyStops(candle);
if (signal.HasValue)
{
_signalQueue.Add(signal.Value);
while (_signalQueue.Count > SignalBar)
{
var activeSignal = _signalQueue[0];
try { _signalQueue.RemoveAt(0); } catch { break; }
ExecuteSignal(activeSignal, candle);
}
}
}
private void TrimHistory()
{
while (_candles.Count > MaxHistory)
{
_candles.RemoveAt(0);
}
while (_value2History.Count > MaxHistory)
{
_value2History.RemoveAt(0);
}
}
private AfStarSignal? ComputeSignal()
{
if (_candles.Count < GetMinHistory())
return null;
var buy1 = false;
var sell1 = false;
foreach (var slow in EnumerateRange(StartSlow, EndSlow, StepPeriod))
{
foreach (var fast in EnumerateRange(StartFast, EndFast, StepPeriod))
{
var slowPer = 2m / (slow + 1m);
var fastPer = 2m / (fast + 1m);
var slowCurrent = GetClose(0) * slowPer + GetClose(1) * (1m - slowPer);
var slowPrevious = GetClose(1) * slowPer + GetClose(2) * (1m - slowPer);
var fastCurrent = GetClose(0) * fastPer + GetClose(1) * (1m - fastPer);
var fastPrevious = GetClose(1) * fastPer + GetClose(2) * (1m - fastPer);
if (!buy1 && fastPrevious < slowPrevious && fastCurrent > slowCurrent)
{
buy1 = true;
break;
}
if (!sell1 && fastPrevious > slowPrevious && fastCurrent < slowCurrent)
{
sell1 = true;
break;
}
}
if (buy1 || sell1)
break;
}
var range = ComputeAverageRange();
var mro1 = FindMro1(range);
var mro2 = FindMro2(range);
var value2 = 0m;
var hasBuy2 = false;
var hasSell2 = false;
foreach (var risk in EnumerateRange(StartRisk, EndRisk, StepRisk))
{
var value10 = 3m + risk * 2m;
var x1 = 67m + risk;
var x2 = 33m - risk;
var value11 = value10;
value11 = mro1 > -1 ? 3m : value10;
value11 = mro2 > -1 ? 4m : value10;
var period = Math.Max(1, (int)value11);
var wpr = GetWilliamsR(period);
value2 = 100m - Math.Abs(wpr);
if (!hasSell2 && value2 < x2)
{
var offset = 1;
while (TryGetPrevValue2(offset, out var prev) && prev >= x2 && prev <= x1)
offset++;
if (TryGetPrevValue2(offset, out var prevOutside) && prevOutside > x1)
hasSell2 = true;
}
if (!hasBuy2 && value2 > x1)
{
var offset = 1;
while (TryGetPrevValue2(offset, out var prev) && prev >= x2 && prev <= x1)
offset++;
if (TryGetPrevValue2(offset, out var prevOutside) && prevOutside < x2)
hasBuy2 = true;
}
if (hasBuy2 || hasSell2)
break;
}
var buySignal = (buy1 && hasBuy2) || (buy1 && _prevBuy2) || (_prevBuy1 && hasBuy2);
var sellSignal = (sell1 && hasSell2) || (sell1 && _prevSell2) || (_prevSell1 && hasSell2);
if (buySignal && sellSignal)
{
buySignal = false;
sellSignal = false;
}
_prevBuy1 = buy1;
_prevSell1 = sell1;
_prevBuy2 = hasBuy2;
_prevSell2 = hasSell2;
_value2History[^1] = value2;
return new AfStarSignal(buySignal, sellSignal);
}
private void ExecuteSignal(AfStarSignal signal, ICandleMessage candle)
{
if (signal.BuyArrow)
{
if (SellExitsEnabled)
ExitShort();
if (BuyEntriesEnabled && Position == 0)
{
BuyMarket();
InitializeLongTargets(candle.ClosePrice);
}
}
if (signal.SellArrow)
{
if (BuyExitsEnabled)
ExitLong();
if (SellEntriesEnabled && Position == 0)
{
SellMarket();
InitializeShortTargets(candle.ClosePrice);
}
}
}
private void ApplyStops(ICandleMessage candle)
{
var position = Position;
if (position > 0)
{
if (_longStopLevel.HasValue && candle.LowPrice <= _longStopLevel.Value)
{
ExitLong();
position = Position;
}
else if (_longTakeLevel.HasValue && candle.HighPrice >= _longTakeLevel.Value)
{
ExitLong();
position = Position;
}
}
if (position < 0)
{
if (_shortStopLevel.HasValue && candle.HighPrice >= _shortStopLevel.Value)
{
ExitShort();
}
else if (_shortTakeLevel.HasValue && candle.LowPrice <= _shortTakeLevel.Value)
{
ExitShort();
}
}
}
private void ExitLong()
{
var position = Position;
if (position > 0)
{
SellMarket();
ResetLongTargets();
}
}
private void ExitShort()
{
var position = Position;
if (position < 0)
{
BuyMarket();
ResetShortTargets();
}
}
private void InitializeLongTargets(decimal entryPrice)
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
step = 1m;
_longStopLevel = StopLossPips > 0 ? entryPrice - step * StopLossPips : null;
_longTakeLevel = TakeProfitPips > 0 ? entryPrice + step * TakeProfitPips : null;
}
private void InitializeShortTargets(decimal entryPrice)
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
step = 1m;
_shortStopLevel = StopLossPips > 0 ? entryPrice + step * StopLossPips : null;
_shortTakeLevel = TakeProfitPips > 0 ? entryPrice - step * TakeProfitPips : null;
}
private void ResetLongTargets()
{
_longStopLevel = null;
_longTakeLevel = null;
}
private void ResetShortTargets()
{
_shortStopLevel = null;
_shortTakeLevel = null;
}
private int GetMinHistory()
{
return 12 + 3 + SignalBar;
}
private decimal ComputeAverageRange()
{
var sum = 0m;
for (var i = 0; i < RangeLength; i++)
{
sum += Math.Abs(GetHigh(i) - GetLow(i));
}
return sum / RangeLength;
}
private int FindMro1(decimal range)
{
for (var offset = 0; offset < 9; offset++)
{
if (Math.Abs(GetOpen(offset) - GetClose(offset + 1)) >= range * 2m)
return offset;
}
return -1;
}
private int FindMro2(decimal range)
{
for (var offset = 0; offset < 6; offset++)
{
if (Math.Abs(GetClose(offset + 3) - GetClose(offset)) >= range * 4.6m)
return offset;
}
return -1;
}
private decimal GetClose(int offset)
{
return _candles[^(offset + 1)].ClosePrice;
}
private decimal GetOpen(int offset)
{
return _candles[^(offset + 1)].OpenPrice;
}
private decimal GetHigh(int offset)
{
return _candles[^(offset + 1)].HighPrice;
}
private decimal GetLow(int offset)
{
return _candles[^(offset + 1)].LowPrice;
}
private decimal GetWilliamsR(int period)
{
var maxHigh = GetHigh(0);
var minLow = GetLow(0);
for (var i = 1; i < period && i < _candles.Count; i++)
{
var high = GetHigh(i);
var low = GetLow(i);
if (high > maxHigh)
maxHigh = high;
if (low < minLow)
minLow = low;
}
var close = GetClose(0);
var range = maxHigh - minLow;
if (range == 0m)
{
return _lastWpr;
}
var wpr = -(maxHigh - close) * 100m / range;
_lastWpr = wpr;
return wpr;
}
private bool TryGetPrevValue2(int offset, out decimal value)
{
var index = _value2History.Count - 1 - offset;
if (index >= 0)
{
value = _value2History[index];
return true;
}
value = 0m;
return false;
}
private IEnumerable<decimal> EnumerateRange(decimal start, decimal end, decimal step)
{
if (step <= 0m)
yield break;
if (start <= end)
{
for (var value = start; value <= end + 0.0000001m; value += step)
yield return value;
}
else
{
for (var value = start; value >= end - 0.0000001m; value -= step)
yield return value;
}
}
private readonly struct AfStarSignal
{
public AfStarSignal(bool buyArrow, bool sellArrow)
{
BuyArrow = buyArrow;
SellArrow = sellArrow;
}
public bool BuyArrow { get; }
public bool SellArrow { get; }
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class af_star_strategy(Strategy):
"""AFStar strategy: fast/slow EMA crossover scan with Williams %R channel breakout confirmation."""
def __init__(self):
super(af_star_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Time frame for candles", "General")
self._start_fast = self.Param("StartFast", 3.0) \
.SetGreaterThanZero() \
.SetDisplay("Start Fast", "Lower bound for fast EMA period", "Indicator")
self._end_fast = self.Param("EndFast", 3.5) \
.SetGreaterThanZero() \
.SetDisplay("End Fast", "Upper bound for fast EMA period", "Indicator")
self._start_slow = self.Param("StartSlow", 8.0) \
.SetGreaterThanZero() \
.SetDisplay("Start Slow", "Lower bound for slow EMA period", "Indicator")
self._end_slow = self.Param("EndSlow", 9.0) \
.SetGreaterThanZero() \
.SetDisplay("End Slow", "Upper bound for slow EMA period", "Indicator")
self._step_period = self.Param("StepPeriod", 0.2) \
.SetGreaterThanZero() \
.SetDisplay("Period Step", "Increment for scanning EMA periods", "Indicator")
self._start_risk = self.Param("StartRisk", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Start Risk", "Lower bound for risk scan", "Williams %R")
self._end_risk = self.Param("EndRisk", 2.8) \
.SetGreaterThanZero() \
.SetDisplay("End Risk", "Upper bound for risk scan", "Williams %R")
self._step_risk = self.Param("StepRisk", 0.5) \
.SetGreaterThanZero() \
.SetDisplay("Risk Step", "Increment for risk parameter", "Williams %R")
self._range_length = self.Param("RangeLength", 10) \
.SetDisplay("Range Length", "Bars used to compute the average range filter", "Indicator")
self._max_history = self.Param("MaxHistory", 512) \
.SetDisplay("Max History", "Maximum candles stored for calculations", "General")
self._signal_bar = self.Param("SignalBar", 1) \
.SetDisplay("Signal Bar", "Delay in bars before executing a signal", "Trading")
self._stop_loss_pips = self.Param("StopLossPips", 1000) \
.SetDisplay("Stop Loss (pips)", "Stop loss distance in price steps", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", 2000) \
.SetDisplay("Take Profit (pips)", "Take profit distance in price steps", "Risk")
self._enable_buy_entries = self.Param("BuyEntriesEnabled", True) \
.SetDisplay("Enable Buy Entries", "Allow long entries on buy signals", "Trading")
self._enable_sell_entries = self.Param("SellEntriesEnabled", True) \
.SetDisplay("Enable Sell Entries", "Allow short entries on sell signals", "Trading")
self._enable_buy_exits = self.Param("BuyExitsEnabled", True) \
.SetDisplay("Enable Buy Exits", "Allow closing longs on sell signals", "Trading")
self._enable_sell_exits = self.Param("SellExitsEnabled", True) \
.SetDisplay("Enable Sell Exits", "Allow closing shorts on buy signals", "Trading")
self._candles_buf = []
self._value2_history = []
self._signal_queue = []
self._last_wpr = -50.0
self._prev_buy1 = False
self._prev_sell1 = False
self._prev_buy2 = False
self._prev_sell2 = False
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def StartFast(self):
return float(self._start_fast.Value)
@property
def EndFast(self):
return float(self._end_fast.Value)
@property
def StartSlow(self):
return float(self._start_slow.Value)
@property
def EndSlow(self):
return float(self._end_slow.Value)
@property
def StepPeriod(self):
return float(self._step_period.Value)
@property
def StartRisk(self):
return float(self._start_risk.Value)
@property
def EndRisk(self):
return float(self._end_risk.Value)
@property
def StepRisk(self):
return float(self._step_risk.Value)
@property
def RangeLength(self):
return int(self._range_length.Value)
@property
def MaxHistory(self):
return int(self._max_history.Value)
@property
def SignalBar(self):
return int(self._signal_bar.Value)
@property
def StopLossPips(self):
return int(self._stop_loss_pips.Value)
@property
def TakeProfitPips(self):
return int(self._take_profit_pips.Value)
@property
def BuyEntriesEnabled(self):
return self._enable_buy_entries.Value
@property
def SellEntriesEnabled(self):
return self._enable_sell_entries.Value
@property
def BuyExitsEnabled(self):
return self._enable_buy_exits.Value
@property
def SellExitsEnabled(self):
return self._enable_sell_exits.Value
def OnStarted2(self, time):
super(af_star_strategy, self).OnStarted2(time)
self._candles_buf = []
self._value2_history = []
self._signal_queue = []
self._last_wpr = -50.0
self._prev_buy1 = False
self._prev_sell1 = False
self._prev_buy2 = False
self._prev_sell2 = False
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
o = float(candle.OpenPrice)
c = float(candle.ClosePrice)
self._candles_buf.append((o, h, lo, c))
self._value2_history.append(0.0)
mx = self.MaxHistory
if len(self._candles_buf) > mx:
self._candles_buf.pop(0)
if len(self._value2_history) > 0:
self._value2_history.pop(0)
self._apply_stops(candle)
signal = self._compute_signal()
if signal is not None:
self._signal_queue.append(signal)
while len(self._signal_queue) > self.SignalBar:
active = self._signal_queue.pop(0)
self._execute_signal(active, c)
def _get_min_history(self):
return 12 + 3 + self.SignalBar
def _get_close(self, offset):
return self._candles_buf[-(offset + 1)][3]
def _get_open(self, offset):
return self._candles_buf[-(offset + 1)][0]
def _get_high(self, offset):
return self._candles_buf[-(offset + 1)][1]
def _get_low(self, offset):
return self._candles_buf[-(offset + 1)][2]
def _compute_avg_range(self):
s = 0.0
for i in range(self.RangeLength):
s += abs(self._get_high(i) - self._get_low(i))
return s / self.RangeLength
def _find_mro1(self, rng):
for offset in range(9):
if abs(self._get_open(offset) - self._get_close(offset + 1)) >= rng * 2.0:
return offset
return -1
def _find_mro2(self, rng):
for offset in range(6):
if abs(self._get_close(offset + 3) - self._get_close(offset)) >= rng * 4.6:
return offset
return -1
def _get_williams_r(self, period):
max_high = self._get_high(0)
min_low = self._get_low(0)
for i in range(1, min(period, len(self._candles_buf))):
h = self._get_high(i)
lo = self._get_low(i)
if h > max_high:
max_high = h
if lo < min_low:
min_low = lo
close = self._get_close(0)
rng = max_high - min_low
if rng == 0:
return self._last_wpr
wpr = -(max_high - close) * 100.0 / rng
self._last_wpr = wpr
return wpr
def _try_get_prev_value2(self, offset):
idx = len(self._value2_history) - 1 - offset
if idx >= 0:
return True, self._value2_history[idx]
return False, 0.0
def _enum_range(self, start, end, step):
if step <= 0:
return
vals = []
if start <= end:
v = start
while v <= end + 0.0000001:
vals.append(v)
v += step
else:
v = start
while v >= end - 0.0000001:
vals.append(v)
v -= step
return vals
def _compute_signal(self):
if len(self._candles_buf) < self._get_min_history():
return None
buy1 = False
sell1 = False
for slow in self._enum_range(self.StartSlow, self.EndSlow, self.StepPeriod):
for fast in self._enum_range(self.StartFast, self.EndFast, self.StepPeriod):
slow_per = 2.0 / (slow + 1.0)
fast_per = 2.0 / (fast + 1.0)
slow_cur = self._get_close(0) * slow_per + self._get_close(1) * (1.0 - slow_per)
slow_prev = self._get_close(1) * slow_per + self._get_close(2) * (1.0 - slow_per)
fast_cur = self._get_close(0) * fast_per + self._get_close(1) * (1.0 - fast_per)
fast_prev = self._get_close(1) * fast_per + self._get_close(2) * (1.0 - fast_per)
if not buy1 and fast_prev < slow_prev and fast_cur > slow_cur:
buy1 = True
break
if not sell1 and fast_prev > slow_prev and fast_cur < slow_cur:
sell1 = True
break
if buy1 or sell1:
break
avg_range = self._compute_avg_range()
mro1 = self._find_mro1(avg_range)
mro2 = self._find_mro2(avg_range)
value2 = 0.0
has_buy2 = False
has_sell2 = False
for risk in self._enum_range(self.StartRisk, self.EndRisk, self.StepRisk):
value10 = 3.0 + risk * 2.0
x1 = 67.0 + risk
x2 = 33.0 - risk
value11 = value10
if mro1 > -1:
value11 = 3.0
if mro2 > -1:
value11 = 4.0
period = max(1, int(value11))
wpr = self._get_williams_r(period)
value2 = 100.0 - abs(wpr)
if not has_sell2 and value2 < x2:
offset = 1
while True:
ok, prev = self._try_get_prev_value2(offset)
if ok and prev >= x2 and prev <= x1:
offset += 1
else:
break
ok2, prev_outside = self._try_get_prev_value2(offset)
if ok2 and prev_outside > x1:
has_sell2 = True
if not has_buy2 and value2 > x1:
offset = 1
while True:
ok, prev = self._try_get_prev_value2(offset)
if ok and prev >= x2 and prev <= x1:
offset += 1
else:
break
ok2, prev_outside = self._try_get_prev_value2(offset)
if ok2 and prev_outside < x2:
has_buy2 = True
if has_buy2 or has_sell2:
break
buy_signal = (buy1 and has_buy2) or (buy1 and self._prev_buy2) or (self._prev_buy1 and has_buy2)
sell_signal = (sell1 and has_sell2) or (sell1 and self._prev_sell2) or (self._prev_sell1 and has_sell2)
if buy_signal and sell_signal:
buy_signal = False
sell_signal = False
self._prev_buy1 = buy1
self._prev_sell1 = sell1
self._prev_buy2 = has_buy2
self._prev_sell2 = has_sell2
self._value2_history[-1] = value2
return (buy_signal, sell_signal)
def _execute_signal(self, signal, close):
buy_arrow, sell_arrow = signal
if buy_arrow:
if self.SellExitsEnabled:
self._exit_short()
if self.BuyEntriesEnabled and self.Position == 0:
self.BuyMarket()
self._init_long_targets(close)
if sell_arrow:
if self.BuyExitsEnabled:
self._exit_long()
if self.SellEntriesEnabled and self.Position == 0:
self.SellMarket()
self._init_short_targets(close)
def _apply_stops(self, candle):
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
if self.Position > 0:
if self._long_stop is not None and lo <= self._long_stop:
self._exit_long()
elif self._long_take is not None and h >= self._long_take:
self._exit_long()
if self.Position < 0:
if self._short_stop is not None and h >= self._short_stop:
self._exit_short()
elif self._short_take is not None and lo <= self._short_take:
self._exit_short()
def _exit_long(self):
if self.Position > 0:
self.SellMarket()
self._long_stop = None
self._long_take = None
def _exit_short(self):
if self.Position < 0:
self.BuyMarket()
self._short_stop = None
self._short_take = None
def _init_long_targets(self, entry):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 1.0
self._long_stop = entry - step * self.StopLossPips if self.StopLossPips > 0 else None
self._long_take = entry + step * self.TakeProfitPips if self.TakeProfitPips > 0 else None
def _init_short_targets(self, entry):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 1.0
self._short_stop = entry + step * self.StopLossPips if self.StopLossPips > 0 else None
self._short_take = entry - step * self.TakeProfitPips if self.TakeProfitPips > 0 else None
def OnReseted(self):
super(af_star_strategy, self).OnReseted()
self._candles_buf = []
self._value2_history = []
self._signal_queue = []
self._last_wpr = -50.0
self._prev_buy1 = False
self._prev_sell1 = False
self._prev_buy2 = False
self._prev_sell2 = False
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
def CreateClone(self):
return af_star_strategy()