486 satır
21 KiB
MQL5
486 satır
21 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| Vwap to Close.mq5 |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2024, MetaQuotes Ltd."
|
|
#property link "https://www.mql5.com"
|
|
#property version "2.00"
|
|
#property strict
|
|
|
|
#include <Trade\Trade.mqh>
|
|
#include <Trade\PositionInfo.mqh>
|
|
#include <Trade\SymbolInfo.mqh>
|
|
|
|
//--- ENUMS
|
|
enum ENUM_VWAP_PERIOD { VWAP_DAILY, VWAP_WEEKLY, VWAP_MONTHLY };
|
|
enum ENUM_EXIT_MODE { EXIT_AT_VWAP, EXIT_TRAILING_ONLY, EXIT_OVERSHOOT_BUFFER, EXIT_LAYERED_SCALE, EXIT_STAGGERED_TRAIL };
|
|
|
|
//--- INPUTS
|
|
input group "=== Trading Settings ==="
|
|
input double InpBaseLot = 0.01; // Base Lot Size
|
|
input double InpLotMultiplier = 2.0; // Lot Scaling Multiplier
|
|
input int InpMaxLevels = 10; // Maximum Open Levels per side
|
|
input int InpStopLoss = 0; // Stop Loss (Points, 0 = Disabled)
|
|
input int InpTakeProfit = 0; // Take Profit (Points, 0 = Disabled)
|
|
input int InpMaxSpread = 40; // Max Allowed Spread (Points, 0 = Disabled)
|
|
|
|
input group "=== VWAP & Metric ==="
|
|
input ENUM_VWAP_PERIOD InpVWAPPeriod = VWAP_DAILY; // VWAP Anchor Period
|
|
input double InpThreshold = 0.005; // Metric Entry Step (e.g. 0.005)
|
|
input int InpStartHour = 0; // Start Hour for Daily/Weekly/Monthly
|
|
input int InpStartDay = 1; // Start Day (1=Mon) for Weekly
|
|
|
|
input group "=== Momentum & Filters ==="
|
|
input bool InpUseMomentumFilter = true; // Use Momentum Filter (M5 Direction)
|
|
input int InpMomentumBars = 1; // Momentum Lookback Bars (M5)
|
|
input bool InpUseNewsFilter = true; // Enable Calendar News Filter
|
|
input bool InpCloseBeforeNews = false; // Close Positions Before News
|
|
input int InpNewsBufferPre = 30; // Minutes Before News
|
|
input int InpNewsBufferPost = 30; // Minutes After News
|
|
|
|
input group "=== Exit Parameters ==="
|
|
input ENUM_EXIT_MODE InpExitMode = EXIT_AT_VWAP; // Exit Strategy
|
|
input int InpVWAPOvershoot = 50; // VWAP Overshoot (Points)
|
|
input bool InpUseTrailing = true; // Use Trailing Stop (for applicable modes)
|
|
input double InpExitThreshold = 0.002; // Metric Exit Threshold (e.g. 0.002)
|
|
input double InpTrailPercent = 0.75; // Trail Percent (0.75 = 3/4th distance)
|
|
input int InpTrailingStepPoints = 20; // Trailing Step (Points)
|
|
input bool InpUseBreakeven = true; // Use Breakeven
|
|
input int InpBreakevenTrigger = 100; // Breakeven Trigger (Points)
|
|
input int InpBreakevenOffset = 10; // Breakeven Offset (Points)
|
|
|
|
input group "=== Session Management ==="
|
|
input bool InpCloseAtSessionEnd = true; // Close before Session End
|
|
input int InpCloseBufferMinutes = 5; // Minutes before end to close
|
|
input bool InpCloseOnAnchorChange = false; // Close on Anchor Reset (e.g. New Day)
|
|
|
|
input group "=== Visuals (Tester Optimized) ==="
|
|
input bool InpShowVisuals = true; // Show Chart Objects (VWAP, Lines)
|
|
input ENUM_TIMEFRAMES InpRefreshRate = PERIOD_M10; // Visual Refresh Rate
|
|
input color InpVWAPColor = clrRoyalBlue; // VWAP Line Color
|
|
input color InpBuyEntryColor = clrLime; // Buy Entry Line Color
|
|
input color InpSellEntryColor = clrRed; // Sell Entry Line Color
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| STRUCTS |
|
|
//+------------------------------------------------------------------+
|
|
struct SMarketData
|
|
{
|
|
double vwap;
|
|
double mid;
|
|
double bid;
|
|
double ask;
|
|
int spread;
|
|
double metric;
|
|
datetime sessionEnd;
|
|
bool isSessionEnding;
|
|
bool isNewsImminent;
|
|
string newsMsg;
|
|
datetime anchor;
|
|
};
|
|
|
|
struct SPositionSummary
|
|
{
|
|
int buys;
|
|
int sells;
|
|
ulong newestBuy;
|
|
ulong newestSell;
|
|
double meanBuy;
|
|
double meanSell;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| HELPERS |
|
|
//+------------------------------------------------------------------+
|
|
// (Including CVwapCalc and CNewsFilter within expert scope for clean encapsulation)
|
|
|
|
class CVwapCalc
|
|
{
|
|
private:
|
|
double m_sumPriceVol, m_sumVol;
|
|
datetime m_lastAnchor, m_lastBarTime;
|
|
double m_currentVwap;
|
|
public:
|
|
CVwapCalc() : m_sumPriceVol(0), m_sumVol(0), m_lastAnchor(0), m_lastBarTime(0), m_currentVwap(0) {}
|
|
double GetVWAP() { return m_currentVwap; }
|
|
datetime GetAnchor() { return m_lastAnchor; }
|
|
bool Update(ENUM_VWAP_PERIOD p, int h, int d) {
|
|
datetime now = TimeCurrent();
|
|
datetime anchor = CalculateAnchor(now, p, h, d);
|
|
if(anchor != m_lastAnchor) { m_sumPriceVol = 0; m_sumVol = 0; m_lastBarTime = 0; m_lastAnchor = anchor; }
|
|
int bars = Bars(_Symbol, PERIOD_M1, m_lastBarTime > 0 ? m_lastBarTime : m_lastAnchor, now);
|
|
if(bars <= 0) return (m_currentVwap > 0);
|
|
MqlRates rates[];
|
|
if(CopyRates(_Symbol, PERIOD_M1, 0, bars, rates) > 0) {
|
|
for(int i=0; i<bars; i++) {
|
|
if(rates[i].time <= m_lastBarTime) continue;
|
|
double typ = (rates[i].high + rates[i].low + rates[i].close) / 3.0;
|
|
m_sumPriceVol += typ * (double)rates[i].tick_volume;
|
|
m_sumVol += (double)rates[i].tick_volume;
|
|
m_lastBarTime = rates[i].time;
|
|
}
|
|
}
|
|
if(m_sumVol > 0) m_currentVwap = m_sumPriceVol / m_sumVol;
|
|
return (m_currentVwap > 0);
|
|
}
|
|
private:
|
|
datetime CalculateAnchor(datetime now, ENUM_VWAP_PERIOD p, int h, int d) {
|
|
MqlDateTime dt; TimeToStruct(now, dt); dt.hour = h; dt.min = 0; dt.sec = 0;
|
|
datetime anchor = StructToTime(dt);
|
|
if(p == VWAP_DAILY) { if(anchor > now) anchor -= 86400; }
|
|
else if(p == VWAP_WEEKLY) { int offset = dt.day_of_week - d; if(offset < 0) offset += 7; anchor -= offset * 86400; if(anchor > now) anchor -= 7 * 86400; }
|
|
else if(p == VWAP_MONTHLY) { dt.day = 1; anchor = StructToTime(dt); if(anchor > now) { dt.mon--; if(dt.mon < 1) { dt.mon = 12; dt.year--; } anchor = StructToTime(dt); } }
|
|
return anchor;
|
|
}
|
|
};
|
|
|
|
class CNewsFilter
|
|
{
|
|
public:
|
|
bool IsNewsUpcoming(int bPre, int bPost, string &msg) {
|
|
MqlCalendarValue values[]; datetime now = TimeCurrent();
|
|
if(CalendarValueHistory(values, now, now + (bPre * 60)) > 0) {
|
|
for(int i=0; i<ArraySize(values); i++) {
|
|
MqlCalendarEvent ev; CalendarEventById(values[i].event_id, ev);
|
|
if(ev.importance == CALENDAR_IMPORTANCE_HIGH) {
|
|
MqlCalendarCountry co; CalendarCountryById(ev.country_id, co);
|
|
if(StringFind(_Symbol, co.currency) >= 0) { msg = ev.name + " (" + co.currency + ")"; return true; }
|
|
}
|
|
}
|
|
}
|
|
if(CalendarValueHistory(values, now - (bPost * 60), now) > 0) {
|
|
for(int i=0; i<ArraySize(values); i++) {
|
|
MqlCalendarEvent ev; CalendarEventById(values[i].event_id, ev);
|
|
if(ev.importance == CALENDAR_IMPORTANCE_HIGH) {
|
|
MqlCalendarCountry co; CalendarCountryById(ev.country_id, co);
|
|
if(StringFind(_Symbol, co.currency) >= 0) { msg = ev.name + " (Recent)"; return true; }
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| EXPERT ENGINE CLASS |
|
|
//+------------------------------------------------------------------+
|
|
class CVwapExpert
|
|
{
|
|
private:
|
|
CTrade m_trade;
|
|
CPositionInfo m_pos;
|
|
CSymbolInfo m_sym;
|
|
CVwapCalc m_vwap;
|
|
CNewsFilter m_news;
|
|
|
|
SMarketData m_data;
|
|
SPositionSummary m_summary;
|
|
|
|
public:
|
|
CVwapExpert() {}
|
|
|
|
bool Init()
|
|
{
|
|
if(!m_sym.Name(_Symbol)) return false;
|
|
m_trade.SetExpertMagicNumber(777111);
|
|
//ArrayFree(m_data.newsMsg);
|
|
ZeroMemory(m_data);
|
|
ZeroMemory(m_summary);
|
|
return true;
|
|
}
|
|
|
|
void Deinit() { Comment(""); ObjectsDeleteAll(0, "VC_"); }
|
|
|
|
void OnTick()
|
|
{
|
|
if(!UpdateMarketData()) return;
|
|
PerformPreScan();
|
|
|
|
HandlePositionManagement();
|
|
HandleEntryLogic();
|
|
|
|
RefreshUI();
|
|
}
|
|
|
|
private:
|
|
bool UpdateMarketData()
|
|
{
|
|
if(!m_vwap.Update(InpVWAPPeriod, InpStartHour, InpStartDay)) return false;
|
|
m_data.vwap = m_vwap.GetVWAP();
|
|
m_data.anchor = m_vwap.GetAnchor();
|
|
|
|
m_data.bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
m_data.ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
|
m_data.mid = (m_data.bid + m_data.ask) / 2.0;
|
|
m_data.spread = (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
|
|
|
|
if(m_data.mid <= 0) return false;
|
|
m_data.metric = m_data.vwap / m_data.mid;
|
|
|
|
// Session Tracking
|
|
if(InpVWAPPeriod == VWAP_DAILY) m_data.sessionEnd = m_data.anchor + 86400;
|
|
else if(InpVWAPPeriod == VWAP_WEEKLY) m_data.sessionEnd = m_data.anchor + 7 * 86400;
|
|
else if(InpVWAPPeriod == VWAP_MONTHLY) {
|
|
MqlDateTime dt; TimeToStruct(m_data.anchor, dt);
|
|
dt.mon++; if(dt.mon > 12) { dt.mon = 1; dt.year++; }
|
|
m_data.sessionEnd = StructToTime(dt);
|
|
}
|
|
m_data.isSessionEnding = (InpCloseAtSessionEnd && TimeCurrent() >= m_data.sessionEnd - (InpCloseBufferMinutes * 60));
|
|
|
|
// News tracking
|
|
m_data.isNewsImminent = (InpUseNewsFilter) ? m_news.IsNewsUpcoming(InpNewsBufferPre, InpNewsBufferPost, m_data.newsMsg) : false;
|
|
|
|
// Anchor Change Check
|
|
static datetime lastAnchor = 0;
|
|
if(InpCloseOnAnchorChange && lastAnchor != 0 && m_data.anchor != lastAnchor) {
|
|
CloseAllPositions("Anchor Reset");
|
|
}
|
|
lastAnchor = m_data.anchor;
|
|
|
|
return true;
|
|
}
|
|
|
|
void PerformPreScan()
|
|
{
|
|
ZeroMemory(m_summary);
|
|
datetime latestB = 0, latestS = 0;
|
|
double sumVB = 0, sumPVB = 0, sumVS = 0, sumPVS = 0;
|
|
|
|
for(int i=PositionsTotal()-1; i>=0; i--) {
|
|
if(m_pos.SelectByIndex(i) && m_pos.Symbol() == _Symbol && m_pos.Magic() == 777111) {
|
|
double v = m_pos.Volume();
|
|
double p = m_pos.PriceOpen();
|
|
if(m_pos.PositionType() == POSITION_TYPE_BUY) {
|
|
m_summary.buys++; sumVB += v; sumPVB += v * p;
|
|
if(m_pos.Time() > latestB) { latestB = m_pos.Time(); m_summary.newestBuy = m_pos.Ticket(); }
|
|
} else {
|
|
m_summary.sells++; sumVS += v; sumPVS += v * p;
|
|
if(m_pos.Time() > latestS) { latestS = m_pos.Time(); m_summary.newestSell = m_pos.Ticket(); }
|
|
}
|
|
}
|
|
}
|
|
m_summary.meanBuy = (sumVB > 0) ? (sumPVB / sumVB) : 0;
|
|
m_summary.meanSell = (sumVS > 0) ? (sumPVS / sumVS) : 0;
|
|
}
|
|
|
|
void HandlePositionManagement()
|
|
{
|
|
// 1. Mandatory Closes
|
|
if(m_data.isSessionEnding || (m_data.isNewsImminent && InpCloseBeforeNews)) {
|
|
CloseAllPositions("News/Session Buffer");
|
|
return;
|
|
}
|
|
|
|
// 2. Individual Position Exits
|
|
for(int i=PositionsTotal()-1; i>=0; i--) {
|
|
if(m_pos.SelectByIndex(i) && m_pos.Symbol() == _Symbol && m_pos.Magic() == 777111) {
|
|
if(ExecuteExitsForPosition()) continue; // If closed, move to next
|
|
}
|
|
}
|
|
|
|
// 3. Staggered Trail
|
|
if(InpExitMode == EXIT_STAGGERED_TRAIL) HandleStaggeredTrail();
|
|
}
|
|
|
|
bool ExecuteExitsForPosition()
|
|
{
|
|
ulong t = m_pos.Ticket();
|
|
ENUM_POSITION_TYPE type = m_pos.PositionType();
|
|
|
|
// A. Static Exits
|
|
if(InpExitMode == EXIT_AT_VWAP) {
|
|
if(type == POSITION_TYPE_BUY && m_data.bid >= m_data.vwap) return m_trade.PositionClose(t);
|
|
if(type == POSITION_TYPE_SELL && m_data.ask <= m_data.vwap) return m_trade.PositionClose(t);
|
|
}
|
|
else if(InpExitMode == EXIT_OVERSHOOT_BUFFER) {
|
|
double over = InpVWAPOvershoot * _Point;
|
|
if(type == POSITION_TYPE_BUY && m_data.bid >= m_data.vwap + over) return m_trade.PositionClose(t);
|
|
if(type == POSITION_TYPE_SELL && m_data.ask <= m_data.vwap - over) return m_trade.PositionClose(t);
|
|
}
|
|
|
|
// B. Breakeven
|
|
if(InpUseBreakeven) {
|
|
double open = m_pos.PriceOpen();
|
|
double sl = m_pos.StopLoss();
|
|
if(type == POSITION_TYPE_BUY && m_data.bid >= open + InpBreakevenTrigger * _Point) {
|
|
double nSl = NormalizeDouble(open + InpBreakevenOffset * _Point, _Digits);
|
|
if(sl < nSl) m_trade.PositionModify(t, nSl, m_pos.TakeProfit());
|
|
} else if(type == POSITION_TYPE_SELL && m_data.ask <= open - InpBreakevenTrigger * _Point) {
|
|
double nSl = NormalizeDouble(open - InpBreakevenOffset * _Point, _Digits);
|
|
if(sl == 0 || sl > nSl) m_trade.PositionModify(t, nSl, m_pos.TakeProfit());
|
|
}
|
|
}
|
|
|
|
// C. Standard Trailing / Layered
|
|
if(InpExitMode == EXIT_TRAILING_ONLY || InpUseTrailing || InpExitMode == EXIT_LAYERED_SCALE) {
|
|
bool isTarget = true;
|
|
if(InpExitMode == EXIT_LAYERED_SCALE) {
|
|
if(type == POSITION_TYPE_BUY && t != m_summary.newestBuy) isTarget = false;
|
|
if(type == POSITION_TYPE_SELL && t != m_summary.newestSell) isTarget = false;
|
|
}
|
|
|
|
if(isTarget && IsInProfitZone(type)) {
|
|
double offset = InpExitThreshold * InpTrailPercent;
|
|
double sl = m_pos.StopLoss();
|
|
if(type == POSITION_TYPE_BUY) {
|
|
double nSl = NormalizeDouble(m_data.vwap / (m_data.metric + offset), _Digits);
|
|
if(nSl > sl + InpTrailingStepPoints * _Point) m_trade.PositionModify(t, nSl, m_pos.TakeProfit());
|
|
} else {
|
|
double nSl = NormalizeDouble(m_data.vwap / (m_data.metric - offset), _Digits);
|
|
if(sl == 0 || nSl < sl - InpTrailingStepPoints * _Point) m_trade.PositionModify(t, nSl, m_pos.TakeProfit());
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void HandleStaggeredTrail()
|
|
{
|
|
ENUM_POSITION_TYPE sides[2] = {POSITION_TYPE_BUY, POSITION_TYPE_SELL};
|
|
for(int s=0; s<2; s++) {
|
|
if(!IsInProfitZone(sides[s])) continue;
|
|
|
|
int count = (sides[s] == POSITION_TYPE_BUY) ? m_summary.buys : m_summary.sells;
|
|
if(count < 1) continue;
|
|
|
|
// Collect and Sort
|
|
struct PData { ulong t; double v; double sl; }; PData d[]; ArrayResize(d, count); int idx=0;
|
|
for(int i=0; i<PositionsTotal(); i++) if(m_pos.SelectByIndex(i) && m_pos.Magic() == 777111 && m_pos.PositionType() == sides[s])
|
|
{ d[idx].t=m_pos.Ticket(); d[idx].v=m_pos.Volume(); d[idx].sl=m_pos.StopLoss(); idx++; }
|
|
for(int i=0; i<count-1; i++) for(int j=i+1; j<count; j++) if(d[i].v > d[j].v){ PData tmp = d[i]; d[i]=d[j]; d[j]=tmp; }
|
|
|
|
double baseOffset = InpExitThreshold * InpTrailPercent;
|
|
for(int i=0; i<count; i++) {
|
|
double mult = (count > 1) ? (2.0 - (1.5 * (double)i / (double)(count - 1))) : 1.0;
|
|
double metricOffset = baseOffset * mult;
|
|
if(sides[s] == POSITION_TYPE_BUY) {
|
|
double nSl = NormalizeDouble(m_data.vwap / (m_data.metric + metricOffset), _Digits);
|
|
if(nSl > d[i].sl) m_trade.PositionModify(d[i].t, nSl, 0);
|
|
} else {
|
|
double nSl = NormalizeDouble(m_data.vwap / (m_data.metric - metricOffset), _Digits);
|
|
if(d[i].sl == 0 || nSl < d[i].sl) m_trade.PositionModify(d[i].t, nSl, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void HandleEntryLogic()
|
|
{
|
|
if(m_data.isSessionEnding || m_data.isNewsImminent) return;
|
|
if(InpMaxSpread > 0 && m_data.spread > InpMaxSpread) return;
|
|
|
|
// Buys
|
|
double nextB = 1.0 + (InpThreshold * (m_summary.buys + 1));
|
|
if(m_summary.buys < InpMaxLevels && m_data.metric >= nextB) {
|
|
if(!InpUseMomentumFilter || IsMomOk(POSITION_TYPE_BUY)) {
|
|
double lot = NormalizeDouble(InpBaseLot * MathPow(InpLotMultiplier, m_summary.buys), 2);
|
|
double sl = (InpStopLoss > 0) ? m_data.ask - InpStopLoss * _Point : 0;
|
|
double tp = (InpTakeProfit > 0) ? m_data.ask + InpTakeProfit * _Point : 0;
|
|
m_trade.Buy(lot, _Symbol, m_data.ask, sl, tp, "VwapExpertB" + (string)(m_summary.buys+1));
|
|
}
|
|
}
|
|
|
|
// Sells
|
|
double nextS = 1.0 - (InpThreshold * (m_summary.sells + 1));
|
|
if(m_summary.sells < InpMaxLevels && m_data.metric <= nextS) {
|
|
if(!InpUseMomentumFilter || IsMomOk(POSITION_TYPE_SELL)) {
|
|
double lot = NormalizeDouble(InpBaseLot * MathPow(InpLotMultiplier, m_summary.sells), 2);
|
|
double sl = (InpStopLoss > 0) ? m_data.bid + InpStopLoss * _Point : 0;
|
|
double tp = (InpTakeProfit > 0) ? m_data.bid - InpTakeProfit * _Point : 0;
|
|
m_trade.Sell(lot, _Symbol, m_data.bid, sl, tp, "VwapExpertS" + (string)(m_summary.sells+1));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool IsInProfitZone(ENUM_POSITION_TYPE type)
|
|
{
|
|
if(type == POSITION_TYPE_BUY) return (m_data.bid > m_summary.meanBuy && m_data.bid < m_data.vwap);
|
|
return (m_data.ask < m_summary.meanSell && m_data.ask > m_data.vwap);
|
|
}
|
|
|
|
bool IsMomOk(ENUM_POSITION_TYPE type)
|
|
{
|
|
double p0 = iClose(_Symbol, PERIOD_M5, 0); // use current candle vs lookback
|
|
double p1 = iClose(_Symbol, PERIOD_M5, InpMomentumBars);
|
|
if(p1 <= 0) return false;
|
|
return (type == POSITION_TYPE_BUY) ? (p0 > p1) : (p0 < p1);
|
|
}
|
|
|
|
void CloseAllPositions(string reason)
|
|
{
|
|
Print("Closing All Positions: ", reason);
|
|
for(int i=PositionsTotal()-1; i>=0; i--)
|
|
if(m_pos.SelectByIndex(i) && m_pos.Magic() == 777111) m_trade.PositionClose(m_pos.Ticket());
|
|
}
|
|
|
|
void RefreshUI()
|
|
{
|
|
static datetime lastVisual = 0;
|
|
static uint lastComment = 0;
|
|
|
|
bool isTester = MQLInfoInteger(MQL_TESTER);
|
|
bool isVisual = MQLInfoInteger(MQL_VISUAL_MODE);
|
|
|
|
// Objects
|
|
if(InpShowVisuals && (!isTester || isVisual)) {
|
|
datetime curM = iTime(_Symbol, InpRefreshRate, 0);
|
|
if(curM != lastVisual) {
|
|
UpdateObjects();
|
|
lastVisual = curM;
|
|
}
|
|
}
|
|
|
|
// Comments
|
|
if(GetTickCount() - lastComment > 500) {
|
|
UpdateComments();
|
|
lastComment = GetTickCount();
|
|
}
|
|
}
|
|
|
|
void UpdateObjects()
|
|
{
|
|
DrawHLine("VC_VWAP", m_data.vwap, InpVWAPColor, STYLE_SOLID, 2, "VWAP");
|
|
DrawHLine("VC_TARGET", m_data.vwap, clrGray, STYLE_DASH, 1, "Exit Target"); // effVwap is same as vwap now
|
|
for(int i=1; i<=3; i++) {
|
|
DrawHLine("VC_BUY_"+(string)i, m_data.vwap / (1.0 + InpThreshold * i), InpBuyEntryColor, STYLE_DOT, 1, "Buy "+(string)i);
|
|
DrawHLine("VC_SELL_"+(string)i, m_data.vwap / (1.0 - InpThreshold * i), InpSellEntryColor, STYLE_DOT, 1, "Sell "+(string)i);
|
|
}
|
|
}
|
|
|
|
void DrawHLine(string name, double price, color clr, ENUM_LINE_STYLE style, int width, string text)
|
|
{
|
|
if(price <= 0) return;
|
|
if(ObjectFind(0, name) < 0) ObjectCreate(0, name, OBJ_HLINE, 0, 0, price);
|
|
ObjectSetDouble(0, name, OBJPROP_PRICE, price);
|
|
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
|
|
ObjectSetInteger(0, name, OBJPROP_STYLE, style);
|
|
ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
|
|
ObjectSetString(0, name, OBJPROP_TEXT, text);
|
|
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
|
|
ObjectSetInteger(0, name, OBJPROP_BACK, true);
|
|
}
|
|
|
|
void UpdateComments()
|
|
{
|
|
string comment = StringFormat("=== Vwap Expert [v2.0] ===\n"
|
|
"VWAP: %.5f | Metric: %.5f\n"
|
|
"Spread: %d | Buys: %d | Sells: %d\n"
|
|
"Mean Entry: B %.5f / S %.5f\n"
|
|
"News: %s\n"
|
|
"Session: %s",
|
|
m_data.vwap, m_data.metric,
|
|
m_data.spread, m_summary.buys, m_summary.sells,
|
|
m_summary.meanBuy, m_summary.meanSell,
|
|
(m_data.isNewsImminent ? "!!! "+m_data.newsMsg+" !!!" : "Clear"),
|
|
(m_data.isSessionEnding ? "CLOSING SOON" : TimeToString(m_data.sessionEnd)));
|
|
Comment(comment);
|
|
}
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| GLOBAL INSTANCE AND HANDLERS |
|
|
//+------------------------------------------------------------------+
|
|
CVwapExpert expert;
|
|
|
|
int OnInit() { return expert.Init() ? INIT_SUCCEEDED : INIT_FAILED; }
|
|
void OnDeinit(const int reason) { expert.Deinit(); }
|
|
void OnTick() { expert.OnTick(); }
|