#property copyright "Mark K Cowan" #property link "http://hackology.co.uk" // Equity interval at which to adjust the "bail out" threshold extern int bailInterval = 200; // Bail out threshold (at bailInterval, bailStopLoss = equity - bailThreshold) extern int bailThreshold = 100; // Limits on the lifetime of an order extern int orderCloseMaxMinutes = 9000; extern int orderCloseMinMinutes = 2000; // Size of buffer for moving average extern int movingAverageLength = 5; // Banned months bitmask (lowest bit not used as Month starts from 1) extern int bannedMonths = 4098; // Close half an order when mean profit is reached extern int closeHalfOnMeanProfit = 1; // Set stop loss to break even after mean profit is reached extern int breakEvenStopLossOnMeanProfit = 1; // Max consecutive losses before switching from buy to sell to buy (0 for random buy/sell) extern int maxConsecutiveLossesToSwitch = 2; // Max equity loss (before trader aborts) extern double maxEquityLoss = 50; // Delay when switching between buy/sell mode extern int switchDelay = 15; // Delay between close and Open extern int delayCloseToOpen = 5; // Lots per initial order extern double lotsPerOrder = 0.02; // Fatal error occurred; bool died = false; // Time of last tick (used to identify start of new bar) datetime lastTickTime = 0; // Remaining delay before next open int delay = 0; // Total orders closed int closed = 0; // Total profit double netProfit = 0; // Buying (1) or selling (0)? (ignored iff randomBuyOrSell) bool buysell = 1; // Current consecutive lossy orders int consecutiveLosses = 0; // Threshold at which to abort trading int bailStopLoss = 0; // Moving averages / gradients double ask[100], bid[100]; double askGradient = 0, bidGradient = 0; int maIdx = 0; // Initial balance double initialBalance; // Minimum equity permissable double minEquity; // Minimum stop loss gap int minTrail; /* * Initialisation */ int init() { initialBalance = AccountBalance(); minEquity = AccountEquity() - maxEquityLoss; minTrail = (MarketInfo(Symbol(), MODE_STOPLEVEL) + 1) * Point; Print("Simple EA initiated"); Print("Initial balance = ", initialBalance); Print("Initial equity = ", AccountEquity()); Print("Equity floor level = ", minTrail); Print("Stop level = ", minTrail); updateBailStopLoss(); return (0); } /* * Finalisation */ int deinit() { Print("Simple EA terminated"); return (0); } /* * Fuzzy floating-point comparison */ bool equal(double a, double b) { return (NormalizeDouble(a - b, Digits) == 0); } /* * Updates the delay */ void updateDelay(int value) { if (value > delay) { delay = value; } } /* * Closes half of an order */ void closeHalf() { bool success = OrderClose(OrderTicket(), NormalizeDouble(OrderLots() / 2, Digits), OrderClosePrice(), 3); errorCheck(success, "Failed half-closing order!"); } /* * Modifies the stop loss of a (assumed to be profiting) * order to ensure that it will break even */ void stopLossBreakEven() { if (equal(OrderStopLoss(), OrderOpenPrice())) { return; } double stopLoss; if (OrderType() == OP_BUY) { stopLoss = OrderOpenPrice() - minTrail; if (MathAbs(stopLoss - Bid) < minTrail) { stopLoss = Bid - minTrail; } } else if (OrderType() == OP_SELL) { stopLoss = OrderOpenPrice() + minTrail; if (MathAbs(stopLoss - Ask) < minTrail) { stopLoss = Ask + minTrail; } } if (MathAbs(OrderOpenPrice() - OrderClosePrice()) <= minTrail) { return; } bool success = OrderModify(OrderTicket(), OrderOpenPrice(), OrderOpenPrice(), 0, 0); bool error = GetLastError(); errorCheck(success || error == 1 || error == 130, "Failed stop-lossing order!"); } /* * Executed on each tick * * Updates pending orders, implementing the following: * + break even stop loss * + close half on mean profit */ void updatePendingOrders() { for (int i = 0; i < OrdersTotal(); i++) { bool success = OrderSelect(i, SELECT_BY_POS); errorCheck(success, "Failed to select order!"); if (closed > 0 && netProfit > 0) { double meanProfit = netProfit / closed; if (breakEvenStopLossOnMeanProfit && OrderProfit() >= meanProfit) { stopLossBreakEven(); } if (closeHalfOnMeanProfit && OrderProfit() >= meanProfit && equal(OrderLots(), lotsPerOrder)) { closeHalf(); } } } } /* * Executed on each tick * * Opens an order */ void openOrder() { int ticket; bool randomBuyOrSell = maxConsecutiveLossesToSwitch == 0; if ((buysell && !randomBuyOrSell) || ((MathRand() % 2) && randomBuyOrSell)) { if (askGradient >= 0) { ticket = OrderSend(Symbol(), OP_BUY, lotsPerOrder, Ask, Bid - minTrail, 0, 0); errorCheck(ticket != -1, "Failed sending buy order!"); } } else { if (bidGradient <= 0) { ticket = OrderSend(Symbol(), OP_SELL, lotsPerOrder, Bid, Ask + minTrail, 0, 0); errorCheck(ticket != -1, "Failed sending sell order!"); } } } /* * Executed on each tick * * Closes an order if relevant assertions and * limits are valid * * Only orders closed via this function will * contribute to the profit accumulator */ void closeOrder() { int minutesSinceOrder = (Time[0] - OrderOpenTime()) / 60; int randomMinutes = MathRand() % (orderCloseMaxMinutes - orderCloseMinMinutes) + orderCloseMinMinutes; if (minutesSinceOrder < randomMinutes) { return; } if (OrderProfit() > 0) { if (OrderType() == OP_BUY && bidGradient > 0 && randomMinutes < orderCloseMaxMinutes) { return; } if (OrderType() == OP_SELL && askGradient < 0 && randomMinutes < orderCloseMaxMinutes) { return; } } bool success = OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), 3); errorCheck(success, "Failed closing order!"); closed++; updateProfit(OrderProfit()); } /* * Executed on each tick * * Keeps track of consecutive losses */ void trackLosses(bool loss) { if (loss) { consecutiveLosses++; if (consecutiveLosses > maxConsecutiveLossesToSwitch) { buysell = !buysell; consecutiveLosses = 0; updateDelay(switchDelay); } } else { consecutiveLosses = 0; } } /* * Executed on each tick * * Updates the bail stop loss bar */ void updateBailStopLoss() { double equity = AccountEquity(); errorCheck(equity >= bailStopLoss, "Equity stop loss bar hit, bailing!"); double oldBailStopLoss = bailStopLoss; double newBailStopLoss = MathFloor(equity / bailInterval) * bailInterval - bailThreshold; if (newBailStopLoss < oldBailStopLoss) { return; } if (newBailStopLoss < initialBalance - bailThreshold) { newBailStopLoss = initialBalance - bailThreshold; } if (!equal(oldBailStopLoss, newBailStopLoss)) { Print("Equity=" + equity + "; Bail bar moved from ", oldBailStopLoss, " to ", newBailStopLoss); bailStopLoss = newBailStopLoss; } } /* * Closes all orders immediately */ void closeAllOrders() { if (OrdersTotal() == 0) { return; } bool success; Print("Closing all (", OrdersTotal(), ") orders"); for (int i = OrdersTotal() - 1; i >= 0; i--) { errorCheck(OrderSelect(i, SELECT_BY_POS), "Failed to select order!"); errorCheck(OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), 5), "Failed to close order!"); } } /* * Executed on each tick * * Updates the net profit accumulator * Triggers immediate closure of orders and termination * of the expert assistant if the bail limit is hit. */ void updateProfit(double delta) { netProfit += delta; updateDelay(delayCloseToOpen); trackLosses(delta < 0); } /* * Executed on each tick * * Updates the cyclic history buffers used to * calculate moving averages and gradients */ void updateMovingAverage() { ask[maIdx] = Ask; bid[maIdx] = Bid; int prevIdx = (maIdx + movingAverageLength - 1) % movingAverageLength; askGradient = (ask[maIdx] - ask[prevIdx]) / movingAverageLength; bidGradient = (bid[maIdx] - bid[prevIdx]) / movingAverageLength; maIdx = (maIdx + 1) % movingAverageLength; } /* * Tests the condtion, if false the trader is terminated * and all orders are closed. */ void errorCheck(bool condition, string msg) { if (!condition) { Print("ABORT: " + msg); /* * The following prevents a recursive errorCheck * in the event that orders cannot be closed */ if (!died) { died = true; closeAllOrders(); } } } /* * Returns true if we're at the start of a new bar */ bool isNewBar() { bool isNew = Time[0] != lastTickTime; lastTickTime = Time[0]; return (isNew); } /* * Executed on each tick * * Returns immediately unless a new bar has started * * If no orders are open, it opens an order. * Otherwise, it closes the last order in the list, * assuming relevant delays have expired. * */ int start() { if (died || !isNewBar() || ((Month() << 1) & bannedMonths)) { return (0); } errorCheck(AccountEquity() > minEquity, "Equity floor reached, bailing!"); updateBailStopLoss(); if (movingAverageLength > 1) { updateMovingAverage(); } updatePendingOrders(); if (OrdersTotal() == 0) { if (delay > 0) { delay--; } if (delay == 0) { openOrder(); } } else { errorCheck(OrderSelect(OrdersTotal() - 1, SELECT_BY_POS), "Failed to select order"); closeOrder(); } return (0); }