After stopping my investigation of
tools for spectral analysis over the last few weeks I have been doing another
mooc, this time
Learning from Data, and also working on the idea of one of my
earlier posts.
In the above linked post there is a video showing the idea as a "paint bar" study. However, I thought it would be a good idea to render it as an indicator, the C++
Octave .oct code for which is shown in the code box below.
DEFUN_DLD ( adjustable_mfe_mae_from_open_indicator, args, nargout,
"-*- texinfo -*-\n\
@deftypefn {Function File} {} adjustable_mfe_mae_from_open_indicator (@var{open,high,low,close,lookback_length})\n\
This function takes four input series for the OHLC and a value for lookback length. The main outputs are\n\
two indicators, long and short, that show the ratio of the MFE over the MAE from the open of the specified\n\
lookback in the past. The indicators are normalised to the range 0 to 1 by a sigmoid function and a MFE/MAE\n\
ratio of 1:1 is shifted in the sigmoid function to give a 'neutral' indicator reading of 0.5. A third output\n\
is the max high - min low range over the lookback_length normalised by the range of the daily support and\n\
resistance levels S1 and R1 calculated for the first bar of the lookback period. This is also normalised to\n\
give a reading of 0.5 in the sigmoid function if the ratio is 1:1. The point of this third output is to give\n\
some relative scale to the unitless MFE/MAE ratio and to act as a measure of strength or importance of the\n\
MFE/MAE ratio.\n\
@end deftypefn" )
{
octave_value_list retval_list ;
int nargin = args.length () ;
// check the input arguments
if ( nargin != 5 )
{
error ( "Invalid arguments. Arguments are price series for open, high, low and close and value for lookback length." ) ;
return retval_list ;
}
if ( args(4).length () != 1 )
{
error ( "Invalid argument. Argument 5 is a scalar value for the lookback length." ) ;
return retval_list ;
}
int lookback_length = args(4).int_value() ;
if ( args(0).length () < lookback_length )
{
error ( "Invalid argument lengths. Argument lengths for open, high, low and close vectors should be >= lookback length." ) ;
return retval_list ;
}
if ( args(1).length () != args(0).length () )
{
error ( "Invalid argument lengths. Argument lengths for open, high, low and close vectors should be equal." ) ;
return retval_list ;
}
if ( args(2).length () != args(0).length () )
{
error ( "Invalid argument lengths. Argument lengths for open, high, low and close vectors should be equal." ) ;
return retval_list ;
}
if ( args(3).length () != args(0).length () )
{
error ( "Invalid argument lengths. Argument lengths for open, high, low and close vectors should be equal." ) ;
return retval_list ;
}
if (error_state)
{
error ( "Invalid arguments. Arguments are price series for open, high, low and close and value for lookback length." ) ;
return retval_list ;
}
// end of input checking
// inputs
ColumnVector open = args(0).column_vector_value () ;
ColumnVector high = args(1).column_vector_value () ;
ColumnVector low = args(2).column_vector_value () ;
ColumnVector close = args(3).column_vector_value () ;
// outputs
ColumnVector long_mfe_mae = args(0).column_vector_value () ;
ColumnVector short_mfe_mae = args(0).column_vector_value () ;
ColumnVector range = args(0).column_vector_value () ;
// variables
double max_high = *std::max_element( &high(0), &high( lookback_length ) ) ;
double min_low = *std::min_element( &low(0), &low( lookback_length ) ) ;
double pivot_point = ( high(0) + low(0) + close(0) ) / 3.0 ;
double s1 = 2.0 * pivot_point - high(0) ;
double r1 = 2.0 * pivot_point - low(0) ;
for ( octave_idx_type ii (0) ; ii < lookback_length ; ii++ ) // initial ii loop
{
// long_mfe_mae
if ( open(0) > min_low ) // the "normal" situation
{
long_mfe_mae(ii) = 1.0 / ( 1.0 + exp( -( ( max_high - open(0) ) / ( open(0) - min_low ) - 1.0 ) ) ) ;
}
else if ( open(0) == min_low )
{
long_mfe_mae(ii) = 1.0 ;
}
else
{
long_mfe_mae(ii) = 0.5 ;
}
// short_mfe_mae
if ( open(0) < max_high ) // the "normal" situation
{
short_mfe_mae(ii) = 1.0 / ( 1.0 + exp( -( ( open(0) - min_low ) / ( max_high - open(0) ) - 1.0 ) ) ) ;
}
else if ( open(0) == max_high )
{
short_mfe_mae(ii) = 1.0 ;
}
else
{
short_mfe_mae(ii) = 0.5 ;
}
range(ii) = 1.0 / ( 1.0 + exp( -( ( max_high - min_low ) / ( r1 - s1 ) - 1.0 ) ) ) ;
} // end of initial ii loop
for ( octave_idx_type ii ( lookback_length ) ; ii < args(0).length() ; ii++ ) // main ii loop
{
// assign variable values
max_high = *std::max_element( &high( ii - lookback_length + 1 ), &high( ii + 1 ) ) ;
min_low = *std::min_element( &low( ii - lookback_length + 1 ), &low( ii + 1 ) ) ;
pivot_point = ( high(ii-lookback_length) + low(ii-lookback_length) + close(ii-lookback_length) ) / 3.0 ;
s1 = 2.0 * pivot_point - high(ii-lookback_length) ;
r1 = 2.0 * pivot_point - low(ii-lookback_length) ;
// long_mfe_mae
if ( open( ii - lookback_length + 1 ) > min_low && open( ii - lookback_length + 1 ) < max_high ) // the "normal" situation
{
long_mfe_mae(ii) = 1.0 / ( 1.0 + exp( -( ( max_high - open( ii - lookback_length + 1 ) ) / ( open( ii - lookback_length + 1 ) - min_low ) - 1.0 ) ) ) ;
}
else if ( open( ii - lookback_length + 1 ) == min_low )
{
long_mfe_mae(ii) = 1.0 ;
}
else
{
long_mfe_mae(ii) = 0.0 ;
}
// short_mfe_mae
if ( open( ii - lookback_length + 1 ) > min_low && open( ii - lookback_length + 1 ) < max_high ) // the "normal" situation
{
short_mfe_mae(ii) = 1.0 / ( 1.0 + exp( -( ( open( ii - lookback_length + 1 ) - min_low ) / ( max_high - open( ii - lookback_length + 1 ) ) - 1.0 ) ) ) ;
}
else if ( open( ii - lookback_length + 1 ) == max_high )
{
short_mfe_mae(ii) = 1.0 ;
}
else
{
short_mfe_mae(ii) = 0.0 ;
}
range(ii) = 1.0 / ( 1.0 + exp( -( ( max_high - min_low ) / ( r1 - s1 ) - 1.0 ) ) ) ;
} // end of main ii loop
retval_list(2) = range ;
retval_list(1) = short_mfe_mae ;
retval_list(0) = long_mfe_mae ;
return retval_list ;
} // end of function
The way to interpret this is as follows:
- if the "long" indicator reading is above 0.5, go long
- if the "short" is above 0.5, go short
- if both are below 0.5, go flat
An alternative, if the indicator reading is flat, is to maintain any previous non flat position. I won't show a chart of the indicator itself as it just looks like a very noisy oscillator, but the equity curve(s) of it, without the benefit of foresight, on the EURUSD forex pair are shown below.
The yellow equity curve is the cumulative, close to close, tick returns of a buy and hold strategy, the blue is the return going flat when indicated, and the red maintaining the previous position when flat is indicated. Not much to write home about. However, this second chart shows the return when one has the benefit of the "peek into the future" as discussed in my earlier post.
The colour of the curves are as before except for the addition of the green equity curve, which is the cumulative, vwap value to vwap value tick returns, a simple representation of what an equity curve with realistic slippage might look like. This second set of equity curves shows the promise of what could be achievable if a neural net to accurately predict future values of the above indicator can be trained. More in an upcoming post.