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