Files

664 lines
22 KiB
Plaintext
Raw Permalink Normal View History

2026-05-02 15:55:04 +02:00
//+------------------------------------------------------------------+
//| US500_H1_ArticleEA.mq5 |
//| ai/yt: article-split ONNX (train 20102019 / OOS 20202024) |
//| Train: python train_article_split.py → models/*.onnx |
//| Attach to US500 (or broker equivalent) H1 chart. |
//+------------------------------------------------------------------+
#property copyright "Profitable EA Project"
#property version "1.05"
#property description "Embedded US500 H1 article-split ONNX; scaler from US500_H1_article_split_meta.json"
#include <Trade\Trade.mqh>
#resource "models\\US500_H1_article_split.onnx" as uchar ExtModel[]
#define FEAT_COUNT 24
#define PRED_HIST_CAP 32
#define REL_EPS 1e-9
input group "Model"
input int InpLookback = 48;
input int InpEntryMode = 1;
input double InpProbBuy = 0.18;
input double InpProbSell = 0.18;
input double InpMinBeatHold = 0.0;
input int InpExitMode = 1; // 0=fixed prob; 1/2=close must beat HOLD and stay-in-trade (2 legacy; old 2 vs-HOLD-only removed)
input double InpProbCloseL = 0.18;
input double InpProbCloseS = 0.18;
input double InpMinCloseBeatHold = 0.0;
input int InpMinBarsInTradeModelExit = 1; // min bars before model exit (0=off); pure mode uses 5-class winner
input bool InpPureRelative = true; // true: no prob cutoffs/edges — entry=trio strict winner, exit=5-class strict winner != side
input bool InpUseCloseHeadExit = true; // legacy only when InpPureRelative=false (CL/CS vs HOLD/stay; see InpExitMode)
input bool InpUseDirFlipExit = true; // legacy only when InpPureRelative=false (gap edges InpFlipExitEdge)
input double InpFlipExitEdge = 0.03; // legacy dir-flip min gap (ignored when InpPureRelative)
input int InpMinBarsAfterExit = 6; // after any close, wait this many flat bars before a new entry (0=off)
input int InpCooldownBarsAfterAdverse = 12; // extra flat-bar pause after adverse (ATR) stop; 0 = use only MinBarsAfterExit
input group "Decision (aggregate + sample, lowers trade churn)"
input int InpSampleEveryNBars = 2; // run ONNX / refresh history every N new bars (>=1)
input int InpAggWindow = 4; // rolling mean over last K samples (>=1)
input int InpMinAggSamples = 2; // need this many samples in window before new entries
input int InpMinBarsBetweenEntries = 0; // after an open, wait this many flat bars before next entry (0=off)
input double InpMinDirEdge = 0.03; // legacy entry mode 1 only (ignored when InpPureRelative)
input bool InpRequireStayOverClose = true; // legacy entry (ignored when InpPureRelative)
input group "Session (match Python SESSION_HOUR_OFFSET)"
input int InpSessionHourOffset = 0;
input group "Scaler override (empty = use built-in US500 train split)"
input string InpFeatMinStr = "";
input string InpFeatMaxStr = "";
input group "Risk"
input double InpLotSize = 0.01;
input int InpMagic = 902503;
input int InpSlippage = 30;
input group "Hard exits (fixed ATR in price — optional)"
input bool InpUseAdverseAtrExit = false; // stop by adverse move in ATR multiples (off = model-only risk)
input bool InpUseProfitAtrExit = false; // take-profit in ATR multiples (needs InpTakeProfitATR > 0)
input double InpMaxAdverseATR = 3.5;
input double InpTakeProfitATR = 0.0;
double g_feat_min[FEAT_COUNT];
double g_feat_max[FEAT_COUNT];
CTrade trade;
long g_onnx = INVALID_HANDLE;
datetime g_last_bar = 0;
double g_pred_hist[PRED_HIST_CAP][5];
int g_pred_hist_len = 0;
double g_smooth[5] = {0.2, 0.2, 0.2, 0.2, 0.2};
ulong g_bar_index = 0;
int g_entry_cooldown_bars = 0;
int g_agg_w = 4;
int g_sample_n = 2;
int g_min_agg_samples = 2;
void InitDefaultScalerBounds()
{
// MinMax bounds from ai/yt/models/US500_H1_article_split_meta.json (train-only scaler)
double def_min[FEAT_COUNT] = {
1352.5,
1352.5999755859375,
1347.9000244140625,
1352.0999755859375,
0.0,
0.04497450217604637,
-0.030356179922819138,
-0.04839427396655083,
0.00028562467196024954,
-0.047754231840372086,
1.0,
0.00017100000695791095,
0.0,
0.01168255414813757,
0.0760856345295906,
-0.49618232250213623,
-1.6348180770874023,
-1.731970191001892,
0.000006116794793342706,
0.0,
0.0,
0.0,
0.0,
0.0
};
double def_max[FEAT_COUNT] = {
3250.199951171875,
3251.5,
3249.5,
3250.199951171875,
26050000896.0,
0.887104868888855,
0.09538312256336212,
0.10739167034626007,
0.02898731827735901,
0.036042287945747375,
1.0754634141921997,
6759499776.0,
20.0,
0.9637425541877747,
0.8267387747764587,
0.5573697686195374,
1.2618913650512695,
1.8784747123718262,
0.9100509881973267,
1.0,
1.0,
1.0,
1.0,
1.0
};
for(int i = 0; i < FEAT_COUNT; i++)
{
g_feat_min[i] = def_min[i];
g_feat_max[i] = def_max[i];
}
}
bool ParseFeatCsv(const string s, double &arr[])
{
if(StringLen(s) < 3) return false;
string parts[];
int n = StringSplit(s, ',', parts);
if(n != FEAT_COUNT) return false;
for(int i = 0; i < FEAT_COUNT; i++)
arr[i] = StringToDouble(parts[i]);
return true;
}
int OnInit()
{
InitDefaultScalerBounds();
trade.SetExpertMagicNumber(InpMagic);
trade.SetDeviationInPoints(InpSlippage);
trade.SetTypeFilling(ORDER_FILLING_IOC);
if(StringLen(InpFeatMinStr) > 0 && ParseFeatCsv(InpFeatMinStr, g_feat_min))
Print("US500 Article EA: loaded InpFeatMinStr (24)");
if(StringLen(InpFeatMaxStr) > 0 && ParseFeatCsv(InpFeatMaxStr, g_feat_max))
Print("US500 Article EA: loaded InpFeatMaxStr (24)");
g_onnx = OnnxCreateFromBuffer(ExtModel, ONNX_DEBUG_LOGS);
if(g_onnx == INVALID_HANDLE)
{
Print("OnnxCreateFromBuffer failed ", GetLastError());
return INIT_FAILED;
}
const long inShape[] = {1, InpLookback, FEAT_COUNT};
if(!OnnxSetInputShape(g_onnx, 0, inShape))
{
Print("OnnxSetInputShape failed ", GetLastError());
OnnxRelease(g_onnx);
return INIT_FAILED;
}
const long outShape[] = {1, 5};
if(!OnnxSetOutputShape(g_onnx, 0, outShape))
{
Print("OnnxSetOutputShape failed ", GetLastError());
OnnxRelease(g_onnx);
return INIT_FAILED;
}
g_agg_w = MathMax(1, MathMin(InpAggWindow, PRED_HIST_CAP));
g_sample_n = MathMax(1, InpSampleEveryNBars);
g_min_agg_samples = MathMax(1, MathMin(InpMinAggSamples, g_agg_w));
g_pred_hist_len = 0;
g_bar_index = 0;
g_entry_cooldown_bars = 0;
for(int k = 0; k < 5; k++)
g_smooth[k] = 0.2;
const bool has_atr = InpUseAdverseAtrExit || (InpUseProfitAtrExit && InpTakeProfitATR > 0.0);
const bool has_model_exit = InpPureRelative || InpUseCloseHeadExit || InpUseDirFlipExit;
if(!has_atr && !has_model_exit)
Print("US500_H1_ArticleEA: WARNING — no exit path enabled (enable InpPureRelative and/or legacy exits / ATR)");
Print("US500_H1_ArticleEA: ONNX OK. Chart TF=", EnumToString(PERIOD_CURRENT), "; Lookback=", InpLookback,
" sampleEvery=", g_sample_n, " aggWindow=", g_agg_w, " minAggSamples=", g_min_agg_samples,
" pureRelative=", InpPureRelative,
" entryCooldownBars=", InpMinBarsBetweenEntries, " minDirEdge=", InpMinDirEdge,
" stayOverClose=", InpRequireStayOverClose,
" exitMode=", InpExitMode, " minBarsInTradeModelExit=", InpMinBarsInTradeModelExit,
" closeHeadExit=", InpUseCloseHeadExit, " dirFlipExit=", InpUseDirFlipExit, " flipExitEdge=", InpFlipExitEdge,
" minBarsAfterExit=", InpMinBarsAfterExit, " cooldownAfterAdverse=", InpCooldownBarsAfterAdverse,
" useAdverseATR=", InpUseAdverseAtrExit, " useProfitATR=", InpUseProfitAtrExit,
" maxAdverseATR=", InpMaxAdverseATR, " takeProfitATR=", InpTakeProfitATR);
return INIT_SUCCEEDED;
}
void OnDeinit(const int r)
{
if(g_onnx != INVALID_HANDLE) OnnxRelease(g_onnx);
}
double AtrNow()
{
double b[];
ArraySetAsSeries(b, true);
int h = iATR(_Symbol, PERIOD_CURRENT, 14);
if(h == INVALID_HANDLE) return 0;
if(CopyBuffer(h, 0, 0, 2, b) < 1) { IndicatorRelease(h); return 0; }
double v = b[0];
IndicatorRelease(h);
return v;
}
bool AdverseExit(const long type, const double open_price)
{
if(!InpUseAdverseAtrExit || InpMaxAdverseATR <= 0.0)
return false;
double atr = AtrNow();
if(atr <= 0) return false;
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(type == POSITION_TYPE_BUY)
{
double adv = (open_price - bid) / atr;
return adv >= InpMaxAdverseATR;
}
double adv = (ask - open_price) / atr;
return adv >= InpMaxAdverseATR;
}
bool ProfitExit(const long type, const double open_price)
{
if(!InpUseProfitAtrExit || InpTakeProfitATR <= 0.0)
return false;
double atr = AtrNow();
if(atr <= 0.0) return false;
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(type == POSITION_TYPE_BUY)
return (bid - open_price) >= InpTakeProfitATR * atr;
return (open_price - ask) >= InpTakeProfitATR * atr;
}
bool ModelCloseLong(const double p0, const double p1, const double p3)
{
if(InpExitMode == 0)
return (p3 >= InpProbCloseL);
// Modes 1/2 (and default): close-long must beat HOLD and stay-long (BUY). Old mode-2 "vs HOLD only" fired almost every bar on softmax.
return (p3 > p0 + InpMinCloseBeatHold && p3 > p1);
}
bool ModelCloseShort(const double p0, const double p2, const double p4)
{
if(InpExitMode == 0)
return (p4 >= InpProbCloseS);
return (p4 > p0 + InpMinCloseBeatHold && p4 > p2);
}
bool ModelDirFlipExitLong(const double p0, const double p1, const double p2)
{
if(!InpUseDirFlipExit)
return false;
const double e = MathMax(0.0, InpFlipExitEdge);
return (p2 > p1 + e && p2 > p0 + InpMinBeatHold);
}
bool ModelDirFlipExitShort(const double p0, const double p1, const double p2)
{
if(!InpUseDirFlipExit)
return false;
const double e = MathMax(0.0, InpFlipExitEdge);
return (p1 > p2 + e && p1 > p0 + InpMinBeatHold);
}
int TrioStrictWinner012(const double p0, const double p1, const double p2)
{
if(p0 > p1 + REL_EPS && p0 > p2 + REL_EPS)
return 0;
if(p1 > p0 + REL_EPS && p1 > p2 + REL_EPS)
return 1;
if(p2 > p0 + REL_EPS && p2 > p1 + REL_EPS)
return 2;
return -1;
}
int FiveStrictWinner01234(const double p0, const double p1, const double p2, const double p3, const double p4)
{
const double p[5] = {p0, p1, p2, p3, p4};
int best = 0;
for(int k = 1; k < 5; k++)
if(p[k] > p[best])
best = k;
const double m = p[best];
int cnt = 0;
for(int k = 0; k < 5; k++)
if(p[k] + REL_EPS >= m)
cnt++;
if(cnt != 1)
return -1;
return best;
}
int PositionBarsInTrade()
{
if(!PositionSelect(_Symbol))
return 0;
const datetime tOpen = (datetime)PositionGetInteger(POSITION_TIME);
const int sh = iBarShift(_Symbol, PERIOD_CURRENT, tOpen, false);
if(sh < 0)
return 9999;
return sh + 1;
}
void ApplyExitCooldown(const bool adverse_stop)
{
int b = MathMax(0, InpMinBarsAfterExit);
if(adverse_stop)
b = MathMax(b, MathMax(0, InpCooldownBarsAfterAdverse));
if(b > 0)
g_entry_cooldown_bars = MathMax(g_entry_cooldown_bars, b);
}
void PushPrediction(const double p0, const double p1, const double p2, const double p3, const double p4, const int maxKeep)
{
for(int i = PRED_HIST_CAP - 1; i > 0; i--)
for(int k = 0; k < 5; k++)
g_pred_hist[i][k] = g_pred_hist[i - 1][k];
g_pred_hist[0][0] = p0;
g_pred_hist[0][1] = p1;
g_pred_hist[0][2] = p2;
g_pred_hist[0][3] = p3;
g_pred_hist[0][4] = p4;
int cap = MathMax(1, MathMin(maxKeep, PRED_HIST_CAP));
g_pred_hist_len = MathMin(g_pred_hist_len + 1, cap);
}
void RecomputeSmooth(const int aggWindow)
{
int w = MathMax(1, MathMin(aggWindow, PRED_HIST_CAP));
int n = MathMin(w, g_pred_hist_len);
if(n < 1)
return;
for(int k = 0; k < 5; k++)
{
double s = 0.0;
for(int i = 0; i < n; i++)
s += g_pred_hist[i][k];
g_smooth[k] = s / (double)n;
}
}
void ScaleFeatures(const float &raw[], float &out[])
{
for(int f = 0; f < FEAT_COUNT; f++)
{
double den = g_feat_max[f] - g_feat_min[f];
if(den < 1e-12) den = 1e-12;
double x = (double)raw[f] - g_feat_min[f];
out[f] = (float)MathMax(0.0, MathMin(1.0, x / den));
}
}
bool PrepareMatrix(matrixf &M)
{
int L = InpLookback;
double open[], high[], low[], close[];
long vol[];
datetime bt[];
ArraySetAsSeries(open, true);
ArraySetAsSeries(high, true);
ArraySetAsSeries(low, true);
ArraySetAsSeries(close, true);
ArraySetAsSeries(vol, true);
ArraySetAsSeries(bt, true);
int need = L + 55;
if(CopyOpen(_Symbol, PERIOD_CURRENT, 0, need, open) < L) return false;
if(CopyHigh(_Symbol, PERIOD_CURRENT, 0, need, high) < L) return false;
if(CopyLow(_Symbol, PERIOD_CURRENT, 0, need, low) < L) return false;
if(CopyClose(_Symbol, PERIOD_CURRENT, 0, need, close) < L) return false;
if(CopyTickVolume(_Symbol, PERIOD_CURRENT, 0, need, vol) < L) return false;
if(CopyTime(_Symbol, PERIOD_CURRENT, 0, need, bt) < L) return false;
double rsi7[], rsi14[], rsi21[], ema20[], ema50[], atr[];
ArraySetAsSeries(rsi7, true);
ArraySetAsSeries(rsi14, true);
ArraySetAsSeries(rsi21, true);
ArraySetAsSeries(ema20, true);
ArraySetAsSeries(ema50, true);
ArraySetAsSeries(atr, true);
int h7 = iRSI(_Symbol, PERIOD_CURRENT, 7, PRICE_CLOSE);
int h14 = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE);
int h21 = iRSI(_Symbol, PERIOD_CURRENT, 21, PRICE_CLOSE);
int hE20 = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_EMA, PRICE_CLOSE);
int hE50 = iMA(_Symbol, PERIOD_CURRENT, 50, 0, MODE_EMA, PRICE_CLOSE);
int hA = iATR(_Symbol, PERIOD_CURRENT, 14);
if(h7 == INVALID_HANDLE || h14 == INVALID_HANDLE || h21 == INVALID_HANDLE ||
hE20 == INVALID_HANDLE || hE50 == INVALID_HANDLE || hA == INVALID_HANDLE)
return false;
if(CopyBuffer(h7, 0, 0, need, rsi7) < L ||
CopyBuffer(h14, 0, 0, need, rsi14) < L ||
CopyBuffer(h21, 0, 0, need, rsi21) < L ||
CopyBuffer(hE20, 0, 0, need, ema20) < L ||
CopyBuffer(hE50, 0, 0, need, ema50) < L ||
CopyBuffer(hA, 0, 0, need, atr) < L)
{
IndicatorRelease(h7); IndicatorRelease(h14); IndicatorRelease(h21);
IndicatorRelease(hE20); IndicatorRelease(hE50); IndicatorRelease(hA);
return false;
}
IndicatorRelease(h7); IndicatorRelease(h14); IndicatorRelease(h21);
IndicatorRelease(hE20); IndicatorRelease(hE50); IndicatorRelease(hA);
M.Resize(L, FEAT_COUNT);
const double RSI_OB = 70.0;
const double RSI_OS = 30.0;
for(int i = 0; i < L; i++)
{
double vma = 0;
int cnt = 0;
for(int k = i; k < i + 20 && k < ArraySize(vol); k++) { vma += (double)vol[k]; cnt++; }
if(cnt < 1) cnt = 1;
vma /= cnt;
double r0 = rsi14[i];
double r1 = (i + 1 < ArraySize(rsi14)) ? rsi14[i + 1] : r0;
double r2 = (i + 2 < ArraySize(rsi14)) ? rsi14[i + 2] : r1;
double rv7 = rsi7[i];
double rv21 = rsi21[i];
double spread = (r0 - rv7) / 50.0;
if(spread > 1.0) spread = 1.0;
if(spread < -1.0) spread = -1.0;
double vel = (r0 - r1) / 25.0;
double acc = ((r0 - r1) - (r1 - r2)) / 25.0;
double dist_mid = MathAbs(r0 - 50.0) / 50.0;
double c_ob = (r1 < RSI_OB && r0 >= RSI_OB) ? 1.0 : 0.0;
double c_os = (r1 > RSI_OS && r0 <= RSI_OS) ? 1.0 : 0.0;
double c50u = (r1 < 50.0 && r0 >= 50.0) ? 1.0 : 0.0;
double c50d = (r1 > 50.0 && r0 <= 50.0) ? 1.0 : 0.0;
MqlDateTime st;
TimeToStruct(bt[i], st);
int hr = (st.hour + InpSessionHourOffset) % 24;
if(hr < 0) hr += 24;
double asian = (hr >= 0 && hr < 8) ? 1.0 : 0.0;
float raw[FEAT_COUNT];
raw[0] = (float)open[i];
raw[1] = (float)high[i];
raw[2] = (float)low[i];
raw[3] = (float)close[i];
raw[4] = (float)((double)vol[i] / 1000000.0);
raw[5] = (float)(r0 / 100.0);
raw[6] = (float)((ema20[i] - close[i]) / close[i]);
raw[7] = (float)((ema50[i] - close[i]) / close[i]);
raw[8] = (float)(atr[i] / close[i]);
double pc = (i < L - 1) ? (close[i] - close[i + 1]) / close[i + 1] : 0.0;
raw[9] = (float)pc;
raw[10] = (float)(high[i] / low[i]);
raw[11] = (float)(vma / 1000000.0);
raw[12] = (float)(vma > 0 ? (double)vol[i] / vma : 1.0);
raw[13] = (float)(rv7 / 100.0);
raw[14] = (float)(rv21 / 100.0);
raw[15] = (float)spread;
raw[16] = (float)vel;
raw[17] = (float)acc;
raw[18] = (float)dist_mid;
raw[19] = (float)c_ob;
raw[20] = (float)c_os;
raw[21] = (float)c50u;
raw[22] = (float)c50d;
raw[23] = (float)asian;
float sc[FEAT_COUNT];
ScaleFeatures(raw, sc);
for(int j = 0; j < FEAT_COUNT; j++)
M[i][j] = sc[j];
}
return true;
}
void OnTick()
{
datetime t = iTime(_Symbol, PERIOD_CURRENT, 0);
if(t == g_last_bar) return;
g_last_bar = t;
const bool had_pos = PositionSelect(_Symbol);
const bool flat = !had_pos;
if(flat && g_entry_cooldown_bars > 0)
g_entry_cooldown_bars--;
g_bar_index++;
const bool do_sample = (g_sample_n < 2) || ((g_bar_index % (ulong)g_sample_n) == 0);
bool fresh_predict = false;
if(do_sample)
{
matrixf Min;
if(!PrepareMatrix(Min))
{
Print("US500 Article EA: PrepareMatrix failed");
if(!had_pos)
return;
}
else
{
vectorf out;
out.Resize(5);
if(!OnnxRun(g_onnx, ONNX_NO_CONVERSION, Min, out))
{
Print("OnnxRun failed ", GetLastError());
if(!had_pos)
return;
}
else
{
PushPrediction(out[0], out[1], out[2], out[3], out[4], g_agg_w);
RecomputeSmooth(g_agg_w);
fresh_predict = true;
Print("US500 Article H1 raw HOLD=", out[0], " BUY=", out[1], " SELL=", out[2], " CL=", out[3], " CS=", out[4],
" | smooth HOLD=", g_smooth[0], " BUY=", g_smooth[1], " SELL=", g_smooth[2], " CL=", g_smooth[3], " CS=", g_smooth[4]);
}
}
}
const double p0 = g_smooth[0];
const double p1 = g_smooth[1];
const double p2 = g_smooth[2];
const double p3 = g_smooth[3];
const double p4 = g_smooth[4];
if(flat)
{
if(!do_sample || !fresh_predict)
return;
if(g_pred_hist_len < g_min_agg_samples)
return;
if(g_entry_cooldown_bars > 0)
return;
if(InpEntryMode == 1)
{
if(InpPureRelative)
{
const int w3 = TrioStrictWinner012(p0, p1, p2);
if(w3 == 1)
{
if(trade.Buy(InpLotSize, _Symbol, 0, 0, 0, "US500 article BUY"))
g_entry_cooldown_bars = MathMax(0, InpMinBarsBetweenEntries);
}
else if(w3 == 2)
{
if(trade.Sell(InpLotSize, _Symbol, 0, 0, 0, "US500 article SELL"))
g_entry_cooldown_bars = MathMax(0, InpMinBarsBetweenEntries);
}
}
else
{
double dir = MathMax(p1, p2);
if(dir <= p0 + InpMinBeatHold)
return;
const double edge = MathMax(0.0, InpMinDirEdge);
const bool stay_ok_buy = (!InpRequireStayOverClose) || (p1 > p3);
const bool stay_ok_sell = (!InpRequireStayOverClose) || (p2 > p4);
if(p1 >= p2 && p1 > p0 + InpMinBeatHold && (p1 - p2) >= edge && stay_ok_buy)
{
if(trade.Buy(InpLotSize, _Symbol, 0, 0, 0, "US500 article BUY"))
g_entry_cooldown_bars = MathMax(0, InpMinBarsBetweenEntries);
}
else if(p2 > p1 && p2 > p0 + InpMinBeatHold && (p2 - p1) >= edge && stay_ok_sell)
{
if(trade.Sell(InpLotSize, _Symbol, 0, 0, 0, "US500 article SELL"))
g_entry_cooldown_bars = MathMax(0, InpMinBarsBetweenEntries);
}
}
}
else
{
if(p1 >= InpProbBuy && p1 >= p2)
{
if(trade.Buy(InpLotSize, _Symbol, 0, 0, 0, "US500 article BUY"))
g_entry_cooldown_bars = MathMax(0, InpMinBarsBetweenEntries);
}
else if(p2 >= InpProbSell && p2 > p1)
{
if(trade.Sell(InpLotSize, _Symbol, 0, 0, 0, "US500 article SELL"))
g_entry_cooldown_bars = MathMax(0, InpMinBarsBetweenEntries);
}
}
return;
}
long typ = (long)PositionGetInteger(POSITION_TYPE);
double opn = PositionGetDouble(POSITION_PRICE_OPEN);
if(AdverseExit(typ, opn))
{
if(trade.PositionClose(_Symbol))
ApplyExitCooldown(true);
return;
}
if(ProfitExit(typ, opn))
{
if(trade.PositionClose(_Symbol))
ApplyExitCooldown(false);
return;
}
const int bars_in = PositionBarsInTrade();
const bool allow_model_exit = (InpMinBarsInTradeModelExit <= 0) || (bars_in >= InpMinBarsInTradeModelExit);
if(allow_model_exit)
{
bool want_close = false;
if(InpPureRelative)
{
const int w5 = FiveStrictWinner01234(p0, p1, p2, p3, p4);
if(typ == POSITION_TYPE_BUY)
want_close = (w5 != -1 && w5 != 1);
else
want_close = (w5 != -1 && w5 != 2);
}
else
{
if(typ == POSITION_TYPE_BUY)
{
const bool head = InpUseCloseHeadExit && ModelCloseLong(p0, p1, p3);
const bool flip = ModelDirFlipExitLong(p0, p1, p2);
want_close = (head || flip);
}
else
{
const bool head = InpUseCloseHeadExit && ModelCloseShort(p0, p2, p4);
const bool flip = ModelDirFlipExitShort(p0, p1, p2);
want_close = (head || flip);
}
}
if(want_close)
{
if(trade.PositionClose(_Symbol))
ApplyExitCooldown(false);
}
}
}