//+------------------------------------------------------------------+
//|                                               TradePreflight.mqh |
//|                                                       Ali Rajput |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Ali Rajput"
#property link      "https://www.mql5.com"
//+------------------------------------------------------------------+
//| Includes                                                         |
//+------------------------------------------------------------------+
#include "PreflightRules.mqh"
#include "PreflightLedger.mqh"
//+------------------------------------------------------------------+
//| CTradePreflight: one gate in front of every trade request        |
//+------------------------------------------------------------------+
class CTradePreflight
  {
private:
   int               m_delay_points;     // room for price movement before the server check
   ulong             m_deviation;        // slippage written into the OrderCheck() request
   bool              m_repair_volume;    // round volume down instead of refusing
   bool              m_repair_tp;        // move a close TP further away
   bool              m_repair_sl;        // move a close SL further away (more risk)
   bool              m_use_order_check;  // finish with an OrderCheck() dry run
   bool              m_check_margin;     // own margin check on hedging accounts
   bool              m_verbose;          // print every refusal and repair
   string            m_detail;           // explanation of the last result
   bool              m_repaired;         // last request was changed to pass
   uint              m_last_retcode;     // OrderCheck() retcode of the last request
   CPreflightLedger  m_ledger;           // counters by result code

   void              Begin(void);
   bool              LoadSpec(const string symbol,SSymbolSpec &s);
   ENUM_PREFLIGHT_CODE CheckPermissions(const string symbol,const int direction);
   bool              IsSessionOpen(const string symbol);
   double            DirectionVolume(const string symbol,const bool is_buy);
   ENUM_PREFLIGHT_CODE CheckVolumeLimit(const SSymbolSpec &s,const bool is_buy,double &volume);
   ENUM_PREFLIGHT_CODE CheckOrderLimit(void);
   ENUM_PREFLIGHT_CODE CheckMargin(const SSymbolSpec &s,const bool is_buy,const double volume,
                                   const double price);
   ENUM_PREFLIGHT_CODE ServerCheck(MqlTradeRequest &request);
   ENUM_PREFLIGHT_CODE Finish(const string action,const string symbol,const ENUM_PREFLIGHT_CODE code);

public:
                     CTradePreflight(void);
   void              SetDelayPoints(const int points);
   void              SetDeviation(const int points);
   void              SetRepair(const bool volume,const bool tp,const bool sl);
   ENUM_PREFLIGHT_CODE CheckMarket(const string symbol,const ENUM_ORDER_TYPE type,
                                   double &volume,double &sl,double &tp);
   ENUM_PREFLIGHT_CODE CheckPending(const string symbol,const ENUM_ORDER_TYPE type,
                                    const double price,double &volume,double &sl,double &tp);
   ENUM_PREFLIGHT_CODE CheckModify(const ulong ticket,double &sl,double &tp);
   ENUM_PREFLIGHT_CODE CheckClose(const ulong ticket);
   ENUM_ORDER_TYPE_FILLING Filling(const string symbol) const;
   //--- turn the OrderCheck() dry run on or off
   void              SetOrderCheck(const bool use) { m_use_order_check=use; }
   //--- turn the own margin check on or off, it never runs on netting or exchange accounts
   void              SetMarginCheck(const bool use) { m_check_margin=use; }
   //--- print refusals and repairs to the journal
   void              SetVerbose(const bool verbose) { m_verbose=verbose; }
   //--- explanation of the last result
   string            LastDetail(void) const { return(m_detail); }
   //--- true when the last request passed only after a repair
   bool              LastRepaired(void) const { return(m_repaired); }
   //--- raw OrderCheck() retcode of the last request, 0 when it passed or did not run
   uint              LastRetcode(void) const { return(m_last_retcode); }
   //--- checks that ended with the given code
   int               Count(const ENUM_PREFLIGHT_CODE code) const { return(m_ledger.Count(code)); }
   //--- print the counters
   void              Report(const string title) const { m_ledger.Report(title); }
  };
//+------------------------------------------------------------------+
//| CTradePreflight constructor                                      |
//+------------------------------------------------------------------+
CTradePreflight::CTradePreflight(void) : m_delay_points(0),
                                         m_deviation(0),
                                         m_repair_volume(true),
                                         m_repair_tp(true),
                                         m_repair_sl(false),
                                         m_use_order_check(true),
                                         m_check_margin(true),
                                         m_verbose(true),
                                         m_detail(""),
                                         m_repaired(false),
                                         m_last_retcode(0)
  {
  }
//+------------------------------------------------------------------+
//| SetDelayPoints: extra distance for SL, TP and pending prices     |
//+------------------------------------------------------------------+
void CTradePreflight::SetDelayPoints(const int points)
  {
   m_delay_points=MathMax(points,0);
  }
//+------------------------------------------------------------------+
//| SetDeviation: slippage written into the OrderCheck() request.    |
//| It is not the delay buffer, set it to what CTrade will send      |
//+------------------------------------------------------------------+
void CTradePreflight::SetDeviation(const int points)
  {
   m_deviation=(ulong)MathMax(points,0);
  }
//+------------------------------------------------------------------+
//| SetRepair: which fixes the gate may apply by itself              |
//+------------------------------------------------------------------+
void CTradePreflight::SetRepair(const bool volume,const bool tp,const bool sl)
  {
   m_repair_volume=volume;
   m_repair_tp=tp;
   m_repair_sl=sl;
  }
//+------------------------------------------------------------------+
//| Begin: clear the state of the previous request                   |
//+------------------------------------------------------------------+
void CTradePreflight::Begin(void)
  {
   m_detail="";
   m_repaired=false;
   m_last_retcode=0;
  }
//+------------------------------------------------------------------+
//| LoadSpec: read the symbol properties and the current tick        |
//+------------------------------------------------------------------+
bool CTradePreflight::LoadSpec(const string symbol,SSymbolSpec &s)
  {
   MqlTick tick;
   if(!SymbolInfoTick(symbol,tick) || tick.bid<=0.0 || tick.ask<=0.0)
     {
      m_detail="no current tick for "+symbol;
      return(false);
     }
   s.name        =symbol;
   s.bid         =tick.bid;
   s.ask         =tick.ask;
   s.point       =SymbolInfoDouble(symbol,SYMBOL_POINT);
   s.tick_size   =SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_SIZE);
   s.digits      =(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
   s.vol_min     =SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN);
   s.vol_max     =SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX);
   s.vol_step    =SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP);
   s.stops_level =(int)SymbolInfoInteger(symbol,SYMBOL_TRADE_STOPS_LEVEL);
   s.freeze_level=(int)SymbolInfoInteger(symbol,SYMBOL_TRADE_FREEZE_LEVEL);
   s.delay_points=m_delay_points;
   if(s.point<=0.0)
     {
      m_detail="SYMBOL_POINT is 0 for "+symbol;
      return(false);
     }
   return(true);
  }
//+------------------------------------------------------------------+
//| CheckPermissions: direction +1 opens a buy, -1 opens a sell,     |
//| 0 modifies or closes                                             |
//+------------------------------------------------------------------+
ENUM_PREFLIGHT_CODE CTradePreflight::CheckPermissions(const string symbol,const int direction)
  {
//--- the tester ignores the AutoTrading button, a live chart does not
   if(!MQLInfoInteger(MQL_TESTER))
     {
      if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
        {
         m_detail="AutoTrading is off in the terminal";
         return(PF_TRADE_DISABLED);
        }
      if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
        {
         m_detail="Allow Algo Trading is off for this program";
         return(PF_TRADE_DISABLED);
        }
     }
   if(!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED))
     {
      m_detail="trading is not allowed on this account";
      return(PF_TRADE_DISABLED);
     }
   if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT))
     {
      m_detail="the server does not allow Expert Advisors on this account";
      return(PF_TRADE_DISABLED);
     }
//--- symbol trade mode
   ENUM_SYMBOL_TRADE_MODE mode=(ENUM_SYMBOL_TRADE_MODE)SymbolInfoInteger(symbol,SYMBOL_TRADE_MODE);
   if(mode==SYMBOL_TRADE_MODE_DISABLED)
     {
      m_detail=symbol+" is disabled for trading";
      return(PF_TRADE_DISABLED);
     }
   if(direction==0)
      return(PF_OK);
   if(mode==SYMBOL_TRADE_MODE_CLOSEONLY ||
      (mode==SYMBOL_TRADE_MODE_LONGONLY && direction<0) ||
      (mode==SYMBOL_TRADE_MODE_SHORTONLY && direction>0))
     {
      m_detail=symbol+" trade mode is "+EnumToString(mode);
      return(PF_DIRECTION_BLOCKED);
     }
   return(PF_OK);
  }
//+------------------------------------------------------------------+
//| IsSessionOpen: is the server time inside a trade session         |
//+------------------------------------------------------------------+
bool CTradePreflight::IsSessionOpen(const string symbol)
  {
   MqlDateTime now;
   TimeToStruct(TimeTradeServer(),now);
   uint seconds=(uint)(now.hour*3600+now.min*60+now.sec);
   datetime from=0,to=0;
//--- today's sessions first, then yesterday's in case one runs past midnight
   for(int back=0; back<=1; back++)
     {
      ENUM_DAY_OF_WEEK day=(ENUM_DAY_OF_WEEK)((now.day_of_week+7-back)%7);
      //--- a day can hold several sessions, walk them until the terminal says there are no more
      for(uint i=0; SymbolInfoSessionTrade(symbol,day,i,from,to); i++)
        {
         if(CPreflightRules::SessionCovers(seconds,(uint)from,(uint)to,back==1))
            return(true);
        }
     }
   m_detail=StringFormat("no trade session for %s at %s",symbol,
                         TimeToString(TimeTradeServer(),TIME_DATE|TIME_SECONDS));
   return(false);
  }
//+------------------------------------------------------------------+
//| DirectionVolume: open and pending volume on one side of a symbol |
//+------------------------------------------------------------------+
double CTradePreflight::DirectionVolume(const string symbol,const bool is_buy)
  {
   double total=0.0;
//--- positions
   for(int i=PositionsTotal()-1; i>=0; i--)
     {
      ulong ticket=PositionGetTicket(i);
      if(ticket==0 || PositionGetString(POSITION_SYMBOL)!=symbol)
         continue;
      bool buy=((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY);
      if(buy==is_buy)
         total+=PositionGetDouble(POSITION_VOLUME);
     }
//--- pending orders
   for(int i=OrdersTotal()-1; i>=0; i--)
     {
      ulong ticket=OrderGetTicket(i);
      if(ticket==0 || OrderGetString(ORDER_SYMBOL)!=symbol)
         continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
      bool buy=(type==ORDER_TYPE_BUY_LIMIT || type==ORDER_TYPE_BUY_STOP || type==ORDER_TYPE_BUY_STOP_LIMIT);
      if(buy==is_buy)
         total+=OrderGetDouble(ORDER_VOLUME_CURRENT);
     }
   return(total);
  }
//+------------------------------------------------------------------+
//| CheckVolumeLimit: SYMBOL_VOLUME_LIMIT, where 0 means no limit    |
//+------------------------------------------------------------------+
ENUM_PREFLIGHT_CODE CTradePreflight::CheckVolumeLimit(const SSymbolSpec &s,const bool is_buy,double &volume)
  {
   double limit=SymbolInfoDouble(s.name,SYMBOL_VOLUME_LIMIT);
   if(limit<=0.0)
      return(PF_OK);
   double room=limit-DirectionVolume(s.name,is_buy);
   if(volume<=room+1e-8)
      return(PF_OK);
   if(!m_repair_volume)
     {
      m_detail=StringFormat("volume %.2f exceeds the %.2f left under SYMBOL_VOLUME_LIMIT",volume,room);
      return(PF_VOLUME_LIMIT);
     }
//--- shrink to the room that is left, if that is still a tradable volume
   double reduced=room;
   bool changed=false;
   string note="";
   if(room<=0.0 || CPreflightRules::CheckVolume(s,reduced,true,changed,note)!=PF_OK)
     {
      m_detail=StringFormat("only %.2f left under SYMBOL_VOLUME_LIMIT",MathMax(room,0.0));
      return(PF_VOLUME_LIMIT);
     }
   m_detail+=StringFormat(" volume cut to %.2f by SYMBOL_VOLUME_LIMIT",reduced);
   volume=reduced;
   m_repaired=true;
   return(PF_OK);
  }
//+------------------------------------------------------------------+
//| CheckOrderLimit: ACCOUNT_LIMIT_ORDERS, where 0 means no limit    |
//+------------------------------------------------------------------+
ENUM_PREFLIGHT_CODE CTradePreflight::CheckOrderLimit(void)
  {
   int limit=(int)AccountInfoInteger(ACCOUNT_LIMIT_ORDERS);
   if(limit>0 && OrdersTotal()>=limit)
     {
      m_detail=StringFormat("%d pending orders already, ACCOUNT_LIMIT_ORDERS is %d",OrdersTotal(),limit);
      return(PF_ORDER_LIMIT);
     }
   return(PF_OK);
  }
//+------------------------------------------------------------------+
//| CheckMargin: margin for the request against free margin          |
//+------------------------------------------------------------------+
ENUM_PREFLIGHT_CODE CTradePreflight::CheckMargin(const SSymbolSpec &s,const bool is_buy,const double volume,
                                                 const double price)
  {
//--- netting and exchange margin can offset open positions, OrderCheck() decides there
   if(!m_check_margin ||
      (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
      return(PF_OK);
   double margin=0.0;
   ENUM_ORDER_TYPE type=is_buy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
   if(!OrderCalcMargin(type,s.name,volume,price,margin))
     {
      m_detail=StringFormat("OrderCalcMargin failed, error %d",GetLastError());
      return(PF_BAD_REQUEST);
     }
   double free_margin=AccountInfoDouble(ACCOUNT_MARGIN_FREE);
   if(margin>free_margin)
     {
      m_detail=StringFormat("%.2f lots need %.2f margin, free margin is %.2f",volume,margin,free_margin);
      return(PF_NO_MONEY);
     }
   return(PF_OK);
  }
//+------------------------------------------------------------------+
//| ServerCheck: OrderCheck() dry run, judged by its retcode         |
//+------------------------------------------------------------------+
ENUM_PREFLIGHT_CODE CTradePreflight::ServerCheck(MqlTradeRequest &request)
  {
   if(!m_use_order_check)
      return(PF_OK);
   MqlTradeCheckResult result;
   ZeroMemory(result);
   bool checked=OrderCheck(request,result);
   m_last_retcode=result.retcode;
//--- true only means the structure passed a basic check, the retcode is the verdict
   if(checked && result.retcode==0)
      return(PF_OK);
   m_detail=StringFormat("OrderCheck returned %s, retcode %u %s",checked ? "true" : "false",
                         result.retcode,result.comment);
//--- map only retcodes with one meaning, LastRetcode() keeps the raw value for the rest
   switch(result.retcode)
     {
      case TRADE_RETCODE_NO_MONEY:       return(PF_NO_MONEY);
      case TRADE_RETCODE_INVALID_VOLUME: return(PF_VOLUME_INVALID);
      case TRADE_RETCODE_MARKET_CLOSED:  return(PF_MARKET_CLOSED);
      case TRADE_RETCODE_TRADE_DISABLED: return(PF_TRADE_DISABLED);
      case TRADE_RETCODE_LIMIT_ORDERS:   return(PF_ORDER_LIMIT);
      case TRADE_RETCODE_LIMIT_VOLUME:   return(PF_VOLUME_LIMIT);
      case TRADE_RETCODE_FROZEN:         return(PF_FROZEN);
      default:                           break;
     }
   return(PF_SERVER_CHECK);
  }
//+------------------------------------------------------------------+
//| Finish: record the result and print it when asked to             |
//+------------------------------------------------------------------+
ENUM_PREFLIGHT_CODE CTradePreflight::Finish(const string action,const string symbol,
                                            const ENUM_PREFLIGHT_CODE code)
  {
   m_ledger.Add(code,m_repaired);
   if(m_verbose && (code!=PF_OK || m_repaired))
      PrintFormat("Preflight %s %s: %s. %s",action,symbol,PreflightCodeText(code),m_detail);
   return(code);
  }
//+------------------------------------------------------------------+
//| Filling: first filling mode the symbol supports                  |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE_FILLING CTradePreflight::Filling(const string symbol) const
  {
   long modes=SymbolInfoInteger(symbol,SYMBOL_FILLING_MODE);
   if((modes&SYMBOL_FILLING_FOK)==SYMBOL_FILLING_FOK)
      return(ORDER_FILLING_FOK);
   if((modes&SYMBOL_FILLING_IOC)==SYMBOL_FILLING_IOC)
      return(ORDER_FILLING_IOC);
   return(ORDER_FILLING_RETURN);
  }
//+------------------------------------------------------------------+
//| CheckMarket: a market buy or sell, may change volume, sl and tp  |
//+------------------------------------------------------------------+
ENUM_PREFLIGHT_CODE CTradePreflight::CheckMarket(const string symbol,const ENUM_ORDER_TYPE type,
                                                 double &volume,double &sl,double &tp)
  {
   Begin();
   if(type!=ORDER_TYPE_BUY && type!=ORDER_TYPE_SELL)
     {
      m_detail="CheckMarket takes ORDER_TYPE_BUY or ORDER_TYPE_SELL";
      return(Finish("market",symbol,PF_BAD_REQUEST));
     }
   SSymbolSpec s;
   if(!LoadSpec(symbol,s))
      return(Finish("market",symbol,PF_BAD_REQUEST));
   bool is_buy=(type==ORDER_TYPE_BUY);
//--- permissions and session
   ENUM_PREFLIGHT_CODE code=CheckPermissions(symbol,is_buy ? 1 : -1);
   if(code!=PF_OK)
      return(Finish("market",symbol,code));
   if(!IsSessionOpen(symbol))
      return(Finish("market",symbol,PF_MARKET_CLOSED));
//--- volume
   bool changed=false;
   code=CPreflightRules::CheckVolume(s,volume,m_repair_volume,changed,m_detail);
   if(changed)
      m_repaired=true;
   if(code==PF_OK)
      code=CheckVolumeLimit(s,is_buy,volume);
   if(code!=PF_OK)
      return(Finish("market",symbol,code));
//--- stops
   changed=false;
   code=CPreflightRules::CheckStops(s,type,0.0,sl,tp,m_repair_sl,m_repair_tp,changed,m_detail);
   if(changed)
      m_repaired=true;
   if(code!=PF_OK)
      return(Finish("market",symbol,code));
//--- money
   double price=is_buy ? s.ask : s.bid;
   code=CheckMargin(s,is_buy,volume,price);
   if(code!=PF_OK)
      return(Finish("market",symbol,code));
//--- the server's own view of the final request
   MqlTradeRequest request;
   ZeroMemory(request);
   request.action      =TRADE_ACTION_DEAL;
   request.symbol      =symbol;
   request.volume      =volume;
   request.type        =type;
   request.price       =price;
   request.sl          =sl;
   request.tp          =tp;
   request.deviation   =m_deviation;
   request.type_filling=Filling(symbol);
   return(Finish("market",symbol,ServerCheck(request)));
  }
//+------------------------------------------------------------------+
//| CheckPending: a limit or stop order, may change volume, sl, tp   |
//+------------------------------------------------------------------+
ENUM_PREFLIGHT_CODE CTradePreflight::CheckPending(const string symbol,const ENUM_ORDER_TYPE type,
                                                  const double price,double &volume,double &sl,double &tp)
  {
   Begin();
   SSymbolSpec s;
   if(!LoadSpec(symbol,s))
      return(Finish("pending",symbol,PF_BAD_REQUEST));
   bool is_buy=(type==ORDER_TYPE_BUY_LIMIT || type==ORDER_TYPE_BUY_STOP);
//--- permissions, session and the account's order limit
   ENUM_PREFLIGHT_CODE code=CheckPermissions(symbol,is_buy ? 1 : -1);
   if(code!=PF_OK)
      return(Finish("pending",symbol,code));
   if(!IsSessionOpen(symbol))
      return(Finish("pending",symbol,PF_MARKET_CLOSED));
   code=CheckOrderLimit();
   if(code!=PF_OK)
      return(Finish("pending",symbol,code));
//--- open price first: the SL and TP are measured from it
   double open_price=price;
   code=CPreflightRules::CheckPendingPrice(s,type,open_price,m_detail);
   if(code!=PF_OK)
      return(Finish("pending",symbol,code));
//--- volume
   bool changed=false;
   code=CPreflightRules::CheckVolume(s,volume,m_repair_volume,changed,m_detail);
   if(changed)
      m_repaired=true;
   if(code==PF_OK)
      code=CheckVolumeLimit(s,is_buy,volume);
   if(code!=PF_OK)
      return(Finish("pending",symbol,code));
//--- stops
   changed=false;
   code=CPreflightRules::CheckStops(s,type,open_price,sl,tp,m_repair_sl,m_repair_tp,changed,m_detail);
   if(changed)
      m_repaired=true;
   if(code!=PF_OK)
      return(Finish("pending",symbol,code));
//--- money, a pending order can need margin too
   code=CheckMargin(s,is_buy,volume,open_price);
   if(code!=PF_OK)
      return(Finish("pending",symbol,code));
//--- the server's own view of the final request
   MqlTradeRequest request;
   ZeroMemory(request);
   request.action      =TRADE_ACTION_PENDING;
   request.symbol      =symbol;
   request.volume      =volume;
   request.type        =type;
   request.price       =open_price;
   request.sl          =sl;
   request.tp          =tp;
   request.type_time   =ORDER_TIME_GTC;
   request.type_filling=Filling(symbol);
   return(Finish("pending",symbol,ServerCheck(request)));
  }
//+------------------------------------------------------------------+
//| CheckModify: new SL and TP for an open position                  |
//+------------------------------------------------------------------+
ENUM_PREFLIGHT_CODE CTradePreflight::CheckModify(const ulong ticket,double &sl,double &tp)
  {
   Begin();
   if(!PositionSelectByTicket(ticket))
     {
      m_detail=StringFormat("position #%I64u not found",ticket);
      return(Finish("modify","",PF_NOT_FOUND));
     }
   string symbol=PositionGetString(POSITION_SYMBOL);
   bool   is_buy=((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY);
   double cur_sl=PositionGetDouble(POSITION_SL);
   double cur_tp=PositionGetDouble(POSITION_TP);
   SSymbolSpec s;
   if(!LoadSpec(symbol,s))
      return(Finish("modify",symbol,PF_BAD_REQUEST));
//--- permissions and session
   ENUM_PREFLIGHT_CODE code=CheckPermissions(symbol,0);
   if(code!=PF_OK)
      return(Finish("modify",symbol,code));
   if(!IsSessionOpen(symbol))
      return(Finish("modify",symbol,PF_MARKET_CLOSED));
//--- a request that changes nothing is refused by the server, so skip it here
   if(CPreflightRules::SameLevel(s,sl,cur_sl) && CPreflightRules::SameLevel(s,tp,cur_tp))
     {
      m_detail=StringFormat("position #%I64u already has these levels",ticket);
      return(Finish("modify",symbol,PF_NO_CHANGE));
     }
//--- the current levels decide whether the position may be touched at all
   if(CPreflightRules::IsFrozen(s,is_buy,cur_sl,cur_tp))
     {
      m_detail=StringFormat("position #%I64u has SL or TP within %d points of the price",
                            ticket,s.freeze_level);
      return(Finish("modify",symbol,PF_FROZEN));
     }
//--- the new levels must respect the stops level
   bool changed=false;
   code=CPreflightRules::CheckStops(s,is_buy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL,0.0,sl,tp,
                                    m_repair_sl,m_repair_tp,changed,m_detail);
   if(changed)
      m_repaired=true;
   if(code!=PF_OK)
      return(Finish("modify",symbol,code));
//--- a repair can land exactly on the old levels
   if(CPreflightRules::SameLevel(s,sl,cur_sl) && CPreflightRules::SameLevel(s,tp,cur_tp))
      return(Finish("modify",symbol,PF_NO_CHANGE));
//--- the server's own view of the final request
   MqlTradeRequest request;
   ZeroMemory(request);
   request.action  =TRADE_ACTION_SLTP;
   request.position=ticket;
   request.symbol  =symbol;
   request.sl      =sl;
   request.tp      =tp;
   return(Finish("modify",symbol,ServerCheck(request)));
  }
//+------------------------------------------------------------------+
//| CheckClose: can this position be closed right now                |
//+------------------------------------------------------------------+
ENUM_PREFLIGHT_CODE CTradePreflight::CheckClose(const ulong ticket)
  {
   Begin();
   if(!PositionSelectByTicket(ticket))
     {
      m_detail=StringFormat("position #%I64u not found",ticket);
      return(Finish("close","",PF_NOT_FOUND));
     }
   string symbol=PositionGetString(POSITION_SYMBOL);
   bool   is_buy=((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY);
   double volume=PositionGetDouble(POSITION_VOLUME);
   double cur_sl=PositionGetDouble(POSITION_SL);
   double cur_tp=PositionGetDouble(POSITION_TP);
   SSymbolSpec s;
   if(!LoadSpec(symbol,s))
      return(Finish("close",symbol,PF_BAD_REQUEST));
//--- permissions and session
   ENUM_PREFLIGHT_CODE code=CheckPermissions(symbol,0);
   if(code!=PF_OK)
      return(Finish("close",symbol,code));
   if(!IsSessionOpen(symbol))
      return(Finish("close",symbol,PF_MARKET_CLOSED));
//--- inside the freeze level the server will not accept a close either
   if(CPreflightRules::IsFrozen(s,is_buy,cur_sl,cur_tp))
     {
      m_detail=StringFormat("position #%I64u has SL or TP within %d points of the price",
                            ticket,s.freeze_level);
      return(Finish("close",symbol,PF_FROZEN));
     }
//--- the server's own view of the closing deal
   MqlTradeRequest request;
   ZeroMemory(request);
   request.action      =TRADE_ACTION_DEAL;
   request.position    =ticket;
   request.symbol      =symbol;
   request.volume      =volume;
   request.type        =is_buy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
   request.price       =is_buy ? s.bid : s.ask;
   request.deviation   =m_deviation;
   request.type_filling=Filling(symbol);
   return(Finish("close",symbol,ServerCheck(request)));
  }
//+------------------------------------------------------------------+
