2026-07-28 09:08:36 -05:00
//+------------------------------------------------------------------+
//| Partials.mqh |
//| Copyright 2025, Niquel Mendoza. |
//| https://www.mql5.com/es/users/nique_372/news |
//+------------------------------------------------------------------+
# property copyright " Copyright 2025, Niquel Mendoza. "
# property link " https://www.mql5.com/es/users/nique_372/news "
# property strict
# ifndef MQLARTICLES_POSMGMT_PARTIALS_MQH
# define MQLARTICLES_POSMGMT_PARTIALS_MQH
//+------------------------------------------------------------------+
//| Include |
//+------------------------------------------------------------------+
# include "..\\RM\\RiskManagement.mqh"
//+------------------------------------------------------------------+
//| Structures |
//+------------------------------------------------------------------+
2026-08-04 17:02:36 -05:00
struct pack ( 8 ) PartialTakeProfit
2026-07-28 09:08:36 -05:00
{
double partial_price ;
double tp_level_percentage ;
double volume_percentage_to_close ;
inline void Reset ( )
{
ZeroMemory ( this ) ;
}
} ;
struct TrackedPosition
{
PartialTakeProfit tp_levels [ ] ;
ulong ticket ;
ENUM_POSITION_TYPE type ;
int current_partial_index ;
} ;
//+------------------------------------------------------------------+
//| Defines |
//+------------------------------------------------------------------+
2026-08-04 17:02:36 -05:00
# define CPARCIAL_RESERVE_ARR 8
# define CPARCIAL_RESERVE_TO_DELETE 8
2026-07-28 09:08:36 -05:00
# define PARTIAL_NO_APPLIED " 0 "
//+------------------------------------------------------------------+
//| Class that implements partial closures |
//+------------------------------------------------------------------+
class CPartials : public CAccountGestor
{
private :
//--- General variables
ulong m_magic_number ; // Magic number used to apply partials only to certain operations
CTrade * m_trade_executor ; // CTrade type instance to apply partials
bool m_disable_partials_flag ; // Boolean that indicates if partials can be executed
bool m_auto ;
//--- Symbol variables
string m_trading_symbol ; // Symbol from which minimum volume and volume step data will be obtained
double m_volume_step ; // Volume step
double m_minimum_volume ; // Minimum volume
int m_price_digits ; // Symbol digits
//--- Variables for partials
int m_max_partial_levels ; // Number of partials to take from each operation
2026-08-04 17:02:36 -05:00
2026-07-28 09:08:36 -05:00
TrackedPosition m_tracked_positions [ ] ; // Array of operations to which partials will be applied
2026-08-04 17:02:36 -05:00
int m_tracked_positions_s ;
int m_tracked_positions_r ;
2026-07-28 09:08:36 -05:00
int m_indices_to_remove [ ] ; // Indices to remove from the m_tracked_positions array
2026-08-04 17:02:36 -05:00
int m_indices_to_remove_s ;
int m_indices_to_remove_r ;
2026-07-28 09:08:36 -05:00
//--- Arrays to take, both arrays must be the same size.
double m_volume_percentages_to_close [ ] ;
double m_tp_level_percentages [ ] ;
//--- Temporary hashmap
CHashMap < double , double > m_temporary_hashmap ;
public :
CPartials ( ) ;
~ CPartials ( ) ;
//--- Initialize function
bool Init ( ulong magic_number_ , string symbol_ , string volume_percentages_string , string tp_percentages_string ) ;
//--- Main function for position review
void CheckTrackedPositions ( ) ;
//--- Function that will be automatically invoked by account status each time a position is opened or closed
void OnOpenPosition ( const ROnOpenPosition & pos ) override final ;
void OnClosePosition ( const ROnClosePosition & pos , const int global_pos_index ) override final ;
//--- Function to add a position to the internal array manually (this is automatically invoked in OnOpen..... for all positions
// opened with the magic number and with a valid tp).
2026-08-04 17:02:36 -05:00
bool AddPositionToTrack ( ulong position_ticket , double position_tp , double entry_price ,
ENUM_POSITION_TYPE position_type , const string symbol ) ;
2026-07-28 09:08:36 -05:00
bool AddPositionToTrack ( ulong position_ticket ) ;
//--- Function that returns true if partials are allowed to be used
inline bool IsPartialsEnabled ( ) const { return m_disable_partials_flag = = false ; }
//---
// Auto mode
inline bool AutoMode ( ) const { return m_auto ; }
void AutoMode ( bool auto ) { m_auto = auto ; }
// Symbol
inline string Symbol ( ) const { return m_trading_symbol ; }
// Magic
inline ulong MagicNumber ( ) const { return m_magic_number ; }
} ;
//+------------------------------------------------------------------+
//| Constructor and Destructor |
//+------------------------------------------------------------------+
CPartials : : CPartials ( )
:
m_magic_number ( NOT_MAGIC_NUMBER ) , m_disable_partials_flag ( true ) , m_trading_symbol ( NULL ) , m_volume_step ( 0.00 ) ,
2026-08-04 17:02:36 -05:00
m_minimum_volume ( 0.00 ) , m_price_digits ( 0 ) , m_max_partial_levels ( 0 ) , m_auto ( true ) , m_tracked_positions_s ( 0 ) ,
m_tracked_positions_r ( CPARCIAL_RESERVE_ARR ) , m_indices_to_remove_s ( 0 ) , m_indices_to_remove_r ( CPARCIAL_RESERVE_TO_DELETE )
2026-07-28 09:08:36 -05:00
{
2026-08-04 17:02:36 -05:00
//---
2026-07-28 09:08:36 -05:00
m_trade_executor = new CTrade ( ) ;
account_status . RegisterEvents ( & this , ACCOUNT_STATUS_REG_FLAG_ON_OPEN_POSITION | ACCOUNT_STATUS_REG_FLAG_ON_CLOSE_POSITION ) ;
2026-08-04 17:02:36 -05:00
//---
: : ArrayResize ( m_tracked_positions , CPARCIAL_RESERVE_ARR , CPARCIAL_RESERVE_ARR ) ;
: : ArrayResize ( m_indices_to_remove , CPARCIAL_RESERVE_TO_DELETE , CPARCIAL_RESERVE_TO_DELETE ) ;
2026-07-28 09:08:36 -05:00
}
//+------------------------------------------------------------------+
CPartials : : ~ CPartials ( )
{
if ( account_status . IsActive ( ) )
account_status . UnregisterEvents ( & this , ACCOUNT_STATUS_REG_FLAG_ON_OPEN_POSITION | ACCOUNT_STATUS_REG_FLAG_ON_CLOSE_POSITION ) ;
if ( : : CheckPointer ( m_trade_executor ) = = POINTER_DYNAMIC )
delete m_trade_executor ;
}
//+------------------------------------------------------------------+
//| Initialization function |
//+------------------------------------------------------------------+
bool CPartials ::Init ( ulong magic_number_ , string symbol_ , string volume_percentages_string , string tp_percentages_string )
{
//--- Validate if the strings are valid
if ( volume_percentages_string = = PARTIAL_NO_APPLIED | | tp_percentages_string = = PARTIAL_NO_APPLIED ) // "0" as an invalid value
{
m_disable_partials_flag = true ;
return false ;
}
//--- Set the variables
m_disable_partials_flag = false ;
m_magic_number = magic_number_ ;
m_trading_symbol = symbol_ ;
m_volume_step = : : SymbolInfoDouble ( m_trading_symbol , SYMBOL_VOLUME_STEP ) ;
m_minimum_volume = : : SymbolInfoDouble ( m_trading_symbol , SYMBOL_VOLUME_MIN ) ;
m_price_digits = ( int ) : : SymbolInfoInteger ( m_trading_symbol , SYMBOL_DIGITS ) ;
m_trade_executor . SetExpertMagicNumber ( m_magic_number ) ;
//--- Convert strings to double arrays
StrTo : : CstArray ( m_volume_percentages_to_close , volume_percentages_string , ' , ' ) ;
StrTo : : CstArray ( m_tp_level_percentages , tp_percentages_string , ' , ' ) ;
//--- 2nd validation, we validate that the array size is valid
if ( m_volume_percentages_to_close .Size ( ) < 1 | | m_tp_level_percentages .Size ( ) < 1 )
{
LogCriticalError ( : : StringFormat ( " The size of the partial arrays to take %u or percentages to take %u are invalid " , m_volume_percentages_to_close .Size ( ) , m_tp_level_percentages .Size ( ) ) , FUNCION_ACTUAL ) ;
m_disable_partials_flag = true ;
return false ;
}
//--- 3rd validation, we validate that both arrays are the same size
if ( m_volume_percentages_to_close .Size ( ) ! = m_tp_level_percentages .Size ( ) )
{
LogCriticalError ( : : StringFormat ( " The size of the partial arrays to take %u and percentages to take %u are not equal " , m_volume_percentages_to_close .Size ( ) , m_tp_level_percentages .Size ( ) ) , FUNCION_ACTUAL ) ;
m_disable_partials_flag = true ;
return false ;
}
//--- Initial log only if "info" type log is enabled
if ( IsInfoLogEnabled ( ) )
{
FastLog ( FUNCION_ACTUAL , INFO_TEXT , " Arrays before revision " ) ;
PrintArrayAsTable ( m_tp_level_percentages , " Percentage of position where partials will be taken " , " percentages simplified " ) ;
PrintArrayAsTable ( m_volume_percentages_to_close , " Percentage to take " , " percentages simplified " ) ;
}
//--- Declaration of indices to remove and hashmap cleanup
m_temporary_hashmap . Clear ( ) ;
int indices_to_remove_temp [ ] ;
//--- Iteration and array cleanup
for ( int i = 0 ; i < : : ArraySize ( m_tp_level_percentages ) ; i + + )
{
if ( m_tp_level_percentages [ i ] < = 0.00 )
{
LogWarning ( : : StringFormat ( " The percentage where partials will be taken %f with index %d is invalid, therefore it will not be considered in partials " , m_tp_level_percentages [ i ] , i ) , FUNCION_ACTUAL ) ;
AddArrayNoVerification ( indices_to_remove_temp , i , 0 ) ;
continue ;
}
if ( m_volume_percentages_to_close [ i ] < = 0.00 )
{
LogWarning ( : : StringFormat ( " The percentage to take %f with index %d is invalid, therefore it will not be considered in partials " , m_volume_percentages_to_close [ i ] , i ) , FUNCION_ACTUAL ) ;
AddArrayNoVerification ( indices_to_remove_temp , i , 0 ) ;
continue ;
}
if ( m_temporary_hashmap . ContainsKey ( m_tp_level_percentages [ i ] ) )
AddArrayNoVerification ( indices_to_remove_temp , i , 0 ) ;
else
m_temporary_hashmap . Add ( m_tp_level_percentages [ i ] , m_volume_percentages_to_close [ i ] ) ;
}
//--- Sort and removal
RemoveMultipleIndexes ( m_tp_level_percentages , indices_to_remove_temp , 0 ) ;
: : ArraySort ( m_tp_level_percentages ) ;
: : ArrayResize ( m_volume_percentages_to_close , : : ArraySize ( m_tp_level_percentages ) ) ;
//--- Final iteration
for ( int i = 0 ; i < : : ArraySize ( m_tp_level_percentages ) ; i + + )
{
double val ;
m_temporary_hashmap . TryGetValue ( m_tp_level_percentages [ i ] , val ) ;
m_volume_percentages_to_close [ i ] = val / 100.0 ;
m_tp_level_percentages [ i ] / = 100.0 ;
}
//--- Final log
if ( IsInfoLogEnabled ( ) )
{
FastLog ( FUNCION_ACTUAL , INFO_TEXT , " Arrays after revision " ) ;
PrintArrayAsTable ( m_tp_level_percentages , " Percentage of position where partials will be taken " , " percentages simplified " ) ;
PrintArrayAsTable ( m_volume_percentages_to_close , " Percentage to take " , " percentages simplified " ) ;
}
m_max_partial_levels = : : ArraySize ( m_volume_percentages_to_close ) ;
return true ;
}
//+--------------------------------------------------------------------+
//| Function that runs every time a position is opened or closed |
//+--------------------------------------------------------------------+
void CPartials : : OnOpenPosition ( const ROnOpenPosition & pos )
{
if ( m_disable_partials_flag )
return ;
if ( m_auto & & ( pos . position . magic = = m_magic_number | | m_magic_number = = NOT_MAGIC_NUMBER ) )
{
LogInfo ( : : StringFormat ( " A trade has just been opened with ticket %I64u " , pos . position . ticket ) , FUNCION_ACTUAL ) ;
2026-08-04 17:02:36 -05:00
AddPositionToTrack ( pos . position . ticket , pos . position . tp , pos . position . open_price , pos . position . type ,
account_status . LastTransctionSymbol ( ) ) ;
2026-07-28 09:08:36 -05:00
}
}
//+------------------------------------------------------------------+
void CPartials : : OnClosePosition ( const ROnClosePosition & pos , const int global_pos_index )
{
if ( pos . deal_entry_type = = DEAL_ENTRY_OUT )
{
// Once the position is closed we verify if that ticket is in the internal array, if so the function
// will remove it.
2026-08-04 17:02:36 -05:00
const ulong p = pos . position . ticket ;
int index = -1 ;
for ( int i = 0 ; i < m_tracked_positions_s ; i + + )
{
if ( m_tracked_positions [ i ] . ticket = = p )
{
index = i ;
break ;
}
}
2026-07-28 09:08:36 -05:00
2026-08-04 17:02:36 -05:00
//---
if ( index ! = -1 )
{
m_tracked_positions_s - - ;
if ( m_tracked_positions_s ! = index )
{
m_tracked_positions [ index ] = m_tracked_positions [ m_tracked_positions_s ] ;
}
}
}
2026-07-28 09:08:36 -05:00
}
//+------------------------------------------------------------------+
//| Adds a position with a set of variables to the internal array |
//| Inputs: |
//| - position_ticket: position ticket. |
//| - position_tp: position takeprofit. |
//| - entry_price: position entry price. |
//| - position_type: position type (buy or sell) |
//| - symbol: position symbol |
//| |
//| Outputs: |
//| - If successful, it returns true; otherwise, it returns false. |
//| |
//| Notes: |
//| - The mentioned properties can be consulted with the native |
//| function: PositionGetDouble(...); |
//| - The position must have a valid tp. |
//| - The function does not check if the ticket is valid, so if |
//| you manually introduce an operation, its ticket must be valid. |
//+------------------------------------------------------------------+
2026-08-04 17:02:36 -05:00
bool CPartials : : AddPositionToTrack ( ulong position_ticket , double position_tp , double entry_price ,
ENUM_POSITION_TYPE position_type , const string symbol )
2026-07-28 09:08:36 -05:00
{
if ( m_disable_partials_flag )
return false ;
//---
if ( symbol ! = m_trading_symbol )
{
LogError ( " Invalid symbol " , FUNCION_ACTUAL ) ;
return false ; // Invalid symbol
}
//--- Initial check, we verify that the tp is valid
if ( position_tp < = 0.00000000000001 )
{
LogError ( : : StringFormat ( " The take profit with a value of %f from position %I64u is less than or equal to 0 " , position_tp , position_ticket ) , FUNCION_ACTUAL ) ;
return false ;
}
//---
2026-08-04 17:02:36 -05:00
if ( m_tracked_positions_s > = m_tracked_positions_r )
{
m_tracked_positions_r < < = 1 ;
ArrayResize ( m_tracked_positions , m_tracked_positions_r , m_tracked_positions_r ) ;
}
2026-07-28 09:08:36 -05:00
2026-08-04 17:02:36 -05:00
//---
m_tracked_positions [ m_tracked_positions_s ] . ticket = position_ticket ;
m_tracked_positions [ m_tracked_positions_s ] . type = position_type ;
m_tracked_positions [ m_tracked_positions_s ] . current_partial_index = 0 ;
: : ArrayResize ( m_tracked_positions [ m_tracked_positions_s ] . tp_levels , : : ArraySize ( m_volume_percentages_to_close ) ) ;
//---
for ( int i = 0 ; i < : : ArraySize ( m_tracked_positions [ m_tracked_positions_s ] . tp_levels ) ; i + + )
2026-07-28 09:08:36 -05:00
{
2026-08-04 17:02:36 -05:00
m_tracked_positions [ m_tracked_positions_s ] . tp_levels [ i ] . Reset ( ) ;
m_tracked_positions [ m_tracked_positions_s ] . tp_levels [ i ] . tp_level_percentage = m_tp_level_percentages [ i ] ;
m_tracked_positions [ m_tracked_positions_s ] . tp_levels [ i ] . volume_percentage_to_close = m_volume_percentages_to_close [ i ] ;
2026-07-28 09:08:36 -05:00
if ( position_type = = POSITION_TYPE_BUY )
{
2026-08-04 17:02:36 -05:00
m_tracked_positions [ m_tracked_positions_s ] . tp_levels [ i ] . partial_price = entry_price + ( ( position_tp - entry_price ) * m_tp_level_percentages [ i ] ) ;
2026-07-28 09:08:36 -05:00
}
else
{
2026-08-04 17:02:36 -05:00
m_tracked_positions [ m_tracked_positions_s ] . tp_levels [ i ] . partial_price = entry_price - ( ( entry_price - position_tp ) * m_tp_level_percentages [ i ] ) ;
2026-07-28 09:08:36 -05:00
}
}
//--- Log about the "tps" where partials will be taken
if ( IsCautionLogEnabled ( ) )
{
FastLog ( " TPS: " , CAUTION_TEXT , FUNCION_ACTUAL ) ;
2026-08-04 17:02:36 -05:00
: : ArrayPrint ( m_tracked_positions [ m_tracked_positions_s ] . tp_levels , m_price_digits , " | " ) ;
2026-07-28 09:08:36 -05:00
}
2026-08-04 17:02:36 -05:00
//---
m_tracked_positions_s + + ;
//--- Add to internal arrays
2026-07-28 09:08:36 -05:00
return true ;
}
//+------------------------------------------------------------------+
bool CPartials : : AddPositionToTrack ( ulong position_ticket )
{
if ( ! : : PositionSelectByTicket ( position_ticket ) )
return false ;
//---
return AddPositionToTrack ( position_ticket , PositionGetDouble ( POSITION_TP ) , PositionGetDouble ( POSITION_PRICE_OPEN ) ,
ENUM_POSITION_TYPE ( PositionGetInteger ( POSITION_TYPE ) ) , PositionGetString ( POSITION_SYMBOL ) ) ;
}
//+------------------------------------------------------------------+
//| Function that will iterate over the m_partials array |
//| and if necessary will partially close the operation |
//+------------------------------------------------------------------+
void CPartials : : CheckTrackedPositions ( void )
{
//--- Initial check to verify not to execute the function if partials are not allowed
if ( m_disable_partials_flag )
return ;
//--- If the internal array m_partials is empty we do an early exit.
2026-08-04 17:02:36 -05:00
if ( m_tracked_positions_s < 1 )
2026-07-28 09:08:36 -05:00
return ;
//---
const double ask = SymbolInfoDouble ( m_trading_symbol , SYMBOL_ASK ) ;
const double bid = SymbolInfoDouble ( m_trading_symbol , SYMBOL_BID ) ;
//---
2026-08-04 17:02:36 -05:00
m_indices_to_remove_s = 0 ;
if ( m_tracked_positions_s > = m_indices_to_remove_r )
{
m_indices_to_remove_r < < = 1 ;
ArrayResize ( m_indices_to_remove , m_indices_to_remove_r , m_indices_to_remove_r ) ;
}
2026-07-28 09:08:36 -05:00
//--- Main iteration
2026-08-04 17:02:36 -05:00
for ( int i = 0 ; i < m_tracked_positions_s ; i + + )
2026-07-28 09:08:36 -05:00
{
const ulong current_ticket = m_tracked_positions [ i ] . ticket ; // Current position ticket
if ( m_tracked_positions [ i ] . type = = POSITION_TYPE_BUY )
{
const int current_level_index = m_tracked_positions [ i ] . current_partial_index ;
if ( ask > m_tracked_positions [ i ] . tp_levels [ current_level_index ] . partial_price )
{
// Failed to select the ticket
if ( ! : : PositionSelectByTicket ( current_ticket ) )
{
LogError ( : : StringFormat ( " Could not select ticket %I64u, last error = %d " , current_ticket , : : GetLastError ( ) ) , FUNCION_ACTUAL ) ;
continue ;
}
const double current_position_volume = : : PositionGetDouble ( POSITION_VOLUME ) ; // Current operation volume
double volume_to_close = current_position_volume * m_tracked_positions [ i ] . tp_levels [ current_level_index ] . volume_percentage_to_close ; // Volume that will be "removed" from the position
if ( volume_to_close < m_minimum_volume )
{
m_tracked_positions [ i ] . current_partial_index + = 1 ;
LogError ( : : StringFormat ( " The lot to take %f is less than the minimum lot %f " , volume_to_close , m_minimum_volume ) , FUNCION_ACTUAL ) ;
if ( m_tracked_positions [ i ] . current_partial_index = = m_max_partial_levels ) // Maximum partials reached
{
// Remove the partial
2026-08-04 17:02:36 -05:00
m_indices_to_remove [ m_indices_to_remove_s + + ] = i ;
2026-07-28 09:08:36 -05:00
}
continue ;
}
volume_to_close = RoundToStep ( volume_to_close , m_volume_step ) ;
m_trade_executor . PositionClosePartial ( current_ticket , volume_to_close ) ;
m_tracked_positions [ i ] . current_partial_index + = 1 ;
if ( m_tracked_positions [ i ] . current_partial_index = = m_max_partial_levels ) // Maximum partials reached
{
// Remove the partial
2026-08-04 17:02:36 -05:00
m_indices_to_remove [ m_indices_to_remove_s + + ] = i ;
2026-07-28 09:08:36 -05:00
}
}
}
else
if ( m_tracked_positions [ i ] . type = = POSITION_TYPE_SELL )
{
const int current_level_index = m_tracked_positions [ i ] . current_partial_index ;
if ( bid < m_tracked_positions [ i ] . tp_levels [ current_level_index ] . partial_price )
{
if ( ! : : PositionSelectByTicket ( current_ticket ) )
{
LogError ( : : StringFormat ( " Could not select ticket %I64u, last error = %d " , current_ticket , : : GetLastError ( ) ) , FUNCION_ACTUAL ) ;
continue ;
}
const double current_position_volume = : : PositionGetDouble ( POSITION_VOLUME ) ; // Current trade volume
double volume_to_close = current_position_volume * m_tracked_positions [ i ] . tp_levels [ current_level_index ] . volume_percentage_to_close ; // Volume to close
if ( volume_to_close < m_minimum_volume ) // Volume too small
{
m_tracked_positions [ i ] . current_partial_index + = 1 ; // Continue with the next one
LogError ( : : StringFormat ( " The lot to take %f is less than the minimum lot %f " , volume_to_close , m_minimum_volume ) , FUNCION_ACTUAL ) ;
if ( m_tracked_positions [ i ] . current_partial_index = = m_max_partial_levels )
{
2026-08-04 17:02:36 -05:00
m_indices_to_remove [ m_indices_to_remove_s + + ] = i ;
2026-07-28 09:08:36 -05:00
}
continue ;
}
//---
volume_to_close = RoundToStep ( volume_to_close , m_volume_step ) ;
m_trade_executor . PositionClosePartial ( current_ticket , volume_to_close ) ; // Partial closure
m_tracked_positions [ i ] . current_partial_index + = 1 ; // Move to next index
if ( m_tracked_positions [ i ] . current_partial_index = = m_max_partial_levels ) // Limit reached, remove this index
{
2026-08-04 17:02:36 -05:00
m_indices_to_remove [ m_indices_to_remove_s + + ] = i ;
2026-07-28 09:08:36 -05:00
}
}
}
}
//---
2026-08-04 17:02:36 -05:00
RemoveMultipleIndexesWithVSizeRemoveCts ( m_tracked_positions , m_tracked_positions_s ,
m_indices_to_remove , 0 , m_indices_to_remove_s ) ;
2026-07-28 09:08:36 -05:00
}
//+-------------------------------------------------------------------------+
//| Receives 2 aligned pairs of tp - volume, as output sets |
//| the two strings passed by reference with the values separated by "," |
//+-------------------------------------------------------------------------+
void PartialSet ( bool use_partials , double volume_percentage1 , double tp_percentage1 , double volume_percentage2 , double tp_percentage2 ,
string & out_volume_percentages_string , string & out_tp_percentages_string )
{
if ( use_partials )
{
out_tp_percentages_string = : : StringFormat ( " %.2f, %.2f " , tp_percentage1 , tp_percentage2 ) ;
out_volume_percentages_string = : : StringFormat ( " %.2f, %.2f " , volume_percentage1 , volume_percentage2 ) ;
}
else
{
out_tp_percentages_string = " 0 " ;
out_volume_percentages_string = " 0 " ;
}
}
//+------------------------------------------------------------------+
# endif
//+------------------------------------------------------------------+