Mql5_scalper_voom/SpikeReversalStacker.mq5
2026-09-02 01:00:12 +00:00

249 lines
8.9 KiB
MQL5

//+------------------------------------------------------------------+
//| SpikeReversalStacker.mq5 |
//| Stacks market orders in the direction of the current move. |
//| No SL/TP. Closes the whole stack and flips direction the instant |
//| a spike reversal is detected against the current trend. |
//| Designed for Boom/Crash-style synthetic indices, but works on |
//| any symbol. |
//+------------------------------------------------------------------+
#property copyright "Carson"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
CTrade trade;
//---------------------------------------------------------------- Inputs
input double InitialLot = 0.01; // Lot size for every order
input int StackStepPoints = 150; // New order every X points of continuation
input int MaxStackOrders = 8; // Cap on orders per side (0 = unlimited)
input int SpikeReversalPoints = 300; // Points against the trend that count as a "spike"
input int SpikeWindowSeconds = 3; // Spike must happen within this many seconds
input ulong MagicNumber = 100100;
//---------------------------------------------------------------- State
enum TrendState { TREND_NONE = 0, TREND_UP = 1, TREND_DOWN = -1 };
TrendState currentTrend = TREND_NONE;
double extremePrice = 0; // best price reached in current trend direction
datetime extremeTime = 0; // when that extreme was set
double lastStackPrice = 0; // price at which the last stack order was opened
//---------------------------------------------------------------- Tick buffer (for time-boxed spike check)
#define TICK_BUFFER_SIZE 2048
double tbPrice[TICK_BUFFER_SIZE];
long tbTimeMs[TICK_BUFFER_SIZE];
int tbHead = 0; // index of most recently written tick
int tbCount = 0; // how many valid entries so far (caps at TICK_BUFFER_SIZE)
void PushTick(double price)
{
tbHead = (tbHead + 1) % TICK_BUFFER_SIZE;
tbPrice[tbHead] = price;
tbTimeMs[tbHead] = GetTickCount(); // ms-resolution local counter, fine for a few-second window
if(tbCount < TICK_BUFFER_SIZE) tbCount++;
}
// Returns the most extreme price reached within the last windowSeconds,
// in the direction needed (highest high for TREND_UP, lowest low for TREND_DOWN).
// Returns false if not enough history yet.
bool GetWindowExtreme(TrendState dir, int windowSeconds, double &outExtreme)
{
if(tbCount == 0) return false;
long nowMs = GetTickCount();
long cutoffMs = nowMs - (long)windowSeconds * 1000;
bool found = false;
double best = 0;
int idx = tbHead;
for(int i = 0; i < tbCount; i++)
{
if(tbTimeMs[idx] < cutoffMs) break; // buffer is chronological walking backwards
double p = tbPrice[idx];
if(!found)
{
best = p;
found = true;
}
else
{
if(dir == TREND_UP && p > best) best = p;
if(dir == TREND_DOWN && p < best) best = p;
}
idx = (idx - 1 + TICK_BUFFER_SIZE) % TICK_BUFFER_SIZE;
}
if(found) outExtreme = best;
return found;
}
//+------------------------------------------------------------------+
int OnInit()
{
trade.SetExpertMagicNumber(MagicNumber);
currentTrend = TREND_NONE;
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
void OnTick()
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
PushTick(bid); // record every tick for the time-boxed spike window
// No position yet: wait for a first directional push to establish trend
if(currentTrend == TREND_NONE)
{
InitializeFirstTrend(bid, ask, point);
return;
}
// Update extreme + check for stacking opportunity
UpdateExtremeAndStack(bid, ask, point);
// Check for spike reversal against current trend
CheckSpikeReversal(bid, ask, point);
}
//+------------------------------------------------------------------+
//| Establish initial trend direction from the last two candles |
//+------------------------------------------------------------------+
void InitializeFirstTrend(double bid, double ask, double point)
{
double closePrev = iClose(_Symbol, PERIOD_M1, 1);
double closePrev2 = iClose(_Symbol, PERIOD_M1, 2);
if(closePrev == 0 || closePrev2 == 0) return;
if(closePrev > closePrev2)
{
currentTrend = TREND_UP;
extremePrice = bid;
}
else if(closePrev < closePrev2)
{
currentTrend = TREND_DOWN;
extremePrice = bid;
}
else
return; // flat, wait for next tick
extremeTime = TimeCurrent();
lastStackPrice = (currentTrend == TREND_UP ? ask : bid);
OpenOrder(currentTrend);
}
//+------------------------------------------------------------------+
//| Track the running extreme; open a new stacked order every |
//| StackStepPoints of continuation in the trend direction |
//+------------------------------------------------------------------+
void UpdateExtremeAndStack(double bid, double ask, double point)
{
if(currentTrend == TREND_UP)
{
if(bid > extremePrice)
{
extremePrice = bid;
extremeTime = TimeCurrent();
}
if(ask - lastStackPrice >= StackStepPoints * point && CountOpenOrders(TREND_UP) < GetMaxAllowed())
{
OpenOrder(TREND_UP);
lastStackPrice = ask;
}
}
else if(currentTrend == TREND_DOWN)
{
if(bid < extremePrice)
{
extremePrice = bid;
extremeTime = TimeCurrent();
}
if(lastStackPrice - bid >= StackStepPoints * point && CountOpenOrders(TREND_DOWN) < GetMaxAllowed())
{
OpenOrder(TREND_DOWN);
lastStackPrice = bid;
}
}
}
int GetMaxAllowed()
{
return (MaxStackOrders <= 0 ? INT_MAX : MaxStackOrders);
}
//+------------------------------------------------------------------+
//| Spike detection: price snaps back by more than SpikeReversalPoints|
//| measured from the extreme reached WITHIN the last |
//| SpikeWindowSeconds only (not the all-time trend extreme) — this |
//| is what makes it a fast spike check rather than a slow-drift one. |
//+------------------------------------------------------------------+
void CheckSpikeReversal(double bid, double ask, double point)
{
double windowExtreme;
if(!GetWindowExtreme(currentTrend, SpikeWindowSeconds, windowExtreme))
return; // not enough tick history yet
double movePoints = 0;
if(currentTrend == TREND_UP)
movePoints = (windowExtreme - bid) / point; // fall from the recent up-extreme
else if(currentTrend == TREND_DOWN)
movePoints = (bid - windowExtreme) / point; // rise from the recent down-extreme
if(movePoints >= SpikeReversalPoints)
{
// Spike confirmed against current trend -> close stack and flip
TrendState newTrend = (currentTrend == TREND_UP) ? TREND_DOWN : TREND_UP;
CloseAllOrders(currentTrend);
currentTrend = newTrend;
extremePrice = bid;
extremeTime = TimeCurrent();
lastStackPrice = (newTrend == TREND_UP ? ask : bid);
tbCount = 0; tbHead = 0; // clear tick window so the old trend's extreme can't linger
PushTick(bid);
OpenOrder(newTrend);
}
}
//+------------------------------------------------------------------+
//| Order helpers |
//+------------------------------------------------------------------+
void OpenOrder(TrendState dir)
{
if(dir == TREND_UP)
trade.Buy(InitialLot, _Symbol, 0, 0, 0, "StackUp");
else if(dir == TREND_DOWN)
trade.Sell(InitialLot, _Symbol, 0, 0, 0, "StackDown");
}
int CountOpenOrders(TrendState dir)
{
int count = 0;
ENUM_POSITION_TYPE wantType = (dir == TREND_UP) ? POSITION_TYPE_BUY : POSITION_TYPE_SELL;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket <= 0) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC) != (long)MagicNumber) continue;
if(PositionGetInteger(POSITION_TYPE) == wantType) count++;
}
return count;
}
void CloseAllOrders(TrendState dir)
{
ENUM_POSITION_TYPE wantType = (dir == TREND_UP) ? POSITION_TYPE_BUY : POSITION_TYPE_SELL;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket <= 0) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC) != (long)MagicNumber) continue;
if(PositionGetInteger(POSITION_TYPE) == wantType)
trade.PositionClose(ticket);
}
}
//+------------------------------------------------------------------+