2483 lines
87 KiB
Plaintext
2483 lines
87 KiB
Plaintext
//+------------------------------------------------------------------+
|
||
//| RsiMomentumEA.mq5 |
|
||
//| EA tự động — logic độc lập (không đọc RsiMomentumIndicator). |
|
||
//| RSI×WMA45 + 5phase | ATR↑ ADX trend EMA200 phiên | Limit 50% body |
|
||
//+------------------------------------------------------------------+
|
||
#property copyright "RsiMomentumEA"
|
||
#property version "4.31"
|
||
|
||
#include <Trade/Trade.mqh>
|
||
#include <RsiMom/TradeJournal.mqh>
|
||
#include <RsiMom/PhaseEntry.mqh>
|
||
#include <RsiMom/AdxFilter.mqh>
|
||
#include <RsiMom/SwingStructure.mqh>
|
||
#include <RsiMom/SignalDebug.mqh>
|
||
|
||
//--- Cơ chế vào lệnh (switch test — logic Limit giữ nguyên trong TradeExecuteLimitOrder)
|
||
enum ENUM_RSI_MOM_ENTRY_MODE
|
||
{
|
||
RSI_MOM_ENTRY_LIMIT_BODY50 = 0, // Limit @ 50% thân nến tín hiệu (mặc định)
|
||
RSI_MOM_ENTRY_MARKET = 1 // Market ngay khi nến mới sau tín hiệu
|
||
};
|
||
|
||
//--- Input (khớp Indicators/RsiMomentumIndicator/Lib/Inputs.mqh)
|
||
input group "Chỉ báo"
|
||
input int InpRSIPeriod = 14;
|
||
input int InpEMA9Period = 9;
|
||
input int InpWMA45Period = 45;
|
||
input int InpEMATrendPeriod = 200;
|
||
|
||
input group "Bộ lọc tín hiệu (RSI + ATR + trend EMA200)"
|
||
input bool InpTrendFilterEnabled = true; // BUY: close > EMA200 | SELL: close < EMA200
|
||
input int InpTrendConfirmBars = 1; // BUY: N close > EMA200 | SELL: N close < EMA200
|
||
input bool InpAtrExpFilterEnabled = false; // tắt tạm — test riêng ADX
|
||
input int InpAtrExpPeriod = 14;
|
||
input int InpAtrExpCompareBars = 3; // so ATR[shift] vs ATR[shift+N]
|
||
input double InpAtrExpMinRatio = 1.005; // ≥1.005 = +0.5% (tối ưu 1.003–1.02)
|
||
input int InpAtrExpRiseBars = 2; // ATR tăng liên tiếp N nến (1 = lỏng hơn)
|
||
|
||
input group "ADX — trend mạnh (pullback hiệu quả)"
|
||
input bool InpAdxFilterEnabled = true; // ADX + hướng +DI/-DI tại nến tín hiệu
|
||
input int InpAdxPeriod = 14;
|
||
input double InpAdxMinLevel = 22.0; // ADX >= ngưỡng (sideway ~<20, trend 22–35+)
|
||
input double InpAdxMaxLevel = 0.0; // 0=tắt; ví dụ 45 tránh trend quá già
|
||
input bool InpAdxRequireDiDirection = true; // BUY +DI>-DI | SELL -DI>+DI
|
||
input double InpAdxMinDiSpread = 0.0; // |+DI−(-DI)| tối thiểu (5–10 = chặt hơn)
|
||
input int InpAdxRiseBars = 0; // ADX tăng vs N nến trước (0=tắt, 1–2 bật)
|
||
|
||
input group "2 swing — đáy tăng / đỉnh giảm (thân nến)"
|
||
input bool InpSwingStructFilterEnabled = true;
|
||
input int InpSwingStructRange = 2; // pivot: N nến mỗi bên mỗi đáy/đỉnh
|
||
input int InpSwingStructLookback = 120; // quét tối đa N nến trước tín hiệu
|
||
input double InpSwingStructTolPts = 0.0; // cho phép 2 đáy/đỉnh bằng nhau (points)
|
||
|
||
input group "Lọc RSI quá mua / quá bán (nến tín hiệu)"
|
||
input bool InpRsiObOsFilterEnabled = true; // BUY khi RSI<70 | SELL khi RSI>30
|
||
input double InpRSIOverbought = 70.0; // RSI ≥ ngưỡng → bỏ BUY
|
||
input double InpRSIOversold = 30.0; // RSI ≤ ngưỡng → bỏ SELL
|
||
|
||
input group "Entry 5 phase (mở rộng → cuộn EMA9 → EMA9 hướng → WMA45 phẳng → cắt gần)"
|
||
input bool InpPhaseFilterEnabled = true;
|
||
input int InpPhaseExpandLookback = 25; // P1: quét mở rộng 3 đường
|
||
input double InpPhaseMinExpandSpread = 10.0; // P1: min max(WMA45-RSI) pt RSI [tối ưu ~6–18, step 1]
|
||
input int InpPhaseCoilLookback = 12; // P2: quét cuộn trước nến tín hiệu
|
||
input int InpPhaseMinRsiEma9Cross = 2; // P2: RSI cắt EMA9 ≥ N lần (chống xuyên 1 lần)
|
||
input double InpPhaseCoilBand = 6.0; // P2: |RSI-EMA9| ≤ band = quanh EMA9
|
||
input int InpPhaseEma9SlopeBars = 2; // P3: cửa sổ so sánh EMA9 (nhỏ hơn = lỏng hơn)
|
||
input double InpPhaseEma9SlopeTol = 1.5; // P3: cho phép EMA9 lệch ngược tối đa (pt RSI)
|
||
input int InpPhaseWmaFlatBars = 4; // P4: WMA45 phẳng — cửa sổ slope
|
||
input double InpPhaseWmaWasSlopeMin = 0.08; // P4: dốc tối thiểu quá khứ (khi RelaxPrior=false)
|
||
input double InpPhaseWmaFlatMaxSlope = 0.55; // P4: |slope| WMA45 gần 0 tại signal (lớn hơn = lỏng)
|
||
input bool InpPhaseWmaRelaxPrior = true; // P4: chỉ cần WMA45 từng đi đúng hướng, không cần dốc mạnh
|
||
input double InpPhaseMaxEma9WmaGap = 16.0; // P5: EMA9–WMA45 tối đa khi cắt (lớn hơn = lỏng)
|
||
|
||
input group "Cơ chế vào lệnh (switch test)"
|
||
input ENUM_RSI_MOM_ENTRY_MODE InpEntryMode = RSI_MOM_ENTRY_MARKET;
|
||
|
||
input group "Entry — Limit 50% thân nến (chỉ InpEntryMode=Limit)"
|
||
input int InpSignalBarShift = 1; // nến tín hiệu (1 = nến vừa đóng)
|
||
|
||
input group "Quản lý Limit pending (chỉ InpEntryMode=Limit)"
|
||
input int InpLimitExpireBars = 40; // hủy Limit nếu không khớp sau N nến
|
||
|
||
input group "Debug — đánh dấu RSI×WMA45 (tắt = backtest nhanh)"
|
||
input bool InpDebugMarkSignals = false; // mọi cross: OK xanh | SIG vàng | SKIP đỏ
|
||
input int InpDebugMarkMaxBars = 400; // chỉ tạo mới trong N nến gần nhất
|
||
input bool InpDebugLogExperts = false; // 1 dòng Experts / nến tín hiệu
|
||
input bool InpDebugHoverHint = false; // rê chuột lên dấu X
|
||
|
||
input group "Mũi tên giao cắt"
|
||
input color InpArrowUpColor = clrLime;
|
||
input color InpArrowDownColor = clrTomato;
|
||
input int InpArrowOffsetPts = 30;
|
||
input int InpArrowSize = 1;
|
||
|
||
input group "Panel trạng thái (góc trên-trái)"
|
||
input bool InpShowPanel = false; // tắt = tester nhanh hơn
|
||
input int InpPanelFontSize = 8;
|
||
input int InpPanelLinePad = 14; // khoảng cách dọc giữa các dòng
|
||
input int InpPanelLeftMargin = 8;
|
||
input int InpPanelTopMargin = 10;
|
||
input color InpPanelColorEMA9 = clrGold;
|
||
input color InpPanelColorWMA45 = clrDodgerBlue;
|
||
|
||
input group "Cảnh báo / Notification (khi có entry mới)"
|
||
input bool InpAlertPush = false;
|
||
input bool InpAlertPopup = false;
|
||
input bool InpAlertSound = false;
|
||
input string InpSoundBuy = "alert.wav";
|
||
input string InpSoundSell = "alert2.wav";
|
||
input bool InpAlertEmail = false;
|
||
input bool InpAlertOnBar0 = false;
|
||
|
||
input group "Giao dịch tự động"
|
||
input bool InpTradeEnabled = true;
|
||
input ulong InpMagic = 202602;
|
||
input double InpRiskPercent = 1; // % balance mất nếu SL khớp (theo lot tính từ SL)
|
||
input double InpRewardRiskRatio = 1.05; // R:R — TP = tỷ lệ × khoảng SL (2.0 = 1:2)
|
||
input int InpSwingMaxBars = 30; // quét swing pivot / fallback min-max
|
||
input int InpSlippagePoints = 30;
|
||
input bool InpOnePositionFlat = true;
|
||
|
||
input group "Phiên & spread (chỉ trade khi pass)"
|
||
input bool InpSessionFilterEnabled = true; // London/NY + chặn giao phiên
|
||
input bool InpEnvUseUtc = false; // false = giờ server/broker
|
||
input int InpLondonStartHour = 8;
|
||
input int InpLondonEndHour = 17;
|
||
input int InpNYStartHour = 13;
|
||
input int InpNYEndHour = 22;
|
||
input int InpSessionAvoidLastMin = 15; // bỏ N phút cuối mỗi phiên
|
||
input bool InpTransitionBlockEnabled = true; // chặn giao phiên / thanh khoản thấp
|
||
input int InpTransition1StartHour = 7; // ví dụ trước London
|
||
input int InpTransition1EndHour = 8;
|
||
input int InpTransition2StartHour = 21; // ví dụ đóng NY / rollover
|
||
input int InpTransition2EndHour = 22;
|
||
input bool InpSpreadFilterEnabled = true;
|
||
input int InpMaxSpreadPoints = 60; // SYMBOL_SPREAD (points) tối đa
|
||
input bool InpSpreadSkipInTester = true; // Tester: bỏ lọc spread (spread cố định thường quá cao)
|
||
input bool InpTesterCalcOnNewBarOnly = true; // Tester: OnCalculate chỉ khi nến mới (tránh chậm dần)
|
||
|
||
input group "Quản lý lệnh mở @ 1R"
|
||
input bool InpManageAt1R = false; // @1R: chốt một phần + dời SL về entry
|
||
input double InpPartialCloseRatio = 0.5; // tỷ lệ volume chốt khi đạt 1R (0.5 = 50%)
|
||
input int InpBreakevenOffsetPts = 0; // SL tại entry ± point (0 = đúng entry)
|
||
|
||
input group "SL buffer theo ATR (đẩy SL xa đáy/đỉnh swing)"
|
||
input bool InpSlAtrBufferEnabled = true;
|
||
input int InpSlAtrPeriod = 14;
|
||
input double InpSlAtrMultiplier = 0.5; // khoảng cách thêm = ATR(shift 1) × hệ số
|
||
input bool InpSlAtrAddSpread = true; // cộng thêm buffer spread vào SL
|
||
|
||
input group "Xuất CSV thống kê (FILE_COMMON)"
|
||
input bool InpExportTradeJournal = false; // bật lại khi cần phân tích CSV (chậm hơn một chút)
|
||
input bool InpJournalResetOnInit = true; // Tester: xóa CSV cũ mỗi lần chạy backtest mới
|
||
|
||
input group "Thống kê (góc dưới-trái chart)"
|
||
input bool InpShowStats = true; // tắt = tester nhanh hơn
|
||
input int InpStatFontSize = 9;
|
||
input int InpStatLinePad = 26; // khoảng cách dọc giữa các dòng (pixel)
|
||
input int InpStatBottomMargin = 28; // lề dưới block thống kê
|
||
input color InpStatColor = clrSilver;
|
||
|
||
//--- Buffers & state (trùng State.mqh)
|
||
double buf_RSI[];
|
||
double buf_EMA9[];
|
||
double buf_WMA45[];
|
||
double buf_Signal[];
|
||
double buf_EMA200[];
|
||
double buf_Trend[];
|
||
|
||
int h_RSI = INVALID_HANDLE;
|
||
int h_EMA9 = INVALID_HANDLE;
|
||
int h_WMA45 = INVALID_HANDLE;
|
||
int h_EMA200 = INVALID_HANDLE;
|
||
int h_ATR = INVALID_HANDLE;
|
||
int h_ATR_Regime = INVALID_HANDLE;
|
||
int h_ADX = INVALID_HANDLE;
|
||
|
||
const string OBJ_PREFIX = "RsiMomEA_";
|
||
const string DBG_PREFIX = OBJ_PREFIX + "DBG_";
|
||
#define PANEL_LINE_COUNT 25
|
||
#define PANEL_IDX_MARKET 19 // dòng 19+ = RSI / signal (sau block trạng thái)
|
||
const string PNL_PREFIX = OBJ_PREFIX + "pnl_";
|
||
const string EA_VERSION_STR = "4.31";
|
||
|
||
datetime g_dbgLogBarTime = 0; // chống spam Experts: 1 dòng / (nến, BUY|SELL)
|
||
int g_dbgLogSide = 0; // 1=BUY, -1=SELL
|
||
|
||
const string STAT_PREFIX = "RsiMomEA_ST_";
|
||
const string STAT_L1 = STAT_PREFIX + "line1";
|
||
const string STAT_L2 = STAT_PREFIX + "line2";
|
||
const string STAT_L3 = STAT_PREFIX + "line3";
|
||
|
||
long g_statExitDeals = 0;
|
||
long g_statSL = 0;
|
||
long g_statTP = 0;
|
||
long g_statOther = 0;
|
||
long g_statWins = 0;
|
||
double g_statSumProfit = 0.0;
|
||
|
||
datetime g_lastAlertBuyBar = 0;
|
||
datetime g_lastAlertSellBar = 0;
|
||
bool g_firstCalc = true;
|
||
static int g_prevCalculated = 0;
|
||
|
||
datetime g_tradeBarAnchor = 0;
|
||
datetime g_pendingPlacedBarTime = 0;
|
||
|
||
ulong g_pmTicket = 0;
|
||
double g_pmInitialRisk = 0.0;
|
||
bool g_pmAt1RDone = false;
|
||
|
||
CTrade g_trade;
|
||
|
||
long ActChart() { return ChartID(); }
|
||
|
||
bool IsStrategyTester()
|
||
{
|
||
return (bool)MQLInfoInteger(MQL_TESTER);
|
||
}
|
||
|
||
bool IsTesterVisualMode()
|
||
{
|
||
return IsStrategyTester() && (bool)MQLInfoInteger(MQL_VISUAL_MODE);
|
||
}
|
||
|
||
bool DebugMarksEffective()
|
||
{
|
||
if(!InpDebugMarkSignals)
|
||
return false;
|
||
// Tester không visual: không vẽ object (tránh hàng nghìn object làm chậm)
|
||
if(IsStrategyTester() && !IsTesterVisualMode())
|
||
return false;
|
||
return true;
|
||
}
|
||
|
||
bool ChartRedrawEffective()
|
||
{
|
||
return !IsStrategyTester() || IsTesterVisualMode();
|
||
}
|
||
|
||
void SetTradeFillingFromSymbol();
|
||
double Sl_GetBufferDistance(const int atrShift = 1);
|
||
bool NearestSwingSlTp(const bool isBuy, const double entry, const int dig, double &sl, double &tp);
|
||
bool StopsValid(const bool isBuy, const double price, const double sl, const double tp);
|
||
int CountMyMagicPositions();
|
||
int CountMyMagicPendingOrders();
|
||
bool HasMyMagicPositionOrPending();
|
||
double NormalizeLots(double v);
|
||
double VolumeForRiskPercent(const bool isBuy, const double entryRef, const double slPrice);
|
||
void TradeTryOnBarOpen(const int calcRet);
|
||
void TradeExecuteOrder(const bool isBuy);
|
||
void TradeExecuteLimitOrder(const bool isBuy);
|
||
void TradeExecuteMarketOrder(const bool isBuy);
|
||
bool EntryModeIsLimit();
|
||
bool EntryModeIsMarket();
|
||
string EntryModeLabel();
|
||
bool Signal_BodyMidPrice(const int shift, double &midOut);
|
||
bool LimitPriceValid(const bool isBuy, const double limitPx);
|
||
void Pending_ManageExpiry();
|
||
void Pending_EnvCancelIfBad();
|
||
bool Pending_CancelMine();
|
||
void Position_ResetPmState();
|
||
ulong Position_FindMyTicket();
|
||
void Position_ManageAt1R();
|
||
bool AtrExp_GetAt(const int shift, double &atr);
|
||
bool AtrExp_IsExpandingAt(const int shift);
|
||
bool AtrExp_AllowsAt(const int shift);
|
||
bool AtrExp_AllowsNow();
|
||
bool Adx_AllowsBuyAt(const int shift, string &why);
|
||
bool Adx_AllowsSellAt(const int shift, string &why);
|
||
void Adx_GetAtBar(const int shift, double &adx, double &plusDi, double &minusDi);
|
||
bool SwingStruct_AllowsBuyAt(const int shift, string &why, double &bodyOld, double &bodyNew);
|
||
bool SwingStruct_AllowsSellAt(const int shift, string &why, double &bodyOld, double &bodyNew);
|
||
int Env_CurrentSpreadPts();
|
||
bool Env_SpreadAllows();
|
||
bool Env_AllowsSessionAt(const datetime t, string &why);
|
||
bool Env_AllowsTradeNow(string &why);
|
||
bool Env_AllowsTradeAtBar(const int sigShift, string &why);
|
||
bool Trend_IsUpAt(const int shift, const int trendN, const double &closeArr[], const double &ema200Arr[]);
|
||
bool Trend_IsDownAt(const int shift, const int trendN, const double &closeArr[], const double &ema200Arr[]);
|
||
bool Signal_Ema9CoreBuyOkAt(const int shift);
|
||
bool Signal_Ema9CoreSellOkAt(const int shift);
|
||
string Signal_ReasonBuy(const bool trendUp, const bool ema9CoreOk, const bool phaseOk, const string phaseFail,
|
||
const bool atrExpOk, const bool envOk, const bool valid);
|
||
string Signal_ReasonSell(const bool trendDown, const bool ema9CoreOk, const bool phaseOk, const string phaseFail,
|
||
const bool atrExpOk, const bool envOk, const bool valid);
|
||
void Stats_CreateObjects();
|
||
void Stats_UpdateDisplay();
|
||
void OnTradeTransaction(const MqlTradeTransaction &trans,
|
||
const MqlTradeRequest &request,
|
||
const MqlTradeResult &result);
|
||
|
||
//+------------------------------------------------------------------+
|
||
bool Handles_CreateAll()
|
||
{
|
||
h_RSI = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE);
|
||
if (h_RSI == INVALID_HANDLE)
|
||
{
|
||
Print("[RsiMomEA] Không tạo được handle RSI");
|
||
return false;
|
||
}
|
||
h_EMA9 = iMA(_Symbol, _Period, InpEMA9Period, 0, MODE_EMA, h_RSI);
|
||
if (h_EMA9 == INVALID_HANDLE)
|
||
{
|
||
Print("[RsiMomEA] Không tạo được handle EMA9(RSI)");
|
||
return false;
|
||
}
|
||
h_WMA45 = iMA(_Symbol, _Period, InpWMA45Period, 0, MODE_LWMA, h_RSI);
|
||
if (h_WMA45 == INVALID_HANDLE)
|
||
{
|
||
Print("[RsiMomEA] Không tạo được handle WMA45(RSI)");
|
||
return false;
|
||
}
|
||
h_EMA200 = iMA(_Symbol, _Period, InpEMATrendPeriod, 0, MODE_EMA, PRICE_CLOSE);
|
||
if (h_EMA200 == INVALID_HANDLE)
|
||
{
|
||
Print("[RsiMomEA] Không tạo được handle EMA200");
|
||
return false;
|
||
}
|
||
h_ATR = iATR(_Symbol, _Period, MathMax(1, InpSlAtrPeriod));
|
||
if (h_ATR == INVALID_HANDLE)
|
||
{
|
||
Print("[RsiMomEA] Không tạo được handle ATR (SL)");
|
||
return false;
|
||
}
|
||
h_ATR_Regime = iATR(_Symbol, _Period, MathMax(1, InpAtrExpPeriod));
|
||
if (h_ATR_Regime == INVALID_HANDLE)
|
||
{
|
||
Print("[RsiMomEA] Không tạo được handle ATR (regime)");
|
||
return false;
|
||
}
|
||
h_ADX = iADX(_Symbol, _Period, MathMax(2, InpAdxPeriod));
|
||
if (h_ADX == INVALID_HANDLE)
|
||
{
|
||
Print("[RsiMomEA] Không tạo được handle ADX");
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void Handles_ReleaseAll()
|
||
{
|
||
if (h_RSI != INVALID_HANDLE) IndicatorRelease(h_RSI);
|
||
if (h_EMA9 != INVALID_HANDLE) IndicatorRelease(h_EMA9);
|
||
if (h_WMA45 != INVALID_HANDLE) IndicatorRelease(h_WMA45);
|
||
if (h_EMA200 != INVALID_HANDLE) IndicatorRelease(h_EMA200);
|
||
if (h_ATR != INVALID_HANDLE) IndicatorRelease(h_ATR);
|
||
if (h_ATR_Regime != INVALID_HANDLE) IndicatorRelease(h_ATR_Regime);
|
||
if (h_ADX != INVALID_HANDLE) IndicatorRelease(h_ADX);
|
||
h_RSI = h_EMA9 = h_WMA45 = h_EMA200 = h_ATR = h_ATR_Regime = h_ADX = INVALID_HANDLE;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
string Panel_LineName(const int idx)
|
||
{
|
||
return PNL_PREFIX + IntegerToString(idx);
|
||
}
|
||
|
||
int Panel_LineStepPx()
|
||
{
|
||
return MathMax(11, InpPanelFontSize + MathMax(6, InpPanelLinePad));
|
||
}
|
||
|
||
void Panel_SetLine(const int idx, const string text, const color clr)
|
||
{
|
||
const long ch = ActChart();
|
||
const string name = Panel_LineName(idx);
|
||
if(ObjectFind(ch, name) < 0)
|
||
return;
|
||
ObjectSetString(ch, name, OBJPROP_TEXT, text);
|
||
ObjectSetInteger(ch, name, OBJPROP_COLOR, clr);
|
||
}
|
||
|
||
void Panel_CreateLine(const int idx)
|
||
{
|
||
const long ch = ActChart();
|
||
const string name = Panel_LineName(idx);
|
||
if(ObjectFind(ch, name) >= 0)
|
||
return;
|
||
|
||
const int step = Panel_LineStepPx();
|
||
const int y = InpPanelTopMargin + idx * step;
|
||
|
||
ObjectCreate(ch, name, OBJ_LABEL, 0, 0, 0);
|
||
ObjectSetInteger(ch, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
||
ObjectSetInteger(ch, name, OBJPROP_XDISTANCE, InpPanelLeftMargin);
|
||
ObjectSetInteger(ch, name, OBJPROP_YDISTANCE, y);
|
||
ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, MathMax(7, InpPanelFontSize));
|
||
ObjectSetString (ch, name, OBJPROP_FONT, "Consolas");
|
||
ObjectSetInteger(ch, name, OBJPROP_BACK, false);
|
||
ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false);
|
||
ObjectSetInteger(ch, name, OBJPROP_HIDDEN, true);
|
||
ObjectSetInteger(ch, name, OBJPROP_COLOR, clrSilver);
|
||
ObjectSetString (ch, name, OBJPROP_TEXT, "");
|
||
}
|
||
|
||
string Panel_FmtOnOff(const bool on)
|
||
{
|
||
return on ? "ON " : "OFF";
|
||
}
|
||
|
||
color Panel_ClrOnOff(const bool on)
|
||
{
|
||
return on ? clrLime : clrDimGray;
|
||
}
|
||
|
||
void Panel_GetSessionLive(string &status, color &clr)
|
||
{
|
||
status = "";
|
||
clr = clrSilver;
|
||
|
||
if(!InpSessionFilterEnabled)
|
||
{
|
||
status = "Phiên: filter OFF (mọi giờ)";
|
||
clr = clrSilver;
|
||
return;
|
||
}
|
||
|
||
const int mins = Env_MinutesFromTime(TimeCurrent());
|
||
if(Env_IsTransitionAt(mins))
|
||
{
|
||
status = "Phiên: GIAO PHIÊN (chặn)";
|
||
clr = clrOrange;
|
||
return;
|
||
}
|
||
|
||
const int avoid = MathMax(0, InpSessionAvoidLastMin);
|
||
const bool inLondon = Env_IsWithinWindow(mins, InpLondonStartHour, InpLondonEndHour, avoid);
|
||
const bool inNY = Env_IsWithinWindow(mins, InpNYStartHour, InpNYEndHour, avoid);
|
||
|
||
if(inLondon && inNY)
|
||
{
|
||
status = "Phiên: London + NY";
|
||
clr = clrLime;
|
||
}
|
||
else if(inLondon)
|
||
{
|
||
status = "Phiên: London";
|
||
clr = clrLime;
|
||
}
|
||
else if(inNY)
|
||
{
|
||
status = "Phiên: New York";
|
||
clr = clrLime;
|
||
}
|
||
else
|
||
{
|
||
status = "Phiên: NGOÀI PHIÊN";
|
||
clr = clrTomato;
|
||
}
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void Panel_CreateAll()
|
||
{
|
||
if(!InpShowPanel)
|
||
return;
|
||
|
||
for(int i = 0; i < PANEL_LINE_COUNT; i++)
|
||
Panel_CreateLine(i);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void Panel_UpdateStatus()
|
||
{
|
||
if(!InpShowPanel)
|
||
return;
|
||
|
||
int ln = 0;
|
||
const string tfTxt = StringSubstr(EnumToString((ENUM_TIMEFRAMES)_Period), 7);
|
||
|
||
Panel_SetLine(ln++, StringFormat("RSI MOMENTUM EA v%s | %s %s",
|
||
EA_VERSION_STR, _Symbol, tfTxt), clrWhite);
|
||
Panel_SetLine(ln++, "────────────────────────────────────", clrDarkGray);
|
||
|
||
Panel_SetLine(ln++, StringFormat("Trade: %s Magic %I64u SignalBar[%d]",
|
||
Panel_FmtOnOff(InpTradeEnabled), InpMagic,
|
||
MathMax(1, InpSignalBarShift)),
|
||
Panel_ClrOnOff(InpTradeEnabled));
|
||
|
||
Panel_SetLine(ln++, StringFormat("Entry: %s", EntryModeLabel()),
|
||
InpEntryMode == RSI_MOM_ENTRY_MARKET ? clrGold : clrSilver);
|
||
Panel_SetLine(ln++, StringFormat("Risk %.2f%% | R:R 1:%.2f%s",
|
||
InpRiskPercent, InpRewardRiskRatio,
|
||
EntryModeIsLimit()
|
||
? StringFormat(" | Limit exp %d bar", InpLimitExpireBars) : ""),
|
||
clrSilver);
|
||
|
||
Panel_SetLine(ln++, StringFormat("Manage @1R: %s (partial %.0f%%)",
|
||
Panel_FmtOnOff(InpManageAt1R),
|
||
InpPartialCloseRatio * 100.0),
|
||
Panel_ClrOnOff(InpManageAt1R));
|
||
|
||
Panel_SetLine(ln++, "────────────────────────────────────", clrDarkGray);
|
||
|
||
Panel_SetLine(ln++, StringFormat("5 phase: %s P1>=%.1f LB%d | EMA200: %s (%d bar)",
|
||
Panel_FmtOnOff(InpPhaseFilterEnabled),
|
||
InpPhaseMinExpandSpread,
|
||
InpPhaseExpandLookback,
|
||
Panel_FmtOnOff(InpTrendFilterEnabled),
|
||
MathMax(1, InpTrendConfirmBars)),
|
||
InpPhaseFilterEnabled ? clrWhite : clrDimGray);
|
||
|
||
Panel_SetLine(ln++, StringFormat("ATR expand: %s | ADX: %s >=%.0f DI: %s",
|
||
Panel_FmtOnOff(InpAtrExpFilterEnabled),
|
||
Panel_FmtOnOff(InpAdxFilterEnabled),
|
||
InpAdxMinLevel,
|
||
Panel_FmtOnOff(InpAdxRequireDiDirection)),
|
||
clrSilver);
|
||
|
||
Panel_SetLine(ln++, StringFormat("Swing2 body: %s range=%d LB=%d",
|
||
Panel_FmtOnOff(InpSwingStructFilterEnabled),
|
||
InpSwingStructRange, InpSwingStructLookback),
|
||
clrSilver);
|
||
|
||
Panel_SetLine(ln++, StringFormat("RSI OB/OS: %s (%.0f / %.0f)",
|
||
Panel_FmtOnOff(InpRsiObOsFilterEnabled),
|
||
InpRSIOverbought, InpRSIOversold),
|
||
clrSilver);
|
||
|
||
const bool spreadEffective = InpSpreadFilterEnabled &&
|
||
!(MQLInfoInteger(MQL_TESTER) && InpSpreadSkipInTester);
|
||
Panel_SetLine(ln++, StringFormat("Session: %s L%d-%d NY%d-%d | Spread: %s%s",
|
||
Panel_FmtOnOff(InpSessionFilterEnabled),
|
||
InpLondonStartHour, InpLondonEndHour,
|
||
InpNYStartHour, InpNYEndHour,
|
||
Panel_FmtOnOff(spreadEffective),
|
||
(InpSpreadFilterEnabled && InpSpreadSkipInTester && MQLInfoInteger(MQL_TESTER))
|
||
? " (skip tester)" : ""),
|
||
Panel_ClrOnOff(InpSessionFilterEnabled));
|
||
|
||
Panel_SetLine(ln++, StringFormat("Giao phiên: %s | Debug marks: %s | Journal: %s",
|
||
Panel_FmtOnOff(InpTransitionBlockEnabled && InpSessionFilterEnabled),
|
||
Panel_FmtOnOff(InpDebugMarkSignals),
|
||
Panel_FmtOnOff(InpExportTradeJournal)),
|
||
clrSilver);
|
||
|
||
Panel_SetLine(ln++, "────────────────────────────────────", clrDarkGray);
|
||
|
||
string sessTxt = "";
|
||
color sessClr = clrSilver;
|
||
Panel_GetSessionLive(sessTxt, sessClr);
|
||
Panel_SetLine(ln++, sessTxt, sessClr);
|
||
|
||
string envWhy = "";
|
||
const bool tradeEnvOk = Env_AllowsTradeNow(envWhy);
|
||
Panel_SetLine(ln++, StringFormat("Trade env: %s%s",
|
||
tradeEnvOk ? "OK" : "BLOCK",
|
||
(StringLen(envWhy) > 0 ? " — " + envWhy : "")),
|
||
tradeEnvOk ? clrLime : clrOrangeRed);
|
||
|
||
const int spr = Env_CurrentSpreadPts();
|
||
const bool sprOk = Env_SpreadAllows();
|
||
Panel_SetLine(ln++, StringFormat("Spread: %d pts (max %d) %s",
|
||
spr, InpMaxSpreadPoints, sprOk ? "OK" : "HIGH"),
|
||
sprOk ? clrSilver : clrOrange);
|
||
|
||
Panel_SetLine(ln++, StringFormat("Giờ lọc: %s | %s",
|
||
InpEnvUseUtc ? "UTC" : "SERVER",
|
||
TimeToString(TimeCurrent(), TIME_DATE | TIME_MINUTES)),
|
||
clrSilver);
|
||
|
||
while(ln < PANEL_IDX_MARKET)
|
||
Panel_SetLine(ln++, "", clrSilver);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void Panel_Update(const double &closeArr[], const double &ema200Arr[],
|
||
const int trendN, const int rates_total)
|
||
{
|
||
Panel_UpdateStatus();
|
||
|
||
if(!InpShowPanel)
|
||
return;
|
||
if(rates_total <= trendN + 1)
|
||
return;
|
||
if(ema200Arr[1] <= 0.0)
|
||
return;
|
||
if(ArraySize(buf_RSI) < 2)
|
||
return;
|
||
|
||
int ln = PANEL_IDX_MARKET;
|
||
|
||
bool panelTrendUp = true;
|
||
bool panelTrendDown = true;
|
||
for(int k = 0; k < trendN; k++)
|
||
{
|
||
const int idx = 1 + k;
|
||
if(ema200Arr[idx] <= 0.0)
|
||
{
|
||
panelTrendUp = false;
|
||
panelTrendDown = false;
|
||
break;
|
||
}
|
||
if(closeArr[idx] <= ema200Arr[idx])
|
||
panelTrendUp = false;
|
||
if(closeArr[idx] >= ema200Arr[idx])
|
||
panelTrendDown = false;
|
||
}
|
||
|
||
string trendTxt = "RANGE";
|
||
color trendClr = clrSilver;
|
||
if(panelTrendUp)
|
||
{
|
||
trendTxt = StringFormat("UPTREND (close>%d>EMA200)", trendN);
|
||
trendClr = InpArrowUpColor;
|
||
}
|
||
else if(panelTrendDown)
|
||
{
|
||
trendTxt = StringFormat("DOWNTREND (close<%d<EMA200)", trendN);
|
||
trendClr = InpArrowDownColor;
|
||
}
|
||
|
||
string sigTxt = "none";
|
||
color sigClr = clrSilver;
|
||
if(buf_Signal[1] > 0.5)
|
||
{
|
||
sigTxt = "BUY";
|
||
sigClr = InpArrowUpColor;
|
||
}
|
||
else if(buf_Signal[1] < -0.5)
|
||
{
|
||
sigTxt = "SELL";
|
||
sigClr = InpArrowDownColor;
|
||
}
|
||
|
||
Panel_SetLine(ln++, StringFormat("Bar[1] Signal: %s | %s", sigTxt, trendTxt), sigClr);
|
||
|
||
Panel_SetLine(ln++, StringFormat("RSI %6.2f", buf_RSI[1]), clrMediumOrchid);
|
||
Panel_SetLine(ln++, StringFormat("EMA9 %6.2f", buf_EMA9[1]), InpPanelColorEMA9);
|
||
Panel_SetLine(ln++, StringFormat("WMA45 %6.2f", buf_WMA45[1]), InpPanelColorWMA45);
|
||
if(InpAdxFilterEnabled)
|
||
{
|
||
double adx = 0.0, pdi = 0.0, mdi = 0.0;
|
||
Adx_GetAtBar(1, adx, pdi, mdi);
|
||
string adxWhy = "";
|
||
const bool adxOk = panelTrendUp ? Adx_AllowsBuyAt(1, adxWhy)
|
||
: (panelTrendDown ? Adx_AllowsSellAt(1, adxWhy) : true);
|
||
Panel_SetLine(ln++, StringFormat("ADX %5.1f +DI=%.1f -DI=%.1f %s",
|
||
adx, pdi, mdi, adxOk ? "OK" : "FAIL"),
|
||
adxOk ? clrSilver : clrOrangeRed);
|
||
}
|
||
Panel_SetLine(ln++, StringFormat("EMA200 close[1] %s %.5f",
|
||
panelTrendUp ? ">" : (panelTrendDown ? "<" : "~"),
|
||
ema200Arr[1]), trendClr);
|
||
|
||
while(ln < PANEL_LINE_COUNT)
|
||
Panel_SetLine(ln++, "", clrSilver);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void FireSignalAlert(const bool isBuy, const datetime barTime, const double price,
|
||
const double rsiVal, const double ema9Val, const double wma45Val, const double ema200Val)
|
||
{
|
||
const string dir = isBuy ? "BUY" : "SELL";
|
||
const string tf = EnumToString((ENUM_TIMEFRAMES)_Period);
|
||
const string tfTxt = StringSubstr(tf, 7);
|
||
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||
|
||
const string pushMsg = StringFormat("[RsiMomEA] %s %s %s @ %s | RSI=%.1f EMA9=%.1f WMA45=%.1f",
|
||
dir, _Symbol, tfTxt, DoubleToString(price, dig), rsiVal, ema9Val, wma45Val);
|
||
const string fullMsg = StringFormat("RsiMomEA %s signal\n%s %s @ %s\nRSI=%.2f EMA9=%.2f WMA45=%.2f\nEMA200=%s\nBar: %s",
|
||
dir, _Symbol, tfTxt, DoubleToString(price, dig),
|
||
rsiVal, ema9Val, wma45Val, DoubleToString(ema200Val, dig),
|
||
TimeToString(barTime, TIME_DATE|TIME_MINUTES));
|
||
|
||
if (InpAlertPush)
|
||
{
|
||
if (!SendNotification(pushMsg))
|
||
PrintFormat("[RsiMomEA] SendNotification FAILED err=%d", GetLastError());
|
||
}
|
||
if (InpAlertPopup)
|
||
Alert(pushMsg);
|
||
if (InpAlertSound)
|
||
{
|
||
const string snd = isBuy ? InpSoundBuy : InpSoundSell;
|
||
if (StringLen(snd) > 0)
|
||
PlaySound(snd);
|
||
}
|
||
if (InpAlertEmail)
|
||
SendMail(StringFormat("RsiMomEA %s %s %s", dir, _Symbol, tfTxt), fullMsg);
|
||
|
||
Print("[RsiMomEA] >>> ", pushMsg);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void Alerts_CheckAndFire(const datetime &timeArr[], const double &closeArr[],
|
||
const double &ema200Arr[], const int need, const int rates_total)
|
||
{
|
||
if (g_firstCalc)
|
||
{
|
||
if (need > 1)
|
||
{
|
||
g_lastAlertBuyBar = timeArr[1];
|
||
g_lastAlertSellBar = timeArr[1];
|
||
}
|
||
g_firstCalc = false;
|
||
return;
|
||
}
|
||
|
||
const int alertShift = InpAlertOnBar0 ? 0 : 1;
|
||
if (alertShift >= need || alertShift + 1 >= rates_total) return;
|
||
|
||
const datetime alertBarTime = timeArr[alertShift];
|
||
|
||
if (buf_Signal[alertShift] > 0.5 && alertBarTime != g_lastAlertBuyBar)
|
||
{
|
||
FireSignalAlert(true, alertBarTime, closeArr[alertShift],
|
||
buf_RSI[alertShift], buf_EMA9[alertShift], buf_WMA45[alertShift], ema200Arr[alertShift]);
|
||
g_lastAlertBuyBar = alertBarTime;
|
||
}
|
||
else if (buf_Signal[alertShift] < -0.5 && alertBarTime != g_lastAlertSellBar)
|
||
{
|
||
FireSignalAlert(false, alertBarTime, closeArr[alertShift],
|
||
buf_RSI[alertShift], buf_EMA9[alertShift], buf_WMA45[alertShift], ema200Arr[alertShift]);
|
||
g_lastAlertSellBar = alertBarTime;
|
||
}
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| Regime: ATR mở rộng tại bar tín hiệu (ATR tăng vs N bar + liên tiếp) |
|
||
//+------------------------------------------------------------------+
|
||
bool AtrExp_GetAt(const int shift, double &atr)
|
||
{
|
||
atr = 0.0;
|
||
if(h_ATR_Regime == INVALID_HANDLE || shift < 0)
|
||
return false;
|
||
|
||
double buf[];
|
||
ArraySetAsSeries(buf, true);
|
||
if(CopyBuffer(h_ATR_Regime, 0, shift, 1, buf) < 1)
|
||
return false;
|
||
|
||
atr = buf[0];
|
||
return (atr > 0.0);
|
||
}
|
||
|
||
bool AtrExp_IsExpandingAt(const int shift)
|
||
{
|
||
if(!InpAtrExpFilterEnabled)
|
||
return true;
|
||
|
||
const int cmpBars = MathMax(1, InpAtrExpCompareBars);
|
||
const int riseBars = MathMax(1, InpAtrExpRiseBars);
|
||
const int needShift = shift + cmpBars + riseBars;
|
||
if(needShift >= Bars(_Symbol, _Period))
|
||
return false;
|
||
|
||
double atrNow = 0.0, atrRef = 0.0;
|
||
if(!AtrExp_GetAt(shift, atrNow) || !AtrExp_GetAt(shift + cmpBars, atrRef))
|
||
return false;
|
||
if(atrRef <= 0.0)
|
||
return false;
|
||
|
||
const double minRatio = MathMax(1.0, InpAtrExpMinRatio);
|
||
if(atrNow / atrRef < minRatio - 1e-8)
|
||
return false;
|
||
|
||
for(int k = 0; k < riseBars; k++)
|
||
{
|
||
double atrA = 0.0, atrB = 0.0;
|
||
if(!AtrExp_GetAt(shift + k, atrA) || !AtrExp_GetAt(shift + k + 1, atrB))
|
||
return false;
|
||
if(atrA <= atrB + 1e-8)
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool AtrExp_AllowsAt(const int shift)
|
||
{
|
||
return AtrExp_IsExpandingAt(shift);
|
||
}
|
||
|
||
bool AtrExp_AllowsNow()
|
||
{
|
||
return AtrExp_AllowsAt(1);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
AdxFilterConfig GetAdxFilterConfig()
|
||
{
|
||
AdxFilterConfig c;
|
||
c.enabled = InpAdxFilterEnabled;
|
||
c.period = MathMax(2, InpAdxPeriod);
|
||
c.minLevel = MathMax(0.0, InpAdxMinLevel);
|
||
c.maxLevel = MathMax(0.0, InpAdxMaxLevel);
|
||
c.requireDiDir = InpAdxRequireDiDirection;
|
||
c.minDiSpread = MathMax(0.0, InpAdxMinDiSpread);
|
||
c.riseBars = MathMax(0, InpAdxRiseBars);
|
||
return c;
|
||
}
|
||
|
||
void Adx_GetAtBar(const int shift, double &adx, double &plusDi, double &minusDi)
|
||
{
|
||
adx = plusDi = minusDi = 0.0;
|
||
if(!InpAdxFilterEnabled || h_ADX == INVALID_HANDLE)
|
||
return;
|
||
AdxFilter_GetAt(h_ADX, shift, adx, plusDi, minusDi);
|
||
}
|
||
|
||
bool Adx_AllowsBuyAt(const int shift, string &why)
|
||
{
|
||
why = "";
|
||
if(!InpAdxFilterEnabled)
|
||
return true;
|
||
return AdxFilter_PassesBuy(h_ADX, shift, GetAdxFilterConfig(), why);
|
||
}
|
||
|
||
bool Adx_AllowsSellAt(const int shift, string &why)
|
||
{
|
||
why = "";
|
||
if(!InpAdxFilterEnabled)
|
||
return true;
|
||
return AdxFilter_PassesSell(h_ADX, shift, GetAdxFilterConfig(), why);
|
||
}
|
||
|
||
SwingStructConfig SwingStruct_BuildConfig()
|
||
{
|
||
SwingStructConfig c;
|
||
c.enabled = InpSwingStructFilterEnabled;
|
||
c.pivotRange = MathMax(1, InpSwingStructRange);
|
||
c.lookback = MathMax(20, InpSwingStructLookback);
|
||
c.tolPts = MathMax(0.0, InpSwingStructTolPts);
|
||
return c;
|
||
}
|
||
|
||
bool SwingStruct_AllowsBuyAt(const int shift, string &why, double &bodyOld, double &bodyNew)
|
||
{
|
||
why = "";
|
||
bodyOld = bodyNew = 0.0;
|
||
if(!InpSwingStructFilterEnabled)
|
||
return true;
|
||
|
||
SwingStructPoint older, newer;
|
||
const SwingStructConfig cfg = SwingStruct_BuildConfig();
|
||
const bool ok = SwingStruct_PassesBuyAt(_Symbol, _Period, shift, cfg, why, older, newer);
|
||
bodyOld = older.bodyPrice;
|
||
bodyNew = newer.bodyPrice;
|
||
return ok;
|
||
}
|
||
|
||
bool SwingStruct_AllowsSellAt(const int shift, string &why, double &bodyOld, double &bodyNew)
|
||
{
|
||
why = "";
|
||
bodyOld = bodyNew = 0.0;
|
||
if(!InpSwingStructFilterEnabled)
|
||
return true;
|
||
|
||
SwingStructPoint older, newer;
|
||
const SwingStructConfig cfg = SwingStruct_BuildConfig();
|
||
const bool ok = SwingStruct_PassesSellAt(_Symbol, _Period, shift, cfg, why, older, newer);
|
||
bodyOld = older.bodyPrice;
|
||
bodyNew = newer.bodyPrice;
|
||
return ok;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| Phiên London/NY + chặn giao phiên + spread tối đa (trade env) |
|
||
//+------------------------------------------------------------------+
|
||
int Env_GmtOffsetSec()
|
||
{
|
||
return (int)(TimeGMT() - TimeCurrent());
|
||
}
|
||
|
||
datetime Env_ToFilterTime(const datetime t)
|
||
{
|
||
if(!InpEnvUseUtc)
|
||
return t;
|
||
return t + Env_GmtOffsetSec();
|
||
}
|
||
|
||
int Env_ClampHour(const int h)
|
||
{
|
||
return MathMax(0, MathMin(23, h));
|
||
}
|
||
|
||
int Env_MinutesFromTime(const datetime t)
|
||
{
|
||
MqlDateTime tm;
|
||
TimeToStruct(Env_ToFilterTime(t), tm);
|
||
return tm.hour * 60 + tm.min;
|
||
}
|
||
|
||
bool Env_IsWithinWindow(const int currentMinutes,
|
||
const int startHour, const int endHour,
|
||
const int avoidLastMin)
|
||
{
|
||
const int startMin = Env_ClampHour(startHour) * 60;
|
||
int endMin = Env_ClampHour(endHour) * 60;
|
||
if(endMin <= startMin)
|
||
endMin += 24 * 60;
|
||
|
||
int cur = currentMinutes;
|
||
if(endMin > 24 * 60 && cur < startMin)
|
||
cur += 24 * 60;
|
||
|
||
if(cur < startMin || cur >= endMin)
|
||
return false;
|
||
|
||
const int avoid = MathMax(0, avoidLastMin);
|
||
if(avoid > 0 && cur >= endMin - avoid)
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
bool Env_IsTransitionAt(const int mins)
|
||
{
|
||
if(!InpTransitionBlockEnabled)
|
||
return false;
|
||
|
||
return (Env_IsWithinWindow(mins, InpTransition1StartHour, InpTransition1EndHour, 0) ||
|
||
Env_IsWithinWindow(mins, InpTransition2StartHour, InpTransition2EndHour, 0));
|
||
}
|
||
|
||
bool Env_InActiveSessionAt(const int mins)
|
||
{
|
||
if(!InpSessionFilterEnabled)
|
||
return true;
|
||
|
||
const int avoid = MathMax(0, InpSessionAvoidLastMin);
|
||
return (Env_IsWithinWindow(mins, InpLondonStartHour, InpLondonEndHour, avoid) ||
|
||
Env_IsWithinWindow(mins, InpNYStartHour, InpNYEndHour, avoid));
|
||
}
|
||
|
||
int Env_CurrentSpreadPts()
|
||
{
|
||
return (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
|
||
}
|
||
|
||
bool Env_SpreadAllows()
|
||
{
|
||
if(!InpSpreadFilterEnabled)
|
||
return true;
|
||
|
||
if(MQLInfoInteger(MQL_TESTER) && InpSpreadSkipInTester)
|
||
return true;
|
||
|
||
const int maxSp = MathMax(1, InpMaxSpreadPoints);
|
||
return (Env_CurrentSpreadPts() <= maxSp);
|
||
}
|
||
|
||
bool Env_AllowsSessionAt(const datetime t, string &why)
|
||
{
|
||
why = "";
|
||
if(!InpSessionFilterEnabled)
|
||
return true;
|
||
|
||
const int mins = Env_MinutesFromTime(t);
|
||
|
||
if(Env_IsTransitionAt(mins))
|
||
{
|
||
why = "giao phiên ";
|
||
return false;
|
||
}
|
||
|
||
if(!Env_InActiveSessionAt(mins))
|
||
{
|
||
why = "ngoài phiên ";
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
bool Env_AllowsTradeNow(string &why)
|
||
{
|
||
return Env_AllowsTradeAtBar(MathMax(1, InpSignalBarShift), why);
|
||
}
|
||
|
||
bool Env_AllowsTradeAtBar(const int sigShift, string &why)
|
||
{
|
||
why = "";
|
||
|
||
const datetime barT = iTime(_Symbol, _Period, sigShift);
|
||
if(barT == 0)
|
||
{
|
||
why = "no bar time ";
|
||
return false;
|
||
}
|
||
|
||
if(!Env_AllowsSessionAt(barT, why))
|
||
return false;
|
||
|
||
if(!Env_SpreadAllows())
|
||
{
|
||
why = StringFormat("spread %d>%d ", Env_CurrentSpreadPts(), InpMaxSpreadPoints);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
bool Trend_IsUpAt(const int shift, const int trendN, const double &closeArr[], const double &ema200Arr[])
|
||
{
|
||
const int n = MathMax(1, trendN);
|
||
for(int k = 0; k < n; k++)
|
||
{
|
||
const int idx = shift + k;
|
||
if(ema200Arr[idx] <= 0.0)
|
||
return false;
|
||
if(closeArr[idx] <= ema200Arr[idx])
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool Trend_IsDownAt(const int shift, const int trendN, const double &closeArr[], const double &ema200Arr[])
|
||
{
|
||
const int n = MathMax(1, trendN);
|
||
for(int k = 0; k < n; k++)
|
||
{
|
||
const int idx = shift + k;
|
||
if(ema200Arr[idx] <= 0.0)
|
||
return false;
|
||
if(closeArr[idx] >= ema200Arr[idx])
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
bool Signal_Ema9CoreBuyOkAt(const int shift)
|
||
{
|
||
return (buf_EMA9[shift] < buf_WMA45[shift]);
|
||
}
|
||
|
||
bool Signal_Ema9CoreSellOkAt(const int shift)
|
||
{
|
||
return (buf_EMA9[shift] > buf_WMA45[shift]);
|
||
}
|
||
|
||
PhaseEntryConfig GetPhaseEntryConfig()
|
||
{
|
||
PhaseEntryConfig c;
|
||
c.enabled = InpPhaseFilterEnabled;
|
||
c.expandLookback = MathMax(5, InpPhaseExpandLookback);
|
||
c.minExpandSpread = MathMax(1.0, InpPhaseMinExpandSpread);
|
||
c.coilLookback = MathMax(3, InpPhaseCoilLookback);
|
||
c.minRsiEma9Crosses = MathMax(1, InpPhaseMinRsiEma9Cross);
|
||
c.coilBand = MathMax(0.5, InpPhaseCoilBand);
|
||
c.ema9SlopeBars = MathMax(1, InpPhaseEma9SlopeBars);
|
||
c.ema9SlopeTol = MathMax(0.0, InpPhaseEma9SlopeTol);
|
||
c.wmaFlatBars = MathMax(2, InpPhaseWmaFlatBars);
|
||
c.wmaWasSlopeMin = MathMax(0.0, InpPhaseWmaWasSlopeMin);
|
||
c.wmaFlatMaxSlope = MathMax(0.05, InpPhaseWmaFlatMaxSlope);
|
||
c.wmaRelaxPrior = InpPhaseWmaRelaxPrior;
|
||
c.maxEma9WmaGap = MathMax(0.5, InpPhaseMaxEma9WmaGap);
|
||
return c;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
bool Signal_RsiOkBuyAt(const int shift)
|
||
{
|
||
if(!InpRsiObOsFilterEnabled)
|
||
return true;
|
||
return (buf_RSI[shift] < InpRSIOverbought);
|
||
}
|
||
|
||
bool Signal_RsiOkSellAt(const int shift)
|
||
{
|
||
if(!InpRsiObOsFilterEnabled)
|
||
return true;
|
||
return (buf_RSI[shift] > InpRSIOversold);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
string Signal_ReasonBuy(const bool trendUp, const bool ema9CoreOk, const bool phaseOk, const string phaseFail,
|
||
const bool atrExpOk, const bool adxOk, const bool swingOk,
|
||
const bool rsiObOsOk, const bool envOk, const bool valid)
|
||
{
|
||
if(valid)
|
||
{
|
||
string s = InpPhaseFilterEnabled
|
||
? "RSI↑WMA45 | 5phase OK | EMA200 UP"
|
||
: "RSI↑WMA45 EMA9<WMA45 | EMA200 UP";
|
||
s += StringFormat(" (%d bar) | %s", MathMax(1, InpTrendConfirmBars), EntryModeLabel());
|
||
if(InpAtrExpFilterEnabled)
|
||
s += StringFormat(" | ATR↑ x%.0f%% %d bar", (InpAtrExpMinRatio - 1.0) * 100.0, InpAtrExpRiseBars);
|
||
if(InpAdxFilterEnabled)
|
||
s += StringFormat(" | ADX>=%.0f", InpAdxMinLevel);
|
||
if(InpSwingStructFilterEnabled)
|
||
s += " | 2đáy↑";
|
||
return s;
|
||
}
|
||
|
||
string f = "";
|
||
if(!InpPhaseFilterEnabled && !ema9CoreOk)
|
||
f += "EMA9>=WMA45 ";
|
||
if(InpPhaseFilterEnabled && !phaseOk)
|
||
f += phaseFail;
|
||
if(InpTrendFilterEnabled && !trendUp)
|
||
f += "ngược EMA200 ";
|
||
if(InpAtrExpFilterEnabled && !atrExpOk)
|
||
f += "ATR co ";
|
||
if(InpAdxFilterEnabled && !adxOk)
|
||
f += "ADX yếu ";
|
||
if(InpSwingStructFilterEnabled && !swingOk)
|
||
f += "2 đáy ";
|
||
if(InpRsiObOsFilterEnabled && !rsiObOsOk)
|
||
f += "RSI quá mua ";
|
||
if(!envOk)
|
||
f += "phiên/spread ";
|
||
if(StringLen(f) == 0)
|
||
f = "no cross ";
|
||
return f;
|
||
}
|
||
|
||
string Signal_ReasonSell(const bool trendDown, const bool ema9CoreOk, const bool phaseOk, const string phaseFail,
|
||
const bool atrExpOk, const bool adxOk, const bool swingOk,
|
||
const bool rsiObOsOk, const bool envOk, const bool valid)
|
||
{
|
||
if(valid)
|
||
{
|
||
string s = InpPhaseFilterEnabled
|
||
? "RSI↓WMA45 | 5phase OK | EMA200 DOWN"
|
||
: "RSI↓WMA45 EMA9>WMA45 | EMA200 DOWN";
|
||
s += StringFormat(" (%d bar) | %s", MathMax(1, InpTrendConfirmBars), EntryModeLabel());
|
||
if(InpAtrExpFilterEnabled)
|
||
s += StringFormat(" | ATR↑ x%.0f%% %d bar", (InpAtrExpMinRatio - 1.0) * 100.0, InpAtrExpRiseBars);
|
||
if(InpAdxFilterEnabled)
|
||
s += StringFormat(" | ADX>=%.0f", InpAdxMinLevel);
|
||
if(InpSwingStructFilterEnabled)
|
||
s += " | 2đỉnh↓";
|
||
return s;
|
||
}
|
||
|
||
string f = "";
|
||
if(!InpPhaseFilterEnabled && !ema9CoreOk)
|
||
f += "EMA9<=WMA45 ";
|
||
if(InpPhaseFilterEnabled && !phaseOk)
|
||
f += phaseFail;
|
||
if(InpTrendFilterEnabled && !trendDown)
|
||
f += "ngược EMA200 ";
|
||
if(InpAtrExpFilterEnabled && !atrExpOk)
|
||
f += "ATR co ";
|
||
if(InpAdxFilterEnabled && !adxOk)
|
||
f += "ADX yếu ";
|
||
if(InpSwingStructFilterEnabled && !swingOk)
|
||
f += "2 đỉnh ";
|
||
if(InpRsiObOsFilterEnabled && !rsiObOsOk)
|
||
f += "RSI quá bán ";
|
||
if(!envOk)
|
||
f += "phiên/spread ";
|
||
if(StringLen(f) == 0)
|
||
f = "no cross ";
|
||
return f;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void Signal_DebugApplyTradeLayer(SignalEvalResult &ev, const int shift)
|
||
{
|
||
const int sigShift = MathMax(1, InpSignalBarShift);
|
||
if(shift != sigShift || !ev.signalOk)
|
||
return;
|
||
|
||
if(!InpTradeEnabled)
|
||
{
|
||
ev.tradeOk = false;
|
||
ev.failTag = "Trade-OFF";
|
||
SignalEval_Append(ev.detail, "Đặt lệnh:OFF (InpTradeEnabled=false)");
|
||
ev.summary = "SIG → Trade tắt";
|
||
return;
|
||
}
|
||
|
||
string tradeWhy = "";
|
||
ev.tradeOk = Env_AllowsTradeAtBar(shift, tradeWhy);
|
||
if(ev.tradeOk)
|
||
{
|
||
ev.summary = "HỢP LỆ → vào lệnh";
|
||
SignalEval_Append(ev.detail, "Đặt lệnh:OK");
|
||
}
|
||
else
|
||
{
|
||
ev.failTag = "Đặt lệnh";
|
||
ev.summary = "SIG → " + tradeWhy;
|
||
SignalEval_Append(ev.detail, StringLen(tradeWhy) > 0
|
||
? "Đặt lệnh:FAIL " + tradeWhy
|
||
: "Đặt lệnh:FAIL");
|
||
}
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
bool Signal_DebugShouldLogExperts(const datetime barTime, const bool isBuy)
|
||
{
|
||
const int side = isBuy ? 1 : -1;
|
||
if(barTime == g_dbgLogBarTime && side == g_dbgLogSide)
|
||
return false;
|
||
g_dbgLogBarTime = barTime;
|
||
g_dbgLogSide = side;
|
||
return true;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void Signal_DebugMarkCross(const long ch, const int shift, const datetime barTime,
|
||
const double barHigh, const double barLow, const double markPrice,
|
||
const bool isBuy, const int rates_total, const int trendN,
|
||
const double &closeArr[], const double &ema200Arr[],
|
||
const PhaseEntryConfig &phaseCfg,
|
||
const bool atrExpOkBar, const bool sessionAtBar,
|
||
const string sessionFailWhy)
|
||
{
|
||
if(DebugMarksEffective() && Signal_DebugMarkExists(ch, DBG_PREFIX, barTime, isBuy))
|
||
return;
|
||
|
||
double adxVal = 0.0, plusDi = 0.0, minusDi = 0.0;
|
||
string adxWhy = "";
|
||
Adx_GetAtBar(shift, adxVal, plusDi, minusDi);
|
||
const bool adxOkBar = isBuy ? Adx_AllowsBuyAt(shift, adxWhy) : Adx_AllowsSellAt(shift, adxWhy);
|
||
|
||
string swingWhy = "";
|
||
double swingOld = 0.0, swingNew = 0.0;
|
||
const bool swingOkBar = isBuy
|
||
? SwingStruct_AllowsBuyAt(shift, swingWhy, swingOld, swingNew)
|
||
: SwingStruct_AllowsSellAt(shift, swingWhy, swingOld, swingNew);
|
||
|
||
SignalEvalResult ev;
|
||
if(isBuy)
|
||
ev = Signal_EvaluateBuyAt(shift, rates_total, trendN,
|
||
buf_RSI, buf_EMA9, buf_WMA45, closeArr, ema200Arr,
|
||
phaseCfg, InpPhaseFilterEnabled, InpTrendFilterEnabled,
|
||
InpAtrExpFilterEnabled, InpAdxFilterEnabled, adxOkBar,
|
||
adxVal, plusDi, minusDi, adxWhy,
|
||
InpSwingStructFilterEnabled, swingOkBar,
|
||
swingOld, swingNew, swingWhy,
|
||
InpRsiObOsFilterEnabled,
|
||
InpRSIOverbought, InpRSIOversold,
|
||
InpSessionFilterEnabled,
|
||
atrExpOkBar, sessionAtBar, sessionFailWhy);
|
||
else
|
||
ev = Signal_EvaluateSellAt(shift, rates_total, trendN,
|
||
buf_RSI, buf_EMA9, buf_WMA45, closeArr, ema200Arr,
|
||
phaseCfg, InpPhaseFilterEnabled, InpTrendFilterEnabled,
|
||
InpAtrExpFilterEnabled, InpAdxFilterEnabled, adxOkBar,
|
||
adxVal, plusDi, minusDi, adxWhy,
|
||
InpSwingStructFilterEnabled, swingOkBar,
|
||
swingOld, swingNew, swingWhy,
|
||
InpRsiObOsFilterEnabled,
|
||
InpRSIOverbought, InpRSIOversold,
|
||
InpSessionFilterEnabled,
|
||
atrExpOkBar, sessionAtBar, sessionFailWhy);
|
||
|
||
Signal_DebugApplyTradeLayer(ev, shift);
|
||
Signal_DebugDrawMark(ch, DBG_PREFIX, barTime, barHigh, barLow, markPrice, isBuy, ev);
|
||
|
||
const int sigShift = MathMax(1, InpSignalBarShift);
|
||
if(InpDebugLogExperts && shift == sigShift
|
||
&& Signal_DebugShouldLogExperts(barTime, isBuy))
|
||
{
|
||
PrintFormat("[RsiMomEA DBG] %s %s %s fail=[%s] | %s",
|
||
TimeToString(barTime, TIME_DATE | TIME_MINUTES),
|
||
isBuy ? "BUY" : "SELL",
|
||
ev.summary,
|
||
ev.failTag,
|
||
ev.detail);
|
||
if(ev.signalOk && MathAbs(buf_Signal[shift]) < 0.5)
|
||
Print("[RsiMomEA DBG] → buf_Signal=0 (mũi tên CR không vẽ) nhưng debug vẫn pass signal — kiểm tra ObjectFind trùng tên");
|
||
}
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void SignalScan_Run(const int barsToScan, const int rates_total, const int need, const int trendN,
|
||
const datetime &timeArr[], const double &highArr[], const double &lowArr[],
|
||
const double &closeArr[], const double &ema200Arr[])
|
||
{
|
||
const long ch = ActChart();
|
||
const double arrowOffset = InpArrowOffsetPts * _Point;
|
||
const int dbgMax = MathMax(50, InpDebugMarkMaxBars);
|
||
|
||
const bool dbgMarks = DebugMarksEffective();
|
||
if(dbgMarks)
|
||
{
|
||
Signal_DebugConfigureChart(ch);
|
||
Signal_DebugPruneOlderThan(ch, DBG_PREFIX, MathMax(50, InpDebugMarkMaxBars) + 5);
|
||
}
|
||
|
||
buf_Signal[0] = 0.0;
|
||
buf_Trend[0] = 0.0;
|
||
buf_EMA200[0] = (need > 0) ? ema200Arr[0] : 0.0;
|
||
|
||
for (int i = barsToScan; i >= 1; i--)
|
||
{
|
||
buf_Signal[i] = 0.0;
|
||
buf_Trend[i] = 0.0;
|
||
buf_EMA200[i] = (i < need) ? ema200Arr[i] : 0.0;
|
||
|
||
if (i + 1 >= rates_total) continue;
|
||
if (InpTrendFilterEnabled && i + trendN >= need) continue;
|
||
const int phaseLb = InpPhaseFilterEnabled
|
||
? MathMax(InpPhaseExpandLookback, InpPhaseCoilLookback) + InpPhaseWmaFlatBars * 2 + 3
|
||
: 0;
|
||
if (i + phaseLb >= rates_total) continue;
|
||
if (ema200Arr[i] <= 0.0) continue;
|
||
|
||
const bool trendUp = !InpTrendFilterEnabled || Trend_IsUpAt(i, trendN, closeArr, ema200Arr);
|
||
const bool trendDown = !InpTrendFilterEnabled || Trend_IsDownAt(i, trendN, closeArr, ema200Arr);
|
||
buf_Trend[i] = trendUp ? 1.0 : (trendDown ? -1.0 : 0.0);
|
||
|
||
// BUY: RSI↑WMA45 + EMA9<WMA45 | SELL: RSI↓WMA45 + EMA9>WMA45
|
||
const bool crossUpWma45 = (buf_RSI[i+1] <= buf_WMA45[i+1]) && (buf_RSI[i] > buf_WMA45[i]);
|
||
const bool crossDownWma45 = (buf_RSI[i+1] >= buf_WMA45[i+1]) && (buf_RSI[i] < buf_WMA45[i]);
|
||
if (!crossUpWma45 && !crossDownWma45) continue;
|
||
|
||
const bool ema9BuyOk = Signal_Ema9CoreBuyOkAt(i);
|
||
const bool ema9SellOk = Signal_Ema9CoreSellOkAt(i);
|
||
const PhaseEntryConfig phaseCfg = GetPhaseEntryConfig();
|
||
string phaseFailBuy = "";
|
||
string phaseFailSell = "";
|
||
const bool phaseBuyOk = Phase_BuyPasses(i, rates_total, buf_RSI, buf_EMA9, buf_WMA45, phaseCfg, phaseFailBuy);
|
||
const bool phaseSellOk = Phase_SellPasses(i, rates_total, buf_RSI, buf_EMA9, buf_WMA45, phaseCfg, phaseFailSell);
|
||
const bool coreBuyOk = InpPhaseFilterEnabled ? phaseBuyOk : ema9BuyOk;
|
||
const bool coreSellOk = InpPhaseFilterEnabled ? phaseSellOk : ema9SellOk;
|
||
const bool atrExpOkBar = AtrExp_AllowsAt(i);
|
||
string adxWhyB = "", adxWhyS = "";
|
||
const bool adxOkBuy = Adx_AllowsBuyAt(i, adxWhyB);
|
||
const bool adxOkSell = Adx_AllowsSellAt(i, adxWhyS);
|
||
string swingWhyB = "", swingWhyS = "";
|
||
double swOld = 0.0, swNew = 0.0;
|
||
const bool swingOkBuy = SwingStruct_AllowsBuyAt(i, swingWhyB, swOld, swNew);
|
||
const bool swingOkSell = SwingStruct_AllowsSellAt(i, swingWhyS, swOld, swNew);
|
||
const bool rsiOkBuy = Signal_RsiOkBuyAt(i);
|
||
const bool rsiOkSell = Signal_RsiOkSellAt(i);
|
||
string envWhy = "";
|
||
const bool envOkBar = Env_AllowsSessionAt(timeArr[i], envWhy);
|
||
|
||
if(crossUpWma45)
|
||
{
|
||
const bool validBuy = coreBuyOk && trendUp && atrExpOkBar && adxOkBuy
|
||
&& swingOkBuy && rsiOkBuy && envOkBar;
|
||
|
||
if(dbgMarks && i <= dbgMax)
|
||
{
|
||
string envDbg = envWhy;
|
||
Signal_DebugMarkCross(ch, i, timeArr[i], highArr[i], lowArr[i],
|
||
lowArr[i] - arrowOffset, true,
|
||
rates_total, trendN, closeArr, ema200Arr, phaseCfg,
|
||
atrExpOkBar, envOkBar, envDbg);
|
||
}
|
||
|
||
if(validBuy)
|
||
{
|
||
const string reason = Signal_ReasonBuy(trendUp, ema9BuyOk, phaseBuyOk, phaseFailBuy,
|
||
atrExpOkBar, adxOkBuy, swingOkBuy,
|
||
rsiOkBuy, envOkBar, true);
|
||
const double arrowPrice = lowArr[i] - arrowOffset;
|
||
const string arrowName = OBJ_PREFIX + "CR_" + IntegerToString((int)timeArr[i]);
|
||
if(ObjectFind(ch, arrowName) < 0)
|
||
{
|
||
buf_Signal[i] = 1.0;
|
||
ObjectCreate(ch, arrowName, OBJ_ARROW, 0, timeArr[i], arrowPrice);
|
||
ObjectSetInteger(ch, arrowName, OBJPROP_ARROWCODE, 233);
|
||
ObjectSetInteger(ch, arrowName, OBJPROP_ANCHOR, ANCHOR_TOP);
|
||
ObjectSetInteger(ch, arrowName, OBJPROP_COLOR, InpArrowUpColor);
|
||
ObjectSetInteger(ch, arrowName, OBJPROP_WIDTH, InpArrowSize);
|
||
ObjectSetInteger(ch, arrowName, OBJPROP_SELECTABLE, false);
|
||
ObjectSetInteger(ch, arrowName, OBJPROP_HIDDEN, true);
|
||
ObjectSetString (ch, arrowName, OBJPROP_TOOLTIP, "BUY | " + reason);
|
||
}
|
||
}
|
||
}
|
||
|
||
if(crossDownWma45)
|
||
{
|
||
const bool validSell = coreSellOk && trendDown && atrExpOkBar && adxOkSell
|
||
&& swingOkSell && rsiOkSell && envOkBar;
|
||
|
||
if(dbgMarks && i <= dbgMax)
|
||
{
|
||
string envDbg = envWhy;
|
||
Signal_DebugMarkCross(ch, i, timeArr[i], highArr[i], lowArr[i],
|
||
highArr[i] + arrowOffset, false,
|
||
rates_total, trendN, closeArr, ema200Arr, phaseCfg,
|
||
atrExpOkBar, envOkBar, envDbg);
|
||
}
|
||
|
||
if(validSell)
|
||
{
|
||
const string reason = Signal_ReasonSell(trendDown, ema9SellOk, phaseSellOk, phaseFailSell,
|
||
atrExpOkBar, adxOkSell, swingOkSell,
|
||
rsiOkSell, envOkBar, true);
|
||
const double arrowPrice = highArr[i] + arrowOffset;
|
||
const string arrowName = OBJ_PREFIX + "CR_" + IntegerToString((int)timeArr[i]);
|
||
if(ObjectFind(ch, arrowName) < 0)
|
||
{
|
||
buf_Signal[i] = -1.0;
|
||
ObjectCreate(ch, arrowName, OBJ_ARROW, 0, timeArr[i], arrowPrice);
|
||
ObjectSetInteger(ch, arrowName, OBJPROP_ARROWCODE, 234);
|
||
ObjectSetInteger(ch, arrowName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
|
||
ObjectSetInteger(ch, arrowName, OBJPROP_COLOR, InpArrowDownColor);
|
||
ObjectSetInteger(ch, arrowName, OBJPROP_WIDTH, InpArrowSize);
|
||
ObjectSetInteger(ch, arrowName, OBJPROP_SELECTABLE, false);
|
||
ObjectSetInteger(ch, arrowName, OBJPROP_HIDDEN, true);
|
||
ObjectSetString (ch, arrowName, OBJPROP_TOOLTIP, "SELL | " + reason);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void Diagnostics_FirstPass(const int rates_total, const int copyN, const int trendN,
|
||
const int need, const double &closeArr[])
|
||
{
|
||
static bool firstSuccess = false;
|
||
if (firstSuccess || copyN < rates_total) return;
|
||
firstSuccess = true;
|
||
|
||
int upCount = 0, downCount = 0, rangeCount = 0, zeroEma = 0;
|
||
const int n = MathMin(500, rates_total - 2);
|
||
for (int i = 1; i <= n; i++)
|
||
{
|
||
if (buf_EMA200[i] <= 0.0) zeroEma++;
|
||
if (buf_Trend[i] > 0.5) upCount++;
|
||
else if (buf_Trend[i] < -0.5) downCount++;
|
||
else rangeCount++;
|
||
}
|
||
PrintFormat("[RsiMomEA] First-pass OK rates_total=%d copyN=%d trendN=%d need=%d", rates_total, copyN, trendN, need);
|
||
PrintFormat("[RsiMomEA] bar1: RSI=%.2f EMA9=%.2f WMA45=%.2f EMA200=%.5f Trend=%.0f Signal=%.0f close[1]=%.5f",
|
||
buf_RSI[1], buf_EMA9[1], buf_WMA45[1], buf_EMA200[1], buf_Trend[1], buf_Signal[1], closeArr[1]);
|
||
PrintFormat("[RsiMomEA] last %d bars trend dist: UP=%d DOWN=%d RANGE=%d (zeroEma200=%d)",
|
||
n, upCount, downCount, rangeCount, zeroEma);
|
||
|
||
{
|
||
int crossUp = 0, crossDn = 0, passTrendUp = 0, passTrendDn = 0, passAtrUp = 0, passAtrDn = 0;
|
||
int passAdxUp = 0, passAdxDn = 0;
|
||
int passSwingUp = 0, passSwingDn = 0;
|
||
int passEnvUp = 0, passEnvDn = 0;
|
||
int passEma9Up = 0, passEma9Dn = 0;
|
||
int passPhaseUp = 0, passPhaseDn = 0;
|
||
int validBuy = 0, validSell = 0;
|
||
const int needSh = MathMax(1, InpAtrExpCompareBars) + MathMax(1, InpAtrExpRiseBars)
|
||
+ (InpAdxFilterEnabled ? MathMax(0, InpAdxRiseBars) : 0);
|
||
const int phaseLb = InpPhaseFilterEnabled
|
||
? MathMax(InpPhaseExpandLookback, InpPhaseCoilLookback) + InpPhaseWmaFlatBars * 2 + 3
|
||
: 0;
|
||
const PhaseEntryConfig phaseCfg = GetPhaseEntryConfig();
|
||
for(int i = 1; i <= n; i++)
|
||
{
|
||
if(i + 1 >= rates_total || i + needSh >= rates_total
|
||
|| (InpTrendFilterEnabled && i + trendN >= need)
|
||
|| i + phaseLb >= rates_total)
|
||
continue;
|
||
|
||
const bool up = (buf_RSI[i+1] <= buf_WMA45[i+1]) && (buf_RSI[i] > buf_WMA45[i]);
|
||
const bool dn = (buf_RSI[i+1] >= buf_WMA45[i+1]) && (buf_RSI[i] < buf_WMA45[i]);
|
||
if(up) crossUp++;
|
||
if(dn) crossDn++;
|
||
if(up && (!InpTrendFilterEnabled || Trend_IsUpAt(i, MathMax(1, InpTrendConfirmBars), closeArr, buf_EMA200)))
|
||
passTrendUp++;
|
||
if(dn && (!InpTrendFilterEnabled || Trend_IsDownAt(i, MathMax(1, InpTrendConfirmBars), closeArr, buf_EMA200)))
|
||
passTrendDn++;
|
||
if(up && AtrExp_AllowsAt(i)) passAtrUp++;
|
||
if(dn && AtrExp_AllowsAt(i)) passAtrDn++;
|
||
string adxW = "";
|
||
if(up && Adx_AllowsBuyAt(i, adxW)) passAdxUp++;
|
||
if(dn && Adx_AllowsSellAt(i, adxW)) passAdxDn++;
|
||
double swO = 0.0, swN = 0.0;
|
||
if(up && SwingStruct_AllowsBuyAt(i, adxW, swO, swN)) passSwingUp++;
|
||
if(dn && SwingStruct_AllowsSellAt(i, adxW, swO, swN)) passSwingDn++;
|
||
if(up && Signal_Ema9CoreBuyOkAt(i)) passEma9Up++;
|
||
if(dn && Signal_Ema9CoreSellOkAt(i)) passEma9Dn++;
|
||
string pfB = "", pfS = "";
|
||
if(up && Phase_BuyPasses(i, rates_total, buf_RSI, buf_EMA9, buf_WMA45, phaseCfg, pfB)) passPhaseUp++;
|
||
if(dn && Phase_SellPasses(i, rates_total, buf_RSI, buf_EMA9, buf_WMA45, phaseCfg, pfS)) passPhaseDn++;
|
||
const datetime tBar = iTime(_Symbol, _Period, i);
|
||
string w = "";
|
||
if(up && Env_AllowsSessionAt(tBar, w)) passEnvUp++;
|
||
if(dn && Env_AllowsSessionAt(tBar, w)) passEnvDn++;
|
||
if(buf_Signal[i] > 0.5) validBuy++;
|
||
if(buf_Signal[i] < -0.5) validSell++;
|
||
}
|
||
PrintFormat("[RsiMomEA] last %d bars: cross UP=%d DN=%d | EMA9 UP=%d DN=%d | 5phase UP=%d DN=%d | EMA200 UP=%d DOWN=%d | ATR↑ UP=%d DN=%d | ADX UP=%d DN=%d | Swing2 UP=%d DN=%d | phiên UP=%d DN=%d | signal BUY=%d SELL=%d",
|
||
n, crossUp, crossDn, passEma9Up, passEma9Dn, passPhaseUp, passPhaseDn,
|
||
passTrendUp, passTrendDn, passAtrUp, passAtrDn, passAdxUp, passAdxDn,
|
||
passSwingUp, passSwingDn, passEnvUp, passEnvDn, validBuy, validSell);
|
||
if(crossUp > 0 && passPhaseUp == 0 && InpPhaseFilterEnabled)
|
||
Print("[RsiMomEA] Gợi ý: cross UP bị 5phase — xem P1-P5 hoặc hạ InpPhaseMinExpandSpread / InpPhaseMinRsiEma9Cross");
|
||
else if(crossUp > 0 && passEma9Up == 0)
|
||
Print("[RsiMomEA] Gợi ý: cross UP nhưng EMA9>=WMA45 — không đủ điều kiện lõi BUY");
|
||
if(InpAtrExpFilterEnabled && crossUp > 0 && passAtrUp == 0)
|
||
Print("[RsiMomEA] Gợi ý: cross bị chặn ATR — hạ InpAtrExpMinRatio / InpAtrExpRiseBars hoặc tắt InpAtrExpFilterEnabled");
|
||
if(InpAdxFilterEnabled && crossUp > 0 && passAdxUp == 0)
|
||
Print("[RsiMomEA] Gợi ý: cross bị chặn ADX — hạ InpAdxMinLevel hoặc tắt InpAdxRequireDiDirection");
|
||
if(InpSwingStructFilterEnabled && crossUp > 0 && passSwingUp == 0)
|
||
Print("[RsiMomEA] Gợi ý: cross bị chặn 2 đáy — tăng lookback hoặc tắt InpSwingStructFilterEnabled");
|
||
if(InpSessionFilterEnabled && crossUp > 0 && passEnvUp == 0)
|
||
Print("[RsiMomEA] Gợi ý: cross bị chặn PHIÊN — chỉnh giờ London/NY (server) hoặc tắt InpSessionFilterEnabled");
|
||
}
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void EnsureBuffers(const int rates_total)
|
||
{
|
||
if (rates_total <= 0) return;
|
||
ArrayResize(buf_RSI, rates_total);
|
||
ArrayResize(buf_EMA9, rates_total);
|
||
ArrayResize(buf_WMA45,rates_total);
|
||
ArrayResize(buf_Signal,rates_total);
|
||
ArrayResize(buf_EMA200,rates_total);
|
||
ArrayResize(buf_Trend, rates_total);
|
||
ArraySetAsSeries(buf_RSI, true);
|
||
ArraySetAsSeries(buf_EMA9, true);
|
||
ArraySetAsSeries(buf_WMA45, true);
|
||
ArraySetAsSeries(buf_Signal, true);
|
||
ArraySetAsSeries(buf_EMA200, true);
|
||
ArraySetAsSeries(buf_Trend, true);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
int RsiMomentum_OnCalculate(const int rates_total, const int prev_calculated)
|
||
{
|
||
const int atrNeed = MathMax(1, InpAtrExpCompareBars) + MathMax(1, InpAtrExpRiseBars) + 5;
|
||
const int adxNeed = InpAdxFilterEnabled
|
||
? MathMax(InpAdxPeriod, 5) + MathMax(0, InpAdxRiseBars) + 3
|
||
: 0;
|
||
const int phaseNeed = InpPhaseFilterEnabled
|
||
? MathMax(InpPhaseExpandLookback, InpPhaseCoilLookback) + InpPhaseWmaFlatBars * 2 + 10
|
||
: 0;
|
||
const int minBars = MathMax(InpWMA45Period + InpRSIPeriod + 5 + phaseNeed,
|
||
MathMax(MathMax(InpAtrExpPeriod, InpSlAtrPeriod) + atrNeed, adxNeed));
|
||
if (rates_total < minBars) return 0;
|
||
|
||
EnsureBuffers(rates_total);
|
||
|
||
const int rsiBars = BarsCalculated(h_RSI);
|
||
const int ema9Bars = BarsCalculated(h_EMA9);
|
||
const int wmaBars = BarsCalculated(h_WMA45);
|
||
const int ema200Bars = BarsCalculated(h_EMA200);
|
||
const int atrRegBars = BarsCalculated(h_ATR_Regime);
|
||
const int adxBars = InpAdxFilterEnabled ? BarsCalculated(h_ADX) : 1;
|
||
if (rsiBars <= 0 || ema9Bars <= 0 || wmaBars <= 0 || ema200Bars <= 0 || atrRegBars <= 0
|
||
|| adxBars <= 0)
|
||
{
|
||
static datetime lastWarn = 0;
|
||
if (TimeCurrent() - lastWarn > 30)
|
||
{
|
||
PrintFormat("[RsiMomEA] Source not ready: RSI=%d EMA9=%d WMA45=%d EMA200=%d ATRreg=%d ADX=%d (rates=%d)",
|
||
rsiBars, ema9Bars, wmaBars, ema200Bars, atrRegBars, adxBars, rates_total);
|
||
lastWarn = TimeCurrent();
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
int srcMin = MathMin(MathMin(MathMin(MathMin(rsiBars, ema9Bars), wmaBars), ema200Bars), atrRegBars);
|
||
if(InpAdxFilterEnabled)
|
||
srcMin = MathMin(srcMin, adxBars);
|
||
const int copyN = MathMin(srcMin, rates_total);
|
||
if (copyN < minBars) return 0;
|
||
|
||
if (CopyBuffer(h_RSI, 0, 0, copyN, buf_RSI) <= 0) return 0;
|
||
if (CopyBuffer(h_EMA9, 0, 0, copyN, buf_EMA9) <= 0) return 0;
|
||
if (CopyBuffer(h_WMA45, 0, 0, copyN, buf_WMA45) <= 0) return 0;
|
||
|
||
int barsToScan = (prev_calculated == 0)
|
||
? rates_total - 2
|
||
: (rates_total - prev_calculated + 2);
|
||
barsToScan = MathMin(barsToScan, rates_total - 2);
|
||
|
||
const int trendN = InpTrendFilterEnabled ? MathMax(1, InpTrendConfirmBars) : 0;
|
||
const int need = MathMin(barsToScan + 2 + (InpTrendFilterEnabled ? trendN : 0), copyN);
|
||
|
||
datetime timeArr[];
|
||
double highArr[], lowArr[], closeArr[], ema200Arr[];
|
||
ArraySetAsSeries(timeArr, true);
|
||
ArraySetAsSeries(highArr, true);
|
||
ArraySetAsSeries(lowArr, true);
|
||
ArraySetAsSeries(closeArr, true);
|
||
ArraySetAsSeries(ema200Arr, true);
|
||
|
||
if (CopyTime (_Symbol, _Period, 0, need, timeArr) < need) return prev_calculated;
|
||
if (CopyHigh (_Symbol, _Period, 0, need, highArr) < need) return prev_calculated;
|
||
if (CopyLow (_Symbol, _Period, 0, need, lowArr) < need) return prev_calculated;
|
||
if (CopyClose (_Symbol, _Period, 0, need, closeArr) < need) return prev_calculated;
|
||
if (CopyBuffer(h_EMA200, 0, 0, need, ema200Arr) < need) return prev_calculated;
|
||
|
||
SignalScan_Run(barsToScan, rates_total, need, trendN, timeArr, highArr, lowArr, closeArr, ema200Arr);
|
||
Alerts_CheckAndFire(timeArr, closeArr, ema200Arr, need, rates_total);
|
||
Panel_Update(closeArr, ema200Arr, trendN, rates_total);
|
||
|
||
if(ChartRedrawEffective())
|
||
ChartRedraw(ActChart());
|
||
Diagnostics_FirstPass(rates_total, copyN, trendN, need, closeArr);
|
||
|
||
return copyN;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
int OnInit()
|
||
{
|
||
g_lastAlertBuyBar = 0;
|
||
g_lastAlertSellBar = 0;
|
||
g_dbgLogBarTime = 0;
|
||
g_dbgLogSide = 0;
|
||
g_firstCalc = true;
|
||
g_prevCalculated = 0;
|
||
g_tradeBarAnchor = iTime(_Symbol, _Period, 0);
|
||
g_pendingPlacedBarTime = 0;
|
||
Position_ResetPmState();
|
||
|
||
g_statExitDeals = 0;
|
||
g_statSL = 0;
|
||
g_statTP = 0;
|
||
g_statOther = 0;
|
||
g_statWins = 0;
|
||
g_statSumProfit = 0.0;
|
||
|
||
if(InpExportTradeJournal)
|
||
{
|
||
RsiMomJournal_ResetMonths();
|
||
if(InpJournalResetOnInit && MQLInfoInteger(MQL_TESTER))
|
||
RsiMomJournal_ResetFiles(_Symbol, _Period);
|
||
}
|
||
|
||
g_trade.SetExpertMagicNumber(InpMagic);
|
||
g_trade.SetDeviationInPoints(InpSlippagePoints);
|
||
SetTradeFillingFromSymbol();
|
||
|
||
if (!Handles_CreateAll())
|
||
return INIT_FAILED;
|
||
|
||
const long chInit = ActChart();
|
||
ObjectsDeleteAll(chInit, OBJ_PREFIX + "RSN_");
|
||
ObjectsDeleteAll(chInit, DBG_PREFIX);
|
||
if(DebugMarksEffective())
|
||
Signal_DebugConfigureChart(chInit);
|
||
|
||
if(InpShowPanel)
|
||
Panel_CreateAll();
|
||
if(InpShowStats)
|
||
{
|
||
Stats_CreateObjects();
|
||
Stats_UpdateDisplay();
|
||
}
|
||
const int spr = Env_CurrentSpreadPts();
|
||
string envWhy = "";
|
||
const bool envNow = Env_AllowsTradeAtBar(MathMax(1, InpSignalBarShift), envWhy);
|
||
if(InpExportTradeJournal)
|
||
Print("[RsiMomEA] Journal CSV: ", RsiMomJournal_TradesPath(_Symbol, _Period),
|
||
" | summary: ", RsiMomJournal_SummaryPath(_Symbol, _Period), " (FILE_COMMON)");
|
||
|
||
Print("[RsiMomEA] Init OK v", EA_VERSION_STR, " — entry=", EntryModeLabel(),
|
||
" | RSI×WMA45 + ", InpPhaseFilterEnabled ? "5phase" : "core",
|
||
" | EMA200=", InpTrendFilterEnabled ? "on" : "OFF",
|
||
" | session=", InpSessionFilterEnabled ? "on" : "OFF",
|
||
" | debugMarks=", InpDebugMarkSignals ? "on" : "off",
|
||
" | ATR+EMA200+phiên | trade=", InpTradeEnabled ? "on" : "off", " risk%=", InpRiskPercent,
|
||
" ATRexp=", InpAtrExpFilterEnabled
|
||
? StringFormat("ratio>=%.2f rise%d cmp%d", InpAtrExpMinRatio, InpAtrExpRiseBars, InpAtrExpCompareBars) : "OFF",
|
||
" ADX=", InpAdxFilterEnabled
|
||
? StringFormat(">=%.0f DI=%s rise%d", InpAdxMinLevel,
|
||
InpAdxRequireDiDirection ? "on" : "off", InpAdxRiseBars) : "OFF",
|
||
" session=", InpSessionFilterEnabled
|
||
? StringFormat("L%d-%d NY%d-%d avoid%d%s", InpLondonStartHour, InpLondonEndHour,
|
||
InpNYStartHour, InpNYEndHour, InpSessionAvoidLastMin,
|
||
InpEnvUseUtc ? " UTC" : " srv") : "off",
|
||
" spread=", InpSpreadFilterEnabled
|
||
? (InpSpreadSkipInTester && MQLInfoInteger(MQL_TESTER)
|
||
? "off-in-tester" : StringFormat("<=%d", InpMaxSpreadPoints)) : "off");
|
||
PrintFormat("[RsiMomEA] SYMBOL_SPREAD=%d pts | env@signalBar1=%s %s",
|
||
spr, envNow ? "OK" : "BLOCK", envWhy);
|
||
if(InpSpreadFilterEnabled && !InpSpreadSkipInTester && spr > InpMaxSpreadPoints)
|
||
PrintFormat("[RsiMomEA] CẢNH BÁO: spread tester %d > max %d → không vào lệnh. Tăng InpMaxSpreadPoints hoặc bật InpSpreadSkipInTester.",
|
||
spr, InpMaxSpreadPoints);
|
||
if(InpSessionFilterEnabled && !envNow &&
|
||
(StringFind(envWhy, "ngoài") >= 0 || StringFind(envWhy, "giao") >= 0))
|
||
Print("[RsiMomEA] Gợi ý phiên: chỉnh London/NY theo giờ SERVER (xem bar time trong tester), hoặc tắt InpSessionFilterEnabled để test.");
|
||
return INIT_SUCCEEDED;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void OnDeinit(const int reason)
|
||
{
|
||
if(InpExportTradeJournal)
|
||
RsiMomJournal_OnDeinit(_Symbol, _Period);
|
||
|
||
Pending_CancelMine();
|
||
Handles_ReleaseAll();
|
||
ObjectsDeleteAll(ActChart(), OBJ_PREFIX);
|
||
ObjectsDeleteAll(ActChart(), STAT_PREFIX);
|
||
ChartRedraw(ActChart());
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void OnTick()
|
||
{
|
||
if(EntryModeIsLimit())
|
||
{
|
||
Pending_EnvCancelIfBad();
|
||
Pending_ManageExpiry();
|
||
}
|
||
Position_ManageAt1R();
|
||
|
||
const datetime t0 = iTime(_Symbol, _Period, 0);
|
||
const bool newBar = (t0 != 0 && t0 != g_tradeBarAnchor);
|
||
const bool runCalc = (g_prevCalculated == 0)
|
||
|| newBar
|
||
|| !IsStrategyTester()
|
||
|| !InpTesterCalcOnNewBarOnly;
|
||
|
||
int calcRet = g_prevCalculated;
|
||
if(runCalc)
|
||
{
|
||
const int rates_total = Bars(_Symbol, _Period);
|
||
calcRet = RsiMomentum_OnCalculate(rates_total, g_prevCalculated);
|
||
if(calcRet != 0)
|
||
g_prevCalculated = calcRet;
|
||
}
|
||
|
||
if(newBar)
|
||
{
|
||
g_tradeBarAnchor = t0;
|
||
TradeTryOnBarOpen(calcRet);
|
||
}
|
||
|
||
if(InpShowPanel)
|
||
Panel_UpdateStatus();
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
|
||
{
|
||
if(!DebugMarksEffective() || !InpDebugHoverHint)
|
||
return;
|
||
|
||
const long ch = ActChart();
|
||
|
||
if(id == CHARTEVENT_MOUSE_MOVE)
|
||
{
|
||
Signal_DebugOnMouseMove(ch, DBG_PREFIX, (int)lparam, (int)dparam);
|
||
return;
|
||
}
|
||
|
||
if(id == CHARTEVENT_CHART_CHANGE || id == CHARTEVENT_CLICK)
|
||
{
|
||
Signal_DebugHideHoverHint(ch);
|
||
}
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void Position_ResetPmState()
|
||
{
|
||
g_pmTicket = 0;
|
||
g_pmInitialRisk = 0.0;
|
||
g_pmAt1RDone = false;
|
||
}
|
||
|
||
ulong Position_FindMyTicket()
|
||
{
|
||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||
{
|
||
const ulong ticket = PositionGetTicket(i);
|
||
if(ticket == 0)
|
||
continue;
|
||
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
|
||
continue;
|
||
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagic)
|
||
continue;
|
||
return ticket;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
double Position_VolumeStepDown(const double vol)
|
||
{
|
||
const double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
|
||
const double vmin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
||
if(step <= 0.0)
|
||
return vol;
|
||
|
||
double v = MathFloor(vol / step) * step;
|
||
if(v < vmin)
|
||
return 0.0;
|
||
return NormalizeDouble(v, 8);
|
||
}
|
||
|
||
void Position_ManageAt1R()
|
||
{
|
||
if(!InpManageAt1R)
|
||
return;
|
||
|
||
const ulong ticket = Position_FindMyTicket();
|
||
if(ticket == 0)
|
||
{
|
||
if(g_pmTicket != 0)
|
||
Position_ResetPmState();
|
||
return;
|
||
}
|
||
|
||
if(g_pmTicket != ticket)
|
||
{
|
||
g_pmTicket = ticket;
|
||
g_pmAt1RDone = false;
|
||
const double entry = PositionGetDouble(POSITION_PRICE_OPEN);
|
||
const double sl = PositionGetDouble(POSITION_SL);
|
||
g_pmInitialRisk = MathAbs(entry - sl);
|
||
if(g_pmInitialRisk < _Point * 2.0)
|
||
g_pmInitialRisk = 0.0;
|
||
}
|
||
|
||
if(g_pmAt1RDone || g_pmInitialRisk <= 0.0)
|
||
return;
|
||
|
||
if(!PositionSelectByTicket(ticket))
|
||
return;
|
||
|
||
MqlTick tk;
|
||
if(!SymbolInfoTick(_Symbol, tk))
|
||
return;
|
||
|
||
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||
const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||
const bool isBuy = (ptype == POSITION_TYPE_BUY);
|
||
const double entry = PositionGetDouble(POSITION_PRICE_OPEN);
|
||
const double cur = isBuy ? tk.bid : tk.ask;
|
||
const double profitDist = isBuy ? (cur - entry) : (entry - cur);
|
||
|
||
if(profitDist + _Point < g_pmInitialRisk)
|
||
return;
|
||
|
||
const double vol = PositionGetDouble(POSITION_VOLUME);
|
||
const double ratio = MathMax(0.01, MathMin(1.0, InpPartialCloseRatio));
|
||
double closeVol = Position_VolumeStepDown(vol * ratio);
|
||
const double vmin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
||
const double remain = vol - closeVol;
|
||
|
||
if(closeVol >= vmin && remain >= vmin)
|
||
{
|
||
if(g_trade.PositionClosePartial(ticket, closeVol))
|
||
Print("[RsiMomEA] Chốt ", DoubleToString(closeVol, 2), " lot (",
|
||
DoubleToString(ratio * 100.0, 0), "%) @ 1R — ticket #", ticket);
|
||
else
|
||
Print("[RsiMomEA] Partial close fail ", g_trade.ResultRetcode(), " ",
|
||
g_trade.ResultComment());
|
||
}
|
||
|
||
const double off = MathMax(0, InpBreakevenOffsetPts) * _Point;
|
||
double beSl = isBuy ? (entry - off) : (entry + off);
|
||
beSl = NormalizeDouble(beSl, dig);
|
||
|
||
const double tp = PositionGetDouble(POSITION_TP);
|
||
const double curSl = PositionGetDouble(POSITION_SL);
|
||
const bool needBe = isBuy ? (curSl < beSl - _Point) : (curSl > beSl + _Point);
|
||
|
||
if(needBe && StopsValid(isBuy, cur, beSl, tp))
|
||
{
|
||
if(g_trade.PositionModify(ticket, beSl, tp))
|
||
Print("[RsiMomEA] SL → entry (BE) @ ", DoubleToString(beSl, dig),
|
||
" sau khi đạt 1R — ticket #", ticket);
|
||
else
|
||
Print("[RsiMomEA] BE modify fail ", g_trade.ResultRetcode(), " ",
|
||
g_trade.ResultComment());
|
||
}
|
||
|
||
g_pmAt1RDone = true;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
bool Signal_BodyMidPrice(const int shift, double &midOut)
|
||
{
|
||
const double o = iOpen(_Symbol, _Period, shift);
|
||
const double c = iClose(_Symbol, _Period, shift);
|
||
midOut = (o + c) * 0.5;
|
||
return (midOut > 0.0);
|
||
}
|
||
|
||
bool LimitPriceValid(const bool isBuy, const double limitPx)
|
||
{
|
||
MqlTick tk;
|
||
if(!SymbolInfoTick(_Symbol, tk))
|
||
return false;
|
||
|
||
const int stops = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||
const int freeze = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);
|
||
const double md = (stops > freeze ? stops : freeze) * _Point;
|
||
|
||
if(isBuy)
|
||
return (limitPx < tk.ask - md);
|
||
return (limitPx > tk.bid + md);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
bool EntryModeIsLimit()
|
||
{
|
||
return (InpEntryMode == RSI_MOM_ENTRY_LIMIT_BODY50);
|
||
}
|
||
|
||
bool EntryModeIsMarket()
|
||
{
|
||
return (InpEntryMode == RSI_MOM_ENTRY_MARKET);
|
||
}
|
||
|
||
string EntryModeLabel()
|
||
{
|
||
return EntryModeIsMarket() ? "Market" : "Limit 50% body";
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void TradeExecuteOrder(const bool isBuy)
|
||
{
|
||
if(EntryModeIsMarket())
|
||
TradeExecuteMarketOrder(isBuy);
|
||
else
|
||
TradeExecuteLimitOrder(isBuy);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void TradeExecuteLimitOrder(const bool isBuy)
|
||
{
|
||
if (InpOnePositionFlat && HasMyMagicPositionOrPending())
|
||
return;
|
||
|
||
string envWhy = "";
|
||
if(!Env_AllowsTradeNow(envWhy))
|
||
{
|
||
Print("[RsiMomEA] Trade skip ", isBuy ? "BUY" : "SELL", ": ", envWhy,
|
||
"(spread=", Env_CurrentSpreadPts(), " pts)");
|
||
return;
|
||
}
|
||
|
||
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||
const int sigSh = MathMax(1, InpSignalBarShift);
|
||
|
||
double bodyMid = 0.0;
|
||
if(!Signal_BodyMidPrice(sigSh, bodyMid))
|
||
{
|
||
Print("[RsiMomEA] Trade skip: không lấy được 50% thân nến tín hiệu (shift ", sigSh, ")");
|
||
return;
|
||
}
|
||
const double entryPx = NormalizeDouble(bodyMid, dig);
|
||
|
||
if(!LimitPriceValid(isBuy, entryPx))
|
||
{
|
||
Print("[RsiMomEA] Trade skip ", isBuy ? "BUY" : "SELL",
|
||
" Limit @ ", DoubleToString(entryPx, dig),
|
||
" không hợp lệ (BUY Limit < Ask, SELL Limit > Bid)");
|
||
return;
|
||
}
|
||
|
||
Pending_CancelMine();
|
||
|
||
double sl = 0.0, tp = 0.0;
|
||
if (!NearestSwingSlTp(isBuy, entryPx, dig, sl, tp))
|
||
{
|
||
Print("[RsiMomEA] Trade skip: SL/TP swing không hợp lệ");
|
||
return;
|
||
}
|
||
if (!StopsValid(isBuy, entryPx, sl, tp))
|
||
{
|
||
Print("[RsiMomEA] Trade skip: STOPS_LEVEL / FREEZE");
|
||
return;
|
||
}
|
||
|
||
double vol = VolumeForRiskPercent(isBuy, entryPx, sl);
|
||
vol = NormalizeLots(vol);
|
||
if (vol <= 0.0)
|
||
{
|
||
Print("[RsiMomEA] Trade skip: volume=0");
|
||
return;
|
||
}
|
||
|
||
const bool ok = isBuy
|
||
? g_trade.BuyLimit(vol, entryPx, _Symbol, sl, tp, ORDER_TIME_GTC, 0, "RsiMom BUY body50")
|
||
: g_trade.SellLimit(vol, entryPx, _Symbol, sl, tp, ORDER_TIME_GTC, 0, "RsiMom SELL body50");
|
||
if (!ok)
|
||
Print("[RsiMomEA] Limit fail ", g_trade.ResultRetcode(), " ", g_trade.ResultComment());
|
||
else
|
||
{
|
||
g_pendingPlacedBarTime = iTime(_Symbol, _Period, 0);
|
||
Print("[RsiMomEA] Limit OK #", g_trade.ResultOrder(), " ", isBuy ? "BUY" : "SELL",
|
||
" @ ", DoubleToString(entryPx, dig), " (50% body bar ", sigSh, ")",
|
||
" vol=", vol, " SL=", DoubleToString(sl, dig), " TP=", DoubleToString(tp, dig),
|
||
" expireBars=", InpLimitExpireBars);
|
||
}
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void TradeExecuteMarketOrder(const bool isBuy)
|
||
{
|
||
if(InpOnePositionFlat && CountMyMagicPositions() > 0)
|
||
return;
|
||
|
||
string envWhy = "";
|
||
if(!Env_AllowsTradeNow(envWhy))
|
||
{
|
||
Print("[RsiMomEA] Trade skip ", isBuy ? "BUY" : "SELL", ": ", envWhy,
|
||
"(spread=", Env_CurrentSpreadPts(), " pts)");
|
||
return;
|
||
}
|
||
|
||
MqlTick tk;
|
||
if(!SymbolInfoTick(_Symbol, tk))
|
||
{
|
||
Print("[RsiMomEA] Trade skip: không lấy được tick");
|
||
return;
|
||
}
|
||
|
||
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||
const double entryPx = NormalizeDouble(isBuy ? tk.ask : tk.bid, dig);
|
||
|
||
double sl = 0.0, tp = 0.0;
|
||
if(!NearestSwingSlTp(isBuy, entryPx, dig, sl, tp))
|
||
{
|
||
Print("[RsiMomEA] Trade skip: SL/TP swing không hợp lệ");
|
||
return;
|
||
}
|
||
if(!StopsValid(isBuy, entryPx, sl, tp))
|
||
{
|
||
Print("[RsiMomEA] Trade skip: STOPS_LEVEL / FREEZE");
|
||
return;
|
||
}
|
||
|
||
double vol = VolumeForRiskPercent(isBuy, entryPx, sl);
|
||
vol = NormalizeLots(vol);
|
||
if(vol <= 0.0)
|
||
{
|
||
Print("[RsiMomEA] Trade skip: volume=0");
|
||
return;
|
||
}
|
||
|
||
const bool ok = isBuy
|
||
? g_trade.Buy(vol, _Symbol, 0.0, sl, tp, "RsiMom BUY mkt")
|
||
: g_trade.Sell(vol, _Symbol, 0.0, sl, tp, "RsiMom SELL mkt");
|
||
if(!ok)
|
||
Print("[RsiMomEA] Market fail ", g_trade.ResultRetcode(), " ", g_trade.ResultComment());
|
||
else
|
||
Print("[RsiMomEA] Market OK #", g_trade.ResultDeal(), " ", isBuy ? "BUY" : "SELL",
|
||
" @~", DoubleToString(entryPx, dig),
|
||
" vol=", vol, " SL=", DoubleToString(sl, dig), " TP=", DoubleToString(tp, dig));
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void TradeTryOnBarOpen(const int calcRet)
|
||
{
|
||
if (!InpTradeEnabled)
|
||
return;
|
||
if (!MQLInfoInteger(MQL_TESTER) && !TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
|
||
return;
|
||
if (calcRet <= 0)
|
||
return;
|
||
if (ArraySize(buf_Signal) < 2)
|
||
return;
|
||
|
||
const double s = buf_Signal[1];
|
||
if (s > -0.5 && s < 0.5)
|
||
return;
|
||
|
||
TradeExecuteOrder(s > 0.5);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
int CountMyMagicPendingOrders()
|
||
{
|
||
int n = 0;
|
||
for (int i = OrdersTotal() - 1; i >= 0; i--)
|
||
{
|
||
const ulong ticket = OrderGetTicket(i);
|
||
if (ticket == 0)
|
||
continue;
|
||
if (OrderGetString(ORDER_SYMBOL) != _Symbol)
|
||
continue;
|
||
if ((ulong)OrderGetInteger(ORDER_MAGIC) != InpMagic)
|
||
continue;
|
||
const ENUM_ORDER_TYPE t = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
|
||
if (t == ORDER_TYPE_BUY_LIMIT || t == ORDER_TYPE_SELL_LIMIT)
|
||
n++;
|
||
}
|
||
return n;
|
||
}
|
||
|
||
bool HasMyMagicPositionOrPending()
|
||
{
|
||
return (CountMyMagicPositions() > 0 || CountMyMagicPendingOrders() > 0);
|
||
}
|
||
|
||
void Pending_EnvCancelIfBad()
|
||
{
|
||
if(!InpSessionFilterEnabled && !InpSpreadFilterEnabled && !InpTransitionBlockEnabled)
|
||
return;
|
||
|
||
if(CountMyMagicPendingOrders() == 0)
|
||
return;
|
||
|
||
string why = "";
|
||
if(Env_AllowsTradeNow(why))
|
||
return;
|
||
|
||
Print("[RsiMomEA] Hủy Limit — môi trường trade: ", why,
|
||
" spread=", Env_CurrentSpreadPts(), " pts");
|
||
Pending_CancelMine();
|
||
}
|
||
|
||
bool Pending_CancelMine()
|
||
{
|
||
bool any = false;
|
||
for (int i = OrdersTotal() - 1; i >= 0; i--)
|
||
{
|
||
const ulong ticket = OrderGetTicket(i);
|
||
if (ticket == 0)
|
||
continue;
|
||
if (OrderGetString(ORDER_SYMBOL) != _Symbol)
|
||
continue;
|
||
if ((ulong)OrderGetInteger(ORDER_MAGIC) != InpMagic)
|
||
continue;
|
||
const ENUM_ORDER_TYPE t = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
|
||
if (t != ORDER_TYPE_BUY_LIMIT && t != ORDER_TYPE_SELL_LIMIT)
|
||
continue;
|
||
if (g_trade.OrderDelete(ticket))
|
||
any = true;
|
||
}
|
||
if (any)
|
||
g_pendingPlacedBarTime = 0;
|
||
return any;
|
||
}
|
||
|
||
void Pending_ManageExpiry()
|
||
{
|
||
if (InpLimitExpireBars <= 0)
|
||
return;
|
||
if (g_pendingPlacedBarTime == 0)
|
||
return;
|
||
|
||
int pendingCount = 0;
|
||
for (int i = OrdersTotal() - 1; i >= 0; i--)
|
||
{
|
||
const ulong ticket = OrderGetTicket(i);
|
||
if (ticket == 0)
|
||
continue;
|
||
if (OrderGetString(ORDER_SYMBOL) != _Symbol)
|
||
continue;
|
||
if ((ulong)OrderGetInteger(ORDER_MAGIC) != InpMagic)
|
||
continue;
|
||
const ENUM_ORDER_TYPE t = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
|
||
if (t != ORDER_TYPE_BUY_LIMIT && t != ORDER_TYPE_SELL_LIMIT)
|
||
continue;
|
||
pendingCount++;
|
||
}
|
||
|
||
if (pendingCount == 0)
|
||
{
|
||
g_pendingPlacedBarTime = 0;
|
||
return;
|
||
}
|
||
|
||
const int shift = iBarShift(_Symbol, _Period, g_pendingPlacedBarTime, true);
|
||
if (shift < 0)
|
||
return;
|
||
|
||
if (shift >= InpLimitExpireBars)
|
||
{
|
||
Print("[RsiMomEA] Hủy Limit sau ", shift, " nến (max ", InpLimitExpireBars, ")");
|
||
Pending_CancelMine();
|
||
}
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
//| Khoảng cách đẩy SL ra xa pivot: spread (+) ATR×mult nếu bật |
|
||
//+------------------------------------------------------------------+
|
||
double Sl_GetBufferDistance(const int atrShift = 1)
|
||
{
|
||
double dist = 0.0;
|
||
|
||
if (InpSlAtrAddSpread)
|
||
{
|
||
const int spr = (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
|
||
dist += spr * _Point;
|
||
}
|
||
|
||
if (InpSlAtrBufferEnabled && h_ATR != INVALID_HANDLE)
|
||
{
|
||
double atrBuf[];
|
||
ArraySetAsSeries(atrBuf, true);
|
||
if (CopyBuffer(h_ATR, 0, atrShift, 1, atrBuf) > 0 && atrBuf[0] > 0.0)
|
||
dist += atrBuf[0] * MathMax(0.0, InpSlAtrMultiplier);
|
||
}
|
||
|
||
return dist;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
bool NearestSwingSlTp(const bool isBuy, const double entry, const int dig, double &sl, double &tp)
|
||
{
|
||
const double rr = MathMax(0.01, InpRewardRiskRatio);
|
||
const int mx = MathMax(5, InpSwingMaxBars);
|
||
const double buf = Sl_GetBufferDistance(1);
|
||
|
||
if (isBuy)
|
||
{
|
||
double pivotLow = 0.0;
|
||
bool found = false;
|
||
for (int i = 2; i <= mx; i++)
|
||
{
|
||
const double L = iLow(_Symbol, _Period, i);
|
||
if (L < iLow(_Symbol, _Period, i - 1) && L < iLow(_Symbol, _Period, i + 1))
|
||
{
|
||
pivotLow = L;
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!found)
|
||
{
|
||
pivotLow = iLow(_Symbol, _Period, 2);
|
||
for (int j = 3; j <= mx; j++)
|
||
pivotLow = MathMin(pivotLow, iLow(_Symbol, _Period, j));
|
||
}
|
||
sl = NormalizeDouble(pivotLow - buf, dig);
|
||
const double risk = entry - sl;
|
||
if (risk <= _Point * 2)
|
||
return false;
|
||
tp = NormalizeDouble(entry + risk * rr, dig);
|
||
}
|
||
else
|
||
{
|
||
double pivotHigh = 0.0;
|
||
bool found = false;
|
||
for (int i = 2; i <= mx; i++)
|
||
{
|
||
const double H = iHigh(_Symbol, _Period, i);
|
||
if (H > iHigh(_Symbol, _Period, i - 1) && H > iHigh(_Symbol, _Period, i + 1))
|
||
{
|
||
pivotHigh = H;
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!found)
|
||
{
|
||
pivotHigh = iHigh(_Symbol, _Period, 2);
|
||
for (int j = 3; j <= mx; j++)
|
||
pivotHigh = MathMax(pivotHigh, iHigh(_Symbol, _Period, j));
|
||
}
|
||
sl = NormalizeDouble(pivotHigh + buf, dig);
|
||
const double risk = sl - entry;
|
||
if (risk <= _Point * 2)
|
||
return false;
|
||
tp = NormalizeDouble(entry - risk * rr, dig);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
bool StopsValid(const bool isBuy, const double price, const double sl, const double tp)
|
||
{
|
||
const int stops = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||
const int freeze = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);
|
||
const double md = (stops > freeze ? stops : freeze) * _Point;
|
||
if (md <= 0.0)
|
||
return true;
|
||
|
||
if (isBuy)
|
||
{
|
||
if (price - sl < md - _Point) return false;
|
||
if (tp - price < md - _Point) return false;
|
||
}
|
||
else
|
||
{
|
||
if (sl - price < md - _Point) return false;
|
||
if (price - tp < md - _Point) return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
int CountMyMagicPositions()
|
||
{
|
||
int n = 0;
|
||
for (int i = PositionsTotal() - 1; i >= 0; i--)
|
||
{
|
||
if (!PositionGetTicket(i))
|
||
continue;
|
||
if (PositionGetString(POSITION_SYMBOL) != _Symbol)
|
||
continue;
|
||
if ((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagic)
|
||
continue;
|
||
n++;
|
||
}
|
||
return n;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
double VolumeForRiskPercent(const bool isBuy, const double entryRef, const double slPrice)
|
||
{
|
||
if (MathAbs(entryRef - slPrice) < _Point)
|
||
return 0.0;
|
||
|
||
const double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
||
const double riskMoney = balance * (InpRiskPercent / 100.0);
|
||
|
||
double profit = 0.0;
|
||
if (!OrderCalcProfit(isBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL,
|
||
_Symbol, 1.0, entryRef, slPrice, profit))
|
||
return 0.0;
|
||
|
||
const double lossPerLot = MathAbs(profit);
|
||
if (lossPerLot < DBL_EPSILON)
|
||
return 0.0;
|
||
|
||
return riskMoney / lossPerLot;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
double NormalizeLots(double v)
|
||
{
|
||
const double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
|
||
const double vmin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
||
const double vmax = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
|
||
if (step <= 0.0)
|
||
return 0.0;
|
||
v = MathFloor(v / step) * step;
|
||
if (v < vmin - 1e-12)
|
||
return 0.0;
|
||
if (v > vmax)
|
||
v = vmax;
|
||
return NormalizeDouble(v, 8);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void SetTradeFillingFromSymbol()
|
||
{
|
||
const long fm = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
|
||
if ((fm & SYMBOL_FILLING_IOC) != 0)
|
||
g_trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||
else if ((fm & SYMBOL_FILLING_FOK) != 0)
|
||
g_trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||
else
|
||
g_trade.SetTypeFilling(ORDER_FILLING_RETURN);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
int Stats_LineStepPx()
|
||
{
|
||
return MathMax(12, InpStatFontSize + MathMax(8, InpStatLinePad));
|
||
}
|
||
|
||
void Stats_CreateObjects()
|
||
{
|
||
const long ch = ActChart();
|
||
const int fs = MathMax(7, InpStatFontSize);
|
||
const int step = Stats_LineStepPx();
|
||
const int y0 = MathMax(10, InpStatBottomMargin);
|
||
|
||
for (int k = 0; k < 3; k++)
|
||
{
|
||
const string name = (k == 0) ? STAT_L1 : ((k == 1) ? STAT_L2 : STAT_L3);
|
||
if (ObjectFind(ch, name) >= 0)
|
||
continue;
|
||
ObjectCreate(ch, name, OBJ_LABEL, 0, 0, 0);
|
||
ObjectSetInteger(ch, name, OBJPROP_CORNER, CORNER_LEFT_LOWER);
|
||
ObjectSetInteger(ch, name, OBJPROP_XDISTANCE, 8);
|
||
ObjectSetInteger(ch, name, OBJPROP_YDISTANCE, y0 + k * step);
|
||
ObjectSetInteger(ch, name, OBJPROP_FONTSIZE, fs);
|
||
ObjectSetString (ch, name, OBJPROP_FONT, "Consolas");
|
||
ObjectSetInteger(ch, name, OBJPROP_COLOR, InpStatColor);
|
||
ObjectSetInteger(ch, name, OBJPROP_BACK, false);
|
||
ObjectSetInteger(ch, name, OBJPROP_SELECTABLE, false);
|
||
ObjectSetInteger(ch, name, OBJPROP_HIDDEN, false);
|
||
ObjectSetString (ch, name, OBJPROP_TEXT, "");
|
||
}
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void Stats_UpdateDisplay()
|
||
{
|
||
const long ch = ActChart();
|
||
|
||
if (ObjectFind(ch, STAT_L1) < 0)
|
||
return;
|
||
|
||
if (!InpShowStats)
|
||
{
|
||
ObjectSetString(ch, STAT_L1, OBJPROP_TEXT, "");
|
||
ObjectSetString(ch, STAT_L2, OBJPROP_TEXT, "");
|
||
ObjectSetString(ch, STAT_L3, OBJPROP_TEXT, "");
|
||
if(ChartRedrawEffective())
|
||
ChartRedraw(ch);
|
||
return;
|
||
}
|
||
|
||
const int fs = MathMax(7, InpStatFontSize);
|
||
const int step = Stats_LineStepPx();
|
||
const int y0 = MathMax(10, InpStatBottomMargin);
|
||
for (int k = 0; k < 3; k++)
|
||
{
|
||
const string nm = (k == 0) ? STAT_L1 : ((k == 1) ? STAT_L2 : STAT_L3);
|
||
ObjectSetInteger(ch, nm, OBJPROP_FONTSIZE, fs);
|
||
ObjectSetInteger(ch, nm, OBJPROP_YDISTANCE, y0 + k * step);
|
||
}
|
||
|
||
double winrate = 0.0;
|
||
if (g_statExitDeals > 0)
|
||
winrate = 100.0 * (double)g_statWins / (double)g_statExitDeals;
|
||
|
||
const string cur = AccountInfoString(ACCOUNT_CURRENCY);
|
||
double avg = 0.0;
|
||
if (g_statExitDeals > 0)
|
||
avg = g_statSumProfit / (double)g_statExitDeals;
|
||
|
||
string line1 = StringFormat("Average Profit / trade: %s %s", DoubleToString(avg, 2), cur);
|
||
const string line2 = StringFormat("Winrate: %.1f%%", winrate);
|
||
string line3 = StringFormat("Total: %I64d | SL %I64d | TP %I64d",
|
||
g_statExitDeals, g_statSL, g_statTP);
|
||
if (g_statOther > 0)
|
||
line3 += StringFormat(" | Other %I64d", g_statOther);
|
||
|
||
ObjectSetString (ch, STAT_L3, OBJPROP_TEXT, line3);
|
||
ObjectSetInteger(ch, STAT_L3, OBJPROP_COLOR, InpStatColor);
|
||
ObjectSetString (ch, STAT_L2, OBJPROP_TEXT, line2);
|
||
ObjectSetInteger(ch, STAT_L2, OBJPROP_COLOR, InpStatColor);
|
||
ObjectSetString (ch, STAT_L1, OBJPROP_TEXT, line1);
|
||
ObjectSetInteger(ch, STAT_L1, OBJPROP_COLOR, InpStatColor);
|
||
ChartRedraw(ch);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void OnTradeTransaction(const MqlTradeTransaction &trans,
|
||
const MqlTradeRequest &request,
|
||
const MqlTradeResult &result)
|
||
{
|
||
if (trans.type != TRADE_TRANSACTION_DEAL_ADD)
|
||
return;
|
||
|
||
const ulong dealTicket = trans.deal;
|
||
if (dealTicket == 0)
|
||
return;
|
||
|
||
if (!HistoryDealSelect(dealTicket))
|
||
return;
|
||
|
||
if (HistoryDealGetString(dealTicket, DEAL_SYMBOL) != _Symbol)
|
||
return;
|
||
if ((ulong)HistoryDealGetInteger(dealTicket, DEAL_MAGIC) != InpMagic)
|
||
return;
|
||
|
||
const long entry = HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
|
||
if (entry != DEAL_ENTRY_OUT)
|
||
return;
|
||
|
||
const double profit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT)
|
||
+ HistoryDealGetDouble(dealTicket, DEAL_SWAP)
|
||
+ HistoryDealGetDouble(dealTicket, DEAL_COMMISSION);
|
||
|
||
const ENUM_DEAL_REASON reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(dealTicket, DEAL_REASON);
|
||
|
||
g_statExitDeals++;
|
||
g_statSumProfit += profit;
|
||
if (profit > 0.0)
|
||
g_statWins++;
|
||
|
||
if (reason == DEAL_REASON_SL)
|
||
g_statSL++;
|
||
else if (reason == DEAL_REASON_TP)
|
||
g_statTP++;
|
||
else
|
||
g_statOther++;
|
||
|
||
if(InpExportTradeJournal)
|
||
{
|
||
const ulong posId = (ulong)HistoryDealGetInteger(dealTicket, DEAL_POSITION_ID);
|
||
ulong entryDeal = 0;
|
||
datetime entryTime = 0;
|
||
long posType = 0;
|
||
double entryPrice = 0.0, sl = 0.0, tp = 0.0, vol = 0.0;
|
||
int sessionOk = 0;
|
||
if(RsiMomJournal_FindEntryDeal(posId, entryDeal, entryTime, posType,
|
||
entryPrice, sl, tp, vol))
|
||
{
|
||
string envWhy = "";
|
||
sessionOk = Env_AllowsSessionAt(entryTime, envWhy) ? 1 : 0;
|
||
}
|
||
|
||
RsiMomJournal_RecordClosedDeal(dealTicket, _Symbol, _Period, InpMagic,
|
||
h_RSI, h_WMA45, h_EMA200, h_ATR_Regime,
|
||
InpAtrExpCompareBars, InpAtrExpMinRatio,
|
||
sessionOk);
|
||
}
|
||
|
||
Stats_UpdateDisplay();
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|