generated from Stingdondaleatih/Mql5
249 lines
8.9 KiB
MQL5
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);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|