CSP/CSP.mq5
super.admin d1223142f5 convert
2025-05-30 14:48:02 +02:00

570 lines
42 KiB
MQL5


#include <trade\symbolInfo.mqh>
#include <trade\PositionInfo.mqh>
#include <trade\trade.mqh>
#include <trade\DealInfo.mqh>
// Static class objects
CSymbolInfo symbolInfo;
CPositionInfo position;
CTrade trade;
CDealInfo deal;
enum ENUM_ENTRY_STRAT
{
SupportResistance,
ThreeLastCandles,
Both,
};
enum ENUM_EXIT_STRAT
{
StoplossTakeProfit,
TrailingSL
};
enum ENUM_MY_TIMEFRAMES
{
M1 = PERIOD_M1,
M5 = PERIOD_M5,
M15 = PERIOD_M15,
M30 = PERIOD_M30,
H1 = PERIOD_H1,
H4 = PERIOD_H4,
H12 = PERIOD_H12,
D1 = PERIOD_D1
};
enum ENUM_TRIGGER
{
Buy,
Sell,
DoNothing
};
input group "Money Management";
input double IN_LotMultiplier = 4.0; //Lot Multiplier
input group "Trade Settings";
input ENUM_EXIT_STRAT IN_ExitStrat = StoplossTakeProfit; //Exit-Strategy
input int IN_TakeProfit = 200; //Takeprofit in points (Set to 1000 when using TrailingSL)
input int IN_StopLoss = 100; //Stoploss in points
input int IN_TrailingStopLossTrigger = 50; //Trailing SL Trigger in points
input int IN_MaxSlippage = 3; //Max Slippage in points
input ENUM_MY_TIMEFRAMES IN_Timeframe = H1; //Timeframe
input ulong IN_MagicNumber = 220193; //Magic number
input group "Long Wick Candle";
input bool ActiveLongWick = true; //Use Long Wick Candle
input ENUM_ENTRY_STRAT EntryStratLWC = Both; //Keylevel
input int MaxBodySizeLWC = 10; //Max Body Size in points
input int MinWickLenLWC = 50; //Min Wick Length in points
input group "Inverted Long Wick Candle";
input bool ActiveInvertedWick = true; //Use Inverted Long Wick Candle
input ENUM_ENTRY_STRAT EntryStratILW = Both; //Keylevel
input int MaxBodySizeILW = 10; //Max Body Size in points
input int MinWickLenILW = 50; //Min Wick Length in points
input group "Inside Bar";
input bool ActiveInsideBar = true; //Use Inverted Long Wick Candle
input int MinBodySizeINB = 10; //Minimum Body Size in points
input group "Support & Resistance";
input int FirstCandle = 3; //First Candle for Lookup Area
input int LastCandle = 30; //Last Candle for Lookup Area
//input group "Time Settings"
//
//input group "Exception 1"
//input bool ActiveExc1 = false; //No Trade Exception 1
//input int startDay1 = 1; //Day, Monday = 1, 0 = Off
//input int startHour1 = 0; //Start hour
//input int startMinute1 = 0; //Start Minute
//input int endHour1 = 24; //End hour
//input int endMinute1 = 0; //End Minute
//
//input group "Exception 2"
//input bool ActiveExc2 = false; //No Trade Exception 2
//input int startDay2 = 1; //Start Day, Monday = 1, 0 = Off
//input int startHour2 = 0; //Start hour
//input int startMinute2 = 0; //Start Minute
//input int endHour2 = 24; //End hour
//input int endMinute2 = 0; //End Minute
//
//input group "Exception 3"
//input bool ActiveExc3 = false; //No Trade Exception 2
//input int startDay3 = 1; //Start Day, Monday = 1, 0 = Off
//input int startHour3 = 0; //Start hour
//input int startMinute3 = 0; //Start Minute
//input int endHour3 = 24; //End hour
//input int endMinute3 = 0; //End Minute
input group "Debug Settings"
input bool IN_PrintDebug = true; //Print debug messages
struct STRUCT_PENDING_TRADE
{
ENUM_POSITION_TYPE pos_type;
ulong orderTicket;
// When this is true we need to prevent trading since this order has not been confirmed yet
bool isPending;
// When this is true we can remove the entry from the array
bool isConfirmed;
// Constructor for new/blank array entries
STRUCT_PENDING_TRADE()
{
pos_type = WRONG_VALUE;
orderTicket = 0;
isPending = false;
isConfirmed = false;
}
};
STRUCT_PENDING_TRADE PendingTrades[];
int OnInit()
{
// Init CSymbolInfo variable for easy access
ResetLastError();
if (!symbolInfo.Name(_Symbol))
{
Print(__FILE__, " ", __FUNCTION__, ", ERROR: CSymbolInfo.Name");
return(INIT_FAILED);
}
RefreshRates();
trade.SetExpertMagicNumber(IN_MagicNumber);
return(INIT_SUCCEEDED);
}
void OnTick()
{
double equity = AccountInfoDouble(ACCOUNT_EQUITY); //Guthaben nach Abzug der offenen Positionen
double dynamicLotSize = NormalizeDouble(equity / 100000, 2);
double dynamicTradeVolume = dynamicLotSize * IN_LotMultiplier;
if (PositionsTotal() != 0 && IN_ExitStrat == TrailingSL) TrailingStopLoss();
int countPendingOrders = ArraySize(PendingTrades);
if (countPendingOrders > 0)
{
for (int i = countPendingOrders - 1; i >= 0; i--)
{
// Remove all confirmed orders from the array
if (PendingTrades[i].isPending && PendingTrades[i].isConfirmed)
{
ArrayRemove(PendingTrades, i, 1);
continue;
}
// Open new order
if (!PendingTrades[i].isPending)
{
OpenPosition(i, dynamicTradeVolume);
}
}
return;
}
if (!RefreshRates())
return;
//Here are 3 possible time filters to set when not to trade
// MqlDateTime mdt;
// TimeCurrent(mdt);
//
// int day = mdt.day_of_week;
// int hour = mdt.hour;
// int min = mdt.min;
//
// if(ActiveExc1 && day == startDay1)
// {
// if(hour >= startHour1 && min >= startMinute1 && hour <= endHour1 && min <= endMinute1) return;
// }
// if(ActiveExc2 && day == startDay2)
// {
// if(hour >= startHour2 && min >= startMinute2 && hour <= endHour2 && min <= endMinute2) return;
// }
// if(ActiveExc3 && day == startDay3)
// {
// if(hour >= startHour3 && min >= startMinute3 && hour <= endHour3 && min <= endMinute3) return;
// }
// We only look for a new trigger when we don't already have an open position
if (AnyOpenPositions())
return;
// Also need to check our PendingTrades array if we have recently placed an order that hasn't been confirmed yet
countPendingOrders = ArraySize(PendingTrades);
if (countPendingOrders > 0)
return;
ENUM_TRIGGER trigger = GetTrigger(SupLevel(), ResLevel());
if (trigger == DoNothing)
return;
if (IN_PrintDebug)
Print(__FILE__, " " ,__FUNCTION__, "Trigger: ", trigger);
// Add new order to PendingTrades array
ArrayResize(PendingTrades, countPendingOrders + 1);
switch (trigger)
{
case Buy:
PendingTrades[countPendingOrders].pos_type = POSITION_TYPE_BUY;
break;
case Sell:
PendingTrades[countPendingOrders].pos_type = POSITION_TYPE_SELL;
break;
default:
break;
}
}
ENUM_TRIGGER GetTrigger(double supportLevel, double resistenceLevel)
{
// Now check for a possible trade trigger condition
// TODO
// Is defined by the candle patter that needs the most candles
int startCandle = 1;
int maxCandles = 4;
double candleHigh[];
ArraySetAsSeries(candleHigh, true);
CopyHigh(_Symbol, (ENUM_TIMEFRAMES)IN_Timeframe, startCandle, maxCandles, candleHigh);
double candleLow[];
ArraySetAsSeries(candleLow, true);
CopyLow(_Symbol, (ENUM_TIMEFRAMES)IN_Timeframe, startCandle, maxCandles, candleLow);
double candleOpen[];
ArraySetAsSeries(candleOpen, true);
CopyOpen(_Symbol, (ENUM_TIMEFRAMES)IN_Timeframe, startCandle, maxCandles, candleOpen);
double candleClose[];
ArraySetAsSeries(candleClose, true);
CopyClose(_Symbol, (ENUM_TIMEFRAMES)IN_Timeframe, startCandle, maxCandles, candleClose);
int candleBodySize[];
ArrayResize(candleBodySize, maxCandles);
int wickLengthUp[];
ArrayResize(wickLengthUp, maxCandles);
int wickLengthDown[];
ArrayResize(wickLengthDown, maxCandles);
for (int i = 0; i < maxCandles; i++)
{
if(candleOpen[i] < candleClose[i]) //If open < close, the candle is bullish
{
candleBodySize[i] = (candleClose[i] - candleOpen[i]) /_Point; //Divided by _Point gives the difference in points which is more easy to calculate with
wickLengthUp[i] = (candleHigh[i] - candleClose[i]) / _Point;
wickLengthDown[i] = (candleOpen[i] - candleLow[i]) / _Point;
}
else if (candleOpen[i] == candleClose[i])
{
candleBodySize[i] = 0;
wickLengthUp[i] = (candleHigh[i] - candleClose[i]) / _Point;
wickLengthDown[i] = (candleOpen[i] - candleLow[i]) / _Point;
}
else //Else candle is bearish
{
candleBodySize[i] = (candleOpen[i] - candleClose[i]) / _Point;
wickLengthUp[i] = (candleHigh[i] - candleOpen[i]) / _Point;
wickLengthDown[i] = (candleClose[i] - candleLow[i]) / _Point;
}
}
bool ThreeGreenCandles = false;
bool ThreeRedCandles = false;
if(candleOpen[3] < candleClose[3] && candleOpen[2] < candleClose[2] && candleOpen[1] < candleClose[1]) ThreeGreenCandles = true;
if(candleOpen[3] > candleClose[3] && candleOpen[2] > candleClose[2] && candleOpen[1] > candleClose[1]) ThreeRedCandles = true;
if(ActiveLongWick)
{
switch (EntryStratLWC)
{
case SupportResistance:
{
if (candleBodySize[0] < MaxBodySizeLWC && wickLengthUp[0] > MinWickLenLWC && candleHigh[0] > resistenceLevel)
return Sell;
if (candleBodySize[0] < MaxBodySizeLWC && wickLengthDown[0] > MinWickLenLWC && candleLow[0] < supportLevel)
return Buy;
}
break;
case ThreeLastCandles:
{
if (candleBodySize[0] < MaxBodySizeLWC && wickLengthUp[0] > MinWickLenLWC && ThreeGreenCandles == true)
return Sell;
if (candleBodySize[0] < MaxBodySizeLWC && wickLengthDown[0] > MinWickLenLWC && ThreeRedCandles == true)
return Buy;
}
break;
case Both:
{
if (candleBodySize[0] < MaxBodySizeLWC && wickLengthUp[0] > MinWickLenLWC && (candleHigh[0] > resistenceLevel || ThreeGreenCandles == true))
return Sell;
if (candleBodySize[0] < MaxBodySizeLWC && wickLengthDown[0] > MinWickLenLWC && (candleLow[0] < supportLevel || ThreeRedCandles == true))
return Buy;
}
break;
}
}
if(ActiveInvertedWick)
{
switch (EntryStratILW)
{
case SupportResistance:
{
if (candleBodySize[0] < MaxBodySizeILW && wickLengthDown[0] > MinWickLenILW && candleHigh[0] > resistenceLevel)
return Sell;
if (candleBodySize[0] < MaxBodySizeILW && wickLengthUp[0] > MinWickLenILW && candleLow[0] < supportLevel)
return Buy;
}
break;
case ThreeLastCandles:
if(ActiveInvertedWick)
{
if (candleBodySize[0] < MaxBodySizeILW && wickLengthDown[0] > MinWickLenILW && ThreeGreenCandles == true)
return Sell;
if (candleBodySize[0] < MaxBodySizeILW && wickLengthUp[0] > MinWickLenILW && candleLow[0] < supportLevel && ThreeRedCandles == true)
return Buy;
}
break;
case Both:
if(ActiveInvertedWick)
{
if (candleBodySize[0] < MaxBodySizeILW && wickLengthDown[0] > MinWickLenILW && (candleHigh[0] > resistenceLevel || ThreeGreenCandles == true))
return Sell;
if (candleBodySize[0] < MaxBodySizeILW && wickLengthUp[0] > MinWickLenILW && candleLow[0] < supportLevel && (candleLow[0] < supportLevel || ThreeRedCandles == true))
return Buy;
}
break;
}
}
if(ActiveInsideBar)
{
if (candleBodySize[0] > MinBodySizeINB && candleHigh[0] < candleHigh[1] && candleLow[0] > candleLow[1] && candleHigh[1] > resistenceLevel)
return Sell;
if (candleBodySize[0] > MinBodySizeINB && candleLow[0] > candleLow[1] && candleHigh[0] < candleHigh[1] && candleLow[1] < supportLevel)
return Buy;
}
return DoNothing;
}
//Erster Versuch Support und Resistance Level zu identifizieren
//Man kann den höchsten und niedrigsten Wert aus einem Array ziehen
//Großer Nachteil ist die fixe Größe des Arrays, die müsste variabel sein, bis ein neues Level gefunden wird
//Da fehlt mir aber noch die Idee, wie genau man das umsetzen kann
double SupLevel()
{
double arrCandles[];
ArraySetAsSeries(arrCandles, true);
CopyClose(_Symbol,(ENUM_TIMEFRAMES)IN_Timeframe,FirstCandle,LastCandle,arrCandles);
double CandleLow = arrCandles[ArrayMinimum(arrCandles,0,WHOLE_ARRAY)];
return CandleLow;
}
double ResLevel()
{
double arrCandles[];
ArraySetAsSeries(arrCandles, true);
CopyClose(_Symbol,(ENUM_TIMEFRAMES)IN_Timeframe,FirstCandle,LastCandle,arrCandles);
double CandleHigh = arrCandles[ArrayMaximum(arrCandles,0,WHOLE_ARRAY)];
return CandleHigh;
}
void TrailingStopLoss()
{
for (int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong posTicket = PositionGetTicket(i);
if (PositionSelectByTicket(posTicket) && PositionGetString(POSITION_SYMBOL) == _Symbol)
{
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double posOpenPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double posTP = PositionGetDouble(POSITION_TP);
double posSL = PositionGetDouble(POSITION_SL);
double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
if(posType == POSITION_TYPE_BUY)
{
if(bid > posOpenPrice + IN_TrailingStopLossTrigger * _Point)
{
double NewSL = bid - IN_TrailingStopLossTrigger * _Point;
NewSL = NormalizeDouble(NewSL, _Digits);
if (NewSL > posSL)
{
trade.PositionModify(posTicket, NewSL, posTP);
}
}
}
else if (posType == POSITION_TYPE_SELL)
{
if (ask < posOpenPrice - IN_TrailingStopLossTrigger * _Point)
{
double NewSL = ask + IN_TrailingStopLossTrigger * _Point;
NewSL = NormalizeDouble(NewSL, _Digits);
if (NewSL < posSL || posSL == 0)
{
trade.PositionModify(posTicket, NewSL, posTP);
}
}
}
}
}
}
// Sets the "isConfirmed" flag in our PendingTrades array
void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
ENUM_TRADE_TRANSACTION_TYPE type = trans.type;
if (type == TRADE_TRANSACTION_DEAL_ADD)
{
ResetLastError();
if (HistoryDealSelect(trans.deal))
{
deal.Ticket(trans.deal);
}
else
{
Print(__FILE__, " ", __FUNCTION__, ", ERROR: ", "HistoryDealSelect(", trans.deal, ") error: ", GetLastError());
return;
}
if (deal.Symbol() == symbolInfo.Name() && deal.Magic() == IN_MagicNumber)
{
if (deal.DealType() == DEAL_TYPE_BUY || deal.DealType() == DEAL_TYPE_SELL)
{
int countPendingOrders = ArraySize(PendingTrades);
if (countPendingOrders > 0)
{
for (int i = 0; i < countPendingOrders; i++)
{
if (PendingTrades[i].isPending)
{
if (PendingTrades[i].orderTicket == deal.Order())
{
Print(__FILE__, " ", __FUNCTION__, ", OK: ", " Transaction confirmed");
PendingTrades[i].isConfirmed = true;
break;
}
}
}
}
}
}
}
}
bool RefreshRates()
{
// Refreshes the symbol quotes data
if (!symbolInfo.RefreshRates())
{
Print(__FILE__, " ", __FUNCTION__, ", ERROR: ", "RefreshRates error");
return false;
}
// Sometimes 0 is returned, no idea why yet
if (symbolInfo.Ask() == 0 || symbolInfo.Bid() == 0)
{
Print(__FILE__, " ", __FUNCTION__, ", ERROR: ", "Ask == 0.0 OR Bid == 0.0");
return false;
}
return true;
}
void OpenPosition(int index, double tradeVolume)
{
if (!RefreshRates())
return;
bool success = false;
string debugType = "";
if (PendingTrades[index].pos_type == POSITION_TYPE_BUY)
{
debugType = "Buy";
success = trade.Buy(tradeVolume, symbolInfo.Name(), symbolInfo.Ask(), symbolInfo.Ask() - IN_StopLoss * _Point,symbolInfo.Ask() + IN_TakeProfit * _Point);
}
else if (PendingTrades[index].pos_type == POSITION_TYPE_SELL)
{
debugType = "Sell";
success = trade.Sell(tradeVolume, symbolInfo.Name(), symbolInfo.Bid(), symbolInfo.Bid() + IN_StopLoss * _Point,symbolInfo.Bid() - IN_TakeProfit * _Point);
}
if (success)
{
if (trade.ResultRetcode() == TRADE_RETCODE_DONE)
{
PendingTrades[index].isPending = true;
PendingTrades[index].orderTicket = trade.ResultOrder();
}
else
{
PendingTrades[index].isPending = false;
if (IN_PrintDebug)
Print(__FILE__, " ", __FUNCTION__, ", ERROR: ", debugType, " -> false, Deal Nr: ", trade.ResultDeal(), " Result Retcode: ", trade.ResultRetcode(), ", descr: ", trade.ResultRetcodeDescription());
}
if (IN_PrintDebug)
PrintResultTrade();
}
else
{
PendingTrades[index].isPending = false;
if (IN_PrintDebug)
Print(__FILE__, " ", __FUNCTION__, ", ERROR: ", debugType, " -> false. Result Retcode: ", trade.ResultRetcode(), ", descrt: ", trade.ResultRetcodeDescription());
}
}
void PrintResultTrade()
{
Print(__FILE__, " ", __FUNCTION__, ", Symbol: ", symbolInfo.Name() + ", " +
"Code of request result: " + IntegerToString(trade.ResultRetcode()) + ", " +
"Code of request result as a string: " + trade.ResultRetcodeDescription(),
"trade execution mode: " + symbolInfo.TradeExecutionDescription());
Print("deal ticket: " + IntegerToString(trade.ResultDeal()) + ", " +
"Order ticket: " + IntegerToString(trade.ResultOrder()) + ", " +
"Order retcode external: " + IntegerToString(trade.ResultRetcodeExternal()) + ", " +
"Volume of deal or order: " + DoubleToString(trade.ResultVolume(), 2));
Print("Price, confirmed by broker: " + DoubleToString(trade.ResultPrice(), symbolInfo.Digits()) + ", " +
"Current bid price: " + DoubleToString(symbolInfo.Bid(), symbolInfo.Digits()) + " (the requote): " + DoubleToString(trade.ResultBid(), symbolInfo.Digits()) + ", " +
"Current ask price: " + DoubleToString(symbolInfo.Ask(), symbolInfo.Digits()) + " (the requote): " + DoubleToString(trade.ResultAsk(), symbolInfo.Digits()));
Print("Broker comment: " + trade.ResultComment());
}
bool AnyOpenPositions()
{
for (int i = PositionsTotal() - 1; i >= 0; i--)
{
if (position.SelectByIndex(i))
{
if (position.Symbol() == symbolInfo.Name() && position.Magic() == IN_MagicNumber)
{
return true;
}
}
}
return false;
}