Skip to content

Instantly share code, notes, and snippets.

@tonkla
Created January 29, 2025 16:08
Show Gist options
  • Save tonkla/6db43d9674f3754724f05c7e08e1eea8 to your computer and use it in GitHub Desktop.
Save tonkla/6db43d9674f3754724f05c7e08e1eea8 to your computer and use it in GitHub Desktop.
A Bollinger Bands-based Expert Advisors (EA) for MetaTrader 5
#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