Files
2026-05-02 15:55:04 +02:00

664 lines
22 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//+------------------------------------------------------------------+
//| 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);
}
}
}