AFStar Strategy
The AFStar strategy scans for short-term momentum shifts by combining a wide range of fast/slow EMA crossovers with a Williams %R channel breakout filter. Only when both components agree does the strategy generate actionable signals.
A buy arrow is produced when at least one fast EMA (within the configured interval) crosses above a compatible slow EMA while the Williams %R based oscillator escapes from the lower band after staying inside the neutral zone. A sell arrow is generated by the symmetric conditions for bearish crossovers and an exit from the upper band. Signals are executed after the configured number of bars defined by the Signal Bar parameter, just like in the original MetaTrader expert.
Once a position is open the strategy can optionally attach protective stop loss and take profit levels expressed in price steps. Those protections are checked on every closed candle. All trades use the constant Order Volume parameter so the complex money-management rules from the MQL5 version are replaced with a simpler fixed-size approach.
Entry Logic
- Long:
- At least one fast EMA within
[Start Fast, End Fast]rises above a slow EMA within[Start Slow, End Slow]using theStep Periodincrement. - The Williams %R channel, evaluated with risk values in the
[Start Risk, End Risk]range andRisk Step, detects a break above the upper boundary after spending time inside the neutral band. - Optional short positions are closed beforehand when Enable Sell Exits is turned on.
- At least one fast EMA within
- Short:
- Symmetric crossover and Williams %R breakout in the opposite direction.
- Optional long exits occur first when Enable Buy Exits is enabled.
Exit Logic
- Opposite arrows close positions when the corresponding exit flags are enabled (buy arrows close shorts, sell arrows close longs).
- Optional stop loss and take profit levels measured in price steps can close positions earlier if price reaches those thresholds.
Parameters
- Order Volume – trade size used for market orders.
- Candle Type – timeframe for market data (defaults to 4-hour candles).
- Start Fast / End Fast / Step Period – fast EMA range for crossover scan.
- Start Slow / End Slow – slow EMA range paired with the fast EMA values.
- Start Risk / End Risk / Risk Step – Williams %R risk scan boundaries.
- Signal Bar – number of finished bars to wait before executing a signal.
- Stop Loss (pips) – optional stop loss distance in price steps.
- Take Profit (pips) – optional take profit distance in price steps.
- Enable Buy Entries / Enable Sell Entries – allow long or short entries.
- Enable Buy Exits / Enable Sell Exits – enable closing in the opposite direction.
Notes
- The strategy keeps up to 512 recent candles to evaluate the AFStar logic.
- If price steps are not available for the security, a value of 1 is used when calculating stop-loss and take-profit distances.
- Signals are queued so that setting Signal Bar = 0 executes immediately, while higher values delay execution by that many completed bars.
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()