Created
January 29, 2025 16:08
-
-
Save tonkla/6db43d9674f3754724f05c7e08e1eea8 to your computer and use it in GitHub Desktop.
A Bollinger Bands-based Expert Advisors (EA) for MetaTrader 5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#property copyright "Stradeji" | |
#property link "https://www.stradeji.com" | |
#property version "1.0" | |
#property strict | |
#include <Trade\Trade.mqh> | |
CTrade ctrade; | |
#define ACCOUNT_ID 0 | |
#define NOTIFY_URL "" | |
#define NOTIFY_SEC 0 | |
input int m_magic; | |
input int d_ma_period=1; | |
input ENUM_TIMEFRAMES d_timeframe; | |
input ENUM_TIMEFRAMES f_timeframe; | |
input ENUM_TIMEFRAMES g_timeframe; | |
input ENUM_TIMEFRAMES h_timeframe; | |
input ENUM_TIMEFRAMES m_timeframe; | |
input ENUM_MA_METHOD ma_method; | |
input int lots_mx; | |
input int max_orders; | |
input double order_gap_atr; | |
input double min_sl_atr; | |
input double max_sl_atr; | |
input double min_tp_atr; | |
input double max_tp_atr; | |
input double net_sl_usd; | |
input double net_tp_usd; | |
input double max_stop_usd; | |
double min_stop_usd=2, min_ts=0, percent_ts=0; | |
int start_hour=0, stop_hour=20; | |
double d_atr, f_bb_slope, g_bb_slope, h_bb_slope, h_bb_c, m_turn; | |
MqlDateTime time; | |
datetime started_at; | |
double max_upl, min_upl, now_upl, max_rpl, min_rpl, now_rpl, max_npl, min_npl, now_npl; | |
ulong buy_positions[], sell_positions[]; | |
double buy_nearest_price, sell_nearest_price; | |
int once_per_sec; | |
int OnInit() { | |
if (m_magic == 0) return INIT_FAILED; | |
ctrade.SetExpertMagicNumber(m_magic); | |
if (ACCOUNT_ID > 0 && AccountInfoInteger(ACCOUNT_LOGIN) != ACCOUNT_ID) return INIT_FAILED; | |
return INIT_SUCCEEDED; | |
} | |
void OnTick() { | |
TimeCurrent(time); | |
if (should_stop()) return; | |
get_ta_d(); | |
get_ta_f(); | |
get_ta_g(); | |
get_ta_h(); | |
get_ta_m(); | |
if (d_atr == 0) return; | |
get_pls(); | |
get_positions(); | |
open_buy(); | |
open_sell(); | |
close_buys(); | |
close_sells(); | |
} | |
void get_ta_d() { | |
int handle_ma_h = iMA(Symbol(), d_timeframe, d_ma_period, 0, ma_method, PRICE_HIGH); | |
int handle_ma_l = iMA(Symbol(), d_timeframe, d_ma_period, 0, ma_method, PRICE_LOW); | |
double buff_ma_h[]; | |
CopyBuffer(handle_ma_h, 0, 0, 1, buff_ma_h); | |
double buff_ma_l[]; | |
CopyBuffer(handle_ma_l, 0, 0, 1, buff_ma_l); | |
d_atr = buff_ma_h[0] - buff_ma_l[0]; | |
} | |
void get_ta_f() { | |
int handle_bb = iBands(Symbol(), f_timeframe, 20, 0, 2, PRICE_CLOSE); | |
double buff_bb_c[]; | |
CopyBuffer(handle_bb, 0, 0, 2, buff_bb_c); | |
f_bb_slope = buff_bb_c[1] - buff_bb_c[0]; | |
} | |
void get_ta_g() { | |
int handle_bb = iBands(Symbol(), g_timeframe, 20, 0, 2, PRICE_CLOSE); | |
double buff_bb_c[]; | |
CopyBuffer(handle_bb, 0, 0, 2, buff_bb_c); | |
g_bb_slope = buff_bb_c[1] - buff_bb_c[0]; | |
} | |
void get_ta_h() { | |
int handle_bb = iBands(Symbol(), h_timeframe, 20, 0, 2, PRICE_CLOSE); | |
double buff_bb_c[]; | |
CopyBuffer(handle_bb, 0, 0, 2, buff_bb_c); | |
h_bb_slope = buff_bb_c[1] - buff_bb_c[0]; | |
h_bb_c = buff_bb_c[1]; | |
} | |
void get_ta_m() { | |
int handle_ma_c = iMA(Symbol(), m_timeframe, d_ma_period, 0, ma_method, PRICE_WEIGHTED); | |
double buff_ma_c[]; | |
CopyBuffer(handle_ma_c, 0, 0, 4, buff_ma_c); | |
double ma_0 = buff_ma_c[3]; | |
double ma_1 = buff_ma_c[2]; | |
double ma_2 = buff_ma_c[1]; | |
double ma_3 = buff_ma_c[0]; | |
double slope_1 = ma_1 - ma_2; | |
double o_0 = iOpen(Symbol(), m_timeframe, 0); | |
double o_1 = iOpen(Symbol(), m_timeframe, 1); | |
double c_0 = iClose(Symbol(), m_timeframe, 0); | |
double c_1 = iClose(Symbol(), m_timeframe, 1); | |
double h_0 = iHigh(Symbol(), m_timeframe, 0); | |
double h_1 = iHigh(Symbol(), m_timeframe, 1); | |
double h_2 = iHigh(Symbol(), m_timeframe, 2); | |
double l_0 = iLow(Symbol(), m_timeframe, 0); | |
double l_1 = iLow(Symbol(), m_timeframe, 1); | |
double l_2 = iLow(Symbol(), m_timeframe, 2); | |
double hcl_1 = (c_1 - l_1) / (h_1 - l_1); | |
bool higher = slope_1 < 0 && l_1 > l_2 && c_1 > o_1 && hcl_1 > 0.7 && h_0 > h_1 && c_0 > o_0; | |
bool lower = slope_1 > 0 && h_1 < h_2 && c_1 < o_1 && hcl_1 < 0.3 && l_0 < l_1 && c_0 < o_0; | |
bool turn_up = ma_3 > ma_2 && ma_2 < ma_1; | |
bool turn_down = ma_3 < ma_2 && ma_2 > ma_1; | |
m_turn = (higher || turn_up) ? 1 : (lower || turn_down) ? -1 : 0; | |
} | |
void get_pls() { | |
if (time.sec == once_per_sec) return; | |
once_per_sec = time.sec; | |
if (started_at != iTime(Symbol(), PERIOD_D1, 0)) { | |
printf("NET: %.2f (%.2f %.2f)", now_npl, min_npl, max_npl); | |
min_upl = 0; | |
max_upl = 0; | |
now_upl = 0; | |
min_rpl = 0; | |
max_rpl = 0; | |
now_rpl = 0; | |
min_npl = 0; | |
max_npl = 0; | |
now_npl = 0; | |
started_at = iTime(Symbol(), PERIOD_D1, 0); | |
} | |
if (PositionsTotal() == 0 && max_upl != 0) { | |
max_upl = 0; | |
min_upl = 0; | |
} | |
now_upl = AccountInfoDouble(ACCOUNT_PROFIT); | |
if (now_upl == 0) return; | |
now_rpl = get_daily_realized_profit(); | |
now_npl = now_upl + now_rpl; | |
if (min_upl == 0 && max_upl == 0) { | |
min_upl = now_upl; | |
max_upl = now_upl; | |
} | |
if (now_upl < min_upl) min_upl = now_upl; | |
if (now_upl > max_upl) max_upl = now_upl; | |
if (min_rpl == 0 && max_rpl == 0) { | |
min_rpl = now_rpl; | |
max_rpl = now_rpl; | |
} | |
if (now_rpl < min_rpl) min_rpl = now_rpl; | |
if (now_rpl > max_rpl) max_rpl = now_rpl; | |
if (min_npl == 0 && max_npl == 0) { | |
min_npl = now_npl; | |
max_npl = now_npl; | |
} | |
if (now_npl < min_npl) min_npl = now_npl; | |
if (now_npl > max_npl) max_npl = now_npl; | |
if (time.sec == NOTIFY_SEC) notify_client(); | |
} | |
void notify_client() { | |
string aid = IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN)); | |
string str_upl = DoubleToString(now_upl, 2) +" (" + | |
(min_upl == 0 ? "0" : DoubleToString(min_upl, 2)) + " " + | |
(max_upl == 0 ? "0" : DoubleToString(max_upl, 2)) + ")"; | |
string str_rpl = (now_rpl == 0 ? "0" : DoubleToString(now_rpl, 2)) + " (" + | |
(min_rpl == 0 ? "0" : DoubleToString(min_rpl, 2)) + " " + | |
(max_rpl == 0 ? "0" : DoubleToString(max_rpl, 2)) + ")"; | |
string str_npl = (now_npl == 0 ? "0" : DoubleToString(now_npl, 2)) + " (" + | |
(min_npl == 0 ? "0" : DoubleToString(min_npl, 2)) + " " + | |
(max_npl == 0 ? "0" : DoubleToString(max_npl, 2)) + ")"; | |
string headers; | |
char post[], result[]; | |
string data = "{\"aid\":\""+ aid + | |
"\",\"upl\":\""+ str_upl + | |
"\",\"rpl\":\""+ str_rpl + | |
"\",\"net\":\""+ str_npl + "\"}"; | |
StringToCharArray(data, post, 0, StringLen(data), CP_UTF8); | |
WebRequest("POST", NOTIFY_URL, NULL, NULL, 5000, post, 0, result, headers); | |
} | |
bool is_trade_session() { | |
datetime tfm, tto, now; | |
MqlDateTime sfm, sto; | |
now = TimeCurrent(); | |
int session = 0; | |
while (true) { | |
if (!SymbolInfoSessionTrade(Symbol(), (ENUM_DAY_OF_WEEK)time.day_of_week, session, tfm, tto)) | |
break; | |
TimeToStruct(tfm, sfm); | |
TimeToStruct(tto, sto); | |
string str_fm = StringFormat("%d.%d.%d %d:%d:%d", | |
time.year, time.mon, time.day, sfm.hour, sfm.min, sfm.sec); | |
string str_to = StringFormat("%d.%d.%d %d:%d:%d", | |
time.year, time.mon, time.day, sto.hour, sto.min, sto.sec); | |
if (now > StringToTime(str_fm) && now < StringToTime(str_to)) | |
return true; | |
session++; | |
} | |
return false; | |
} | |
bool should_stop() { | |
if (time.hour == start_hour && !is_trade_session()) return true; | |
if (PositionsTotal() == 0) { | |
if (net_tp_usd > 0 && now_npl >= net_tp_usd) return true; | |
if (net_sl_usd < 0 && now_npl <= net_sl_usd) return true; | |
if (time.hour < start_hour || time.hour >= stop_hour) return true; | |
} | |
if (time.hour == 23 && time.min >= 50) close_all_positions(); | |
if (time.hour >= stop_hour && max_stop_usd > 0 && now_npl > max_stop_usd) close_all_positions(); | |
if (min_ts > 0 && percent_ts > 0 && max_upl > min_ts && now_upl <= max_upl * (100 - percent_ts) / 100) | |
close_all_positions(); | |
bool is_tp = net_tp_usd > 0 && now_upl > min_stop_usd && now_npl > net_tp_usd; | |
bool is_sl = net_sl_usd < 0 && now_upl < -min_stop_usd && now_npl < net_sl_usd; | |
if (is_tp || is_sl) close_all_positions(); | |
return false; | |
} | |
bool should_open_buy() { | |
if (m_turn <= 0) return false; | |
if (SymbolInfoDouble(Symbol(), SYMBOL_ASK) >= h_bb_c) return false; | |
return h_bb_slope > 0 || g_bb_slope > 0 || f_bb_slope > 0; | |
} | |
bool should_open_sell() { | |
if (m_turn >= 0) return false; | |
if (SymbolInfoDouble(Symbol(), SYMBOL_BID) <= h_bb_c) return false; | |
return h_bb_slope < 0 || g_bb_slope < 0 || f_bb_slope < 0; | |
} | |
bool should_close_buy() { | |
double price = SymbolInfoDouble(Symbol(), SYMBOL_BID); | |
double open_price = PositionGetDouble(POSITION_PRICE_OPEN); | |
double pl_pip = price - open_price; | |
if (max_tp_atr > 0 && pl_pip > max_tp_atr * d_atr) return true; | |
if (max_sl_atr < 0 && pl_pip < max_sl_atr * d_atr) return true; | |
return false; | |
} | |
bool should_close_sell() { | |
double price = SymbolInfoDouble(Symbol(), SYMBOL_ASK); | |
double open_price = PositionGetDouble(POSITION_PRICE_OPEN); | |
double pl_pip = open_price - price; | |
if (max_tp_atr > 0 && pl_pip > max_tp_atr * d_atr) return true; | |
if (max_sl_atr < 0 && pl_pip < max_sl_atr * d_atr) return true; | |
return false; | |
} | |
void open_buy() { | |
if (!should_open_buy()) return; | |
if (ArraySize(buy_positions) >= max_orders) return; | |
double price = SymbolInfoDouble(Symbol(), SYMBOL_ASK); | |
if (buy_nearest_price > 0 && MathAbs(buy_nearest_price - price) < order_gap_atr * d_atr) return; | |
close_all_sells(); | |
ctrade.PositionOpen(Symbol(), ORDER_TYPE_BUY, get_lots(), price, 0, 0, ""); | |
} | |
void open_sell() { | |
if (!should_open_sell()) return; | |
if (ArraySize(sell_positions) >= max_orders) return; | |
double price = SymbolInfoDouble(Symbol(), SYMBOL_BID); | |
if (sell_nearest_price > 0 && MathAbs(price - sell_nearest_price) < order_gap_atr * d_atr) return; | |
close_all_buys(); | |
ctrade.PositionOpen(Symbol(), ORDER_TYPE_SELL, get_lots(), price, 0, 0, ""); | |
} | |
void close_buys() { | |
for (int i = 0; i < ArraySize(buy_positions); i++) { | |
if (!PositionSelectByTicket(buy_positions[i])) continue; | |
if (should_close_buy()) ctrade.PositionClose(PositionGetInteger(POSITION_TICKET)); | |
} | |
} | |
void close_sells() { | |
for (int i = 0; i < ArraySize(sell_positions); i++) { | |
if (!PositionSelectByTicket(sell_positions[i])) continue; | |
if (should_close_sell()) ctrade.PositionClose(PositionGetInteger(POSITION_TICKET)); | |
} | |
} | |
double get_lots() { | |
double min_lots = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN); | |
return lots_mx > 0 ? lots_mx * min_lots : min_lots; | |
} | |
void get_positions() { | |
int size = 0; | |
ArrayFree(buy_positions); | |
ArrayFree(sell_positions); | |
double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK); | |
double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID); | |
for (int i = PositionsTotal() - 1; i >= 0; i--) { | |
ulong ticket = PositionGetTicket(i); | |
if (ticket == 0) continue; | |
if (Symbol() != PositionGetString(POSITION_SYMBOL) || | |
m_magic != PositionGetInteger(POSITION_MAGIC)) continue; | |
double open_price = PositionGetDouble(POSITION_PRICE_OPEN); | |
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { | |
size = ArraySize(buy_positions); | |
ArrayResize(buy_positions, size + 1); | |
buy_positions[size] = ticket; | |
if (buy_nearest_price == 0 || MathAbs(open_price - ask) < MathAbs(buy_nearest_price - ask)) { | |
buy_nearest_price = open_price; | |
} | |
} else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { | |
size = ArraySize(sell_positions); | |
ArrayResize(sell_positions, size + 1); | |
sell_positions[size] = ticket; | |
if (sell_nearest_price == 0 || MathAbs(open_price - bid) < MathAbs(sell_nearest_price - bid)) { | |
sell_nearest_price = open_price; | |
} | |
} | |
} | |
} | |
double get_daily_realized_profit() { | |
if (!HistorySelect(iTime(Symbol(), PERIOD_D1, 0), TimeCurrent())) return 0; | |
double rpl = 0; | |
for (int i = HistoryDealsTotal() - 1; i >= 0; i--) { | |
ulong ticket = HistoryDealGetTicket(i); | |
if (ticket == 0) continue; | |
if (HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN) continue; | |
rpl += HistoryDealGetDouble(ticket, DEAL_PROFIT); | |
} | |
return rpl; | |
} | |
int get_next_bar_mins() { | |
int tf = d_timeframe == PERIOD_H8 | |
? 8 : d_timeframe == PERIOD_H6 | |
? 6 : d_timeframe == PERIOD_H4 | |
? 4 : 1; | |
return (time.hour + 1) % tf == 0 ? 60 - time.min : 1440; | |
} | |
void close_all_buys() { | |
for (int i = 0; i < ArraySize(buy_positions); i++) { | |
if (!PositionSelectByTicket(buy_positions[i])) continue; | |
ctrade.PositionClose(PositionGetInteger(POSITION_TICKET)); | |
} | |
} | |
void close_all_sells() { | |
for (int i = 0; i < ArraySize(sell_positions); i++) { | |
if (!PositionSelectByTicket(sell_positions[i])) continue; | |
ctrade.PositionClose(PositionGetInteger(POSITION_TICKET)); | |
} | |
} | |
void close_all_positions() { | |
for (int i = PositionsTotal() - 1; i >= 0; i--) { | |
ulong ticket = PositionGetTicket(i); | |
if (ticket == 0) continue; | |
ctrade.PositionClose(ticket); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment