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);
| |||
}
| |||
}
| |||
//+------------------------------------------------------------------+
|