Friday, 31 July 2020

Currency Strength Candlestick Chart

In my previous posts on currency strength indices I have always visualised the indicator(s) as a line chart, e.g. here. However, after some deep thought, I have now created a way to visualise this as a candlestick chart using Octave's candle function, which, by the way, was written by me. Creating the candlestick body of a currency strength index was quite straight forward - just use the previous currency strength value as the bar's open and the current currency strength value as the close. A simple plot of this, with an overlaid currency strength index line chart, is

Of course the problem with this rendering is that there are no candlestick wicks.

My solution to create the wicks is showcased by the following code snippets
retval_high_wicks( ii , 6 ) = log( str2double( S.candles{ ii }.mid.h ) / max( str2double( S.candles{ ii }.mid.o ) , str2double( S.candles{ ii }.mid.c ) ) ) ;
retval_low_wicks( ii , 6 ) = log( str2double( S.candles{ ii }.mid.l ) / min( str2double( S.candles{ ii }.mid.o ) , str2double( S.candles{ ii }.mid.c ) ) ) ;
and
[ ii , ~ , v ] = find( [ retval_high_wicks( : , [32 33 34 35 36 37].+5 ) , -1.*retval_low_wicks( : , [5 20 28].+5 ) ] ) ;
new_index_high_wicks( : , 13 ) = accumarray( ii , v , [] , @mean ) ;
[ ii , ~ , v ] = find( [ retval_low_wicks( : , [32 33 34 35 36 37].+5 ) , -1.*retval_high_wicks( : , [5 20 28].+5 ) ] ) ;
new_index_low_wicks( : , 13 ) = accumarray( ii , v , [] , @mean ) ;
The first snippet shows an additional bit of code to the code here to record the log values of highs (lows) over (under) the candlestick bodies of all relevant currencies used in creating the currency strength indices.

The second snippet shows how the wicks are created, namely by taking the mean log values of high (low) wicks indexed by e.g.
[32 33 34 35 36 37].+5 and [5 20 28].+5
columns of downloaded forex crosses.

The reasoning behind this is as follows: take, for example, the EUR_USD forex pair - the upper wicks of these bars are recorded as upper wicks for the EUR index candles and as lower wicks for the USD index candles, reflecting the fact that upper wicks in EUR_USD can be viewed as intrabar EUR strength pushing to new highs or, alternatively, USD index candle's weakness pushing to new lows which, because the USD is the quote currency of the pair, also leads to new highs in the cross. A similar, reversed logic applies to the low wicks of the cross.

Below are charts of currency strength index candles created according to this methodology
The upper pane shows GBP currency strength index candles and the lower pane the same for USD. This is basically price action for Thursday, 30th July, 2020. The green vertical lines are the London and New York opens respectively, the red vertical line is the London close and the charts end at more or less the New York close. Bars prior to the London open are obviously the overnight Asian session.

My contemporaneous volume profile chart is the upper right pane below
 
From these charts it is easy to discern that the upward movement of GBP_USD during the main London session was due to GBP strength, whilst after the London close the continued upward movement of GBP_USD was due to USD weakness.

However, the point of this blog post was not to pass commentary on FX price movements, but to illustrate a methodology of creating candlestick charts for currency strength indices.

Enjoy!

Wednesday, 15 July 2020

Forex Intraday Seasonality

Over the last week or so I have been reading about/investigating this post's title matter. Some quotes from various papers' abstracts on the matter are:
  • "We provide empirical evidence that the unique signature of the FX market seasonality is indeed due to the different time zones market participants operate from. However, once normalised using our custom-designed procedure, we observe a pattern akin to equity markets. Thus, we have revealed an important FX market property that has not been reported before." - Phd. paper - April 2013
  • "Using 10 years of high‐frequency foreign exchange data, we present evidence of time‐of‐day effects in foreign exchange returns through a significant tendency for currencies to depreciate during local trading hours. We confirm this pattern across a range of currencies and find that, in the case of EUR/USD, it can form a simple, profitable trading strategy" - Paper date - November 2010 - emphasis is mine
  • "This paper examines the intraday seasonality of transacted limit and market orders in the DEM/USD foreign exchange market. Empirical analysis of completed transactions data based on the Dealing 2000-2 electronic inter-dealer broking system indicates significant evidence of intraday seasonality in returns and return volatilities under usual market conditions. Moreover, analysis of realised tail outcomes supports seasonality for extraordinary market conditions across the trading day." - Paper date - May 2007
  • "In this article, we search for the evidence of intraweek and intraday anomalies on the spot foreign exchange (FOREX) market. Having in mind the international scope of this market ... We find that intraday and interaction between day and hour anomalies are present in trading EUR/USD on the spot FOREX market over the period of 10 years" - Paper date - 2014
  • "We find that the underpinnings for the time-varying pattern of the probability of informed trading are rooted in the strategic arrival of informed traders on a particular hour-of-day, day-of-week, and geographic location (market)." - Paper date - April 2008
In addition to this there seem to be numerous blogs, articles online etc. which also suggest that forex seasonality is a real phenomenon, so I thought I'd have a quick look into it myself.

Rather than do a full, statistical analysis I have used the following Octave function
clear all ;
data = dlmread( '/home/path/to/hourly_currency_index_g_mults' ) ;
## aud_x = x( 1)  ; cad_x = x( 2 ) ; chf_x = x( 3 ) ; eur_x = x( 4 ) ; gbp_x = x( 5 ) ; hkd_x = x( 6 ) ;
## jpy_x = x( 7 ) ; nzd_x = x( 8 ) ; sgd_x = x( 9 ) ; usd_x = x( 10 ) ; ## plus 6 for ix to account for date cols
## first 6 cols are YYYY MM DD HH-GMT HH-BST HH-EST
logged_data = data ; logged_data( : , 7 : end ) = log( logged_data( : , 7 : end ) ) ;

## get the days. The days of the week are numbered 1–7 with the first day being Sunday.
days_num = weekday( datenum( [ data(:,1) , data(:,2) , data(:,3) , data(:,5) ] ) ) ; ## BST time

start = input( 'Do you want to enter start date? Y or N ' , 's' ) ;
if ( strcmp( tolower( start ) , 'y' ) )
 year_start = input( 'Enter year YYYY:  ' ) ;
 month_start = input( 'Enter month MM:  ' ) ;
 day_start = input( 'Enter day date:  ' ) ;
 delete_ix = find( (logged_data(:,1)==year_start) .* (logged_data(:,2)==month_start) .* (logged_data(:,3)==day_start) ) ;
 
 if ( !isempty( delete_ix ) )
 logged_data( 1 : delete_ix , : ) = [] ; days_num( 1 : delete_ix , : ) = [] ;
 else
 disp( 'Invalid start date, so charts will show all data.' ) ;
 endif

endif

## create individual day indices
monday_indices = [ ( 0 : 1 : 23 )' , zeros( 24 , 10 ) ] ;
tuesday_indices = monday_indices ;
wednesday_indices = monday_indices ;
thursday_indices = monday_indices ;
friday_indices = monday_indices ;
alldays_indices = monday_indices ;

running_denom = zeros( 24 , 10 ) ;

for jj = 0 : 23
ix = find( ( days_num == 2 ) .* ( logged_data( : , 5 ) == jj ) ) ;
running_denom( jj + 1 , : ) = running_denom( jj + 1 , : ) + numel( ix ) ;
monday_indices( jj + 1 , 2 : end ) = sum( logged_data( ix , 7 : end ) , 1 ) ./ numel( ix ) ;
alldays_indices( jj + 1 , 2 : end ) = sum( logged_data( ix , 7 : end ) , 1 ) ;
endfor

for jj = 0 : 23
ix = find( ( days_num == 3 ) .* ( logged_data( : , 5 ) == jj ) ) ;
running_denom( jj + 1 , : ) = running_denom( jj + 1 , : ) + numel( ix ) ;
tuesday_indices( jj + 1 , 2 : end ) = sum( logged_data( ix , 7 : end ) , 1 ) ./ numel( ix ) ;
alldays_indices( jj + 1 , 2 : end ) = alldays_indices( jj + 1 , 2 : end ) .+ sum( logged_data( ix , 7 : end ) , 1 ) ;
endfor

for jj = 0 : 23
ix = find( ( days_num == 4 ) .* ( logged_data( : , 5 ) == jj ) ) ;
running_denom( jj + 1 , : ) = running_denom( jj + 1 , : ) + numel( ix ) ;
wednesday_indices( jj + 1 , 2 : end ) = sum( logged_data( ix , 7 : end ) , 1 ) ./ numel( ix ) ;
alldays_indices( jj + 1 , 2 : end ) = alldays_indices( jj + 1 , 2 : end ) .+ sum( logged_data( ix , 7 : end ) , 1 ) ;
endfor

for jj = 0 : 23
ix = find( ( days_num == 5 ) .* ( logged_data( : , 5 ) == jj ) ) ;
running_denom( jj + 1 , : ) = running_denom( jj + 1 , : ) + numel( ix ) ;
thursday_indices( jj + 1 , 2 : end ) = sum( logged_data( ix , 7 : end ) , 1 ) ./ numel( ix ) ;
alldays_indices( jj + 1 , 2 : end ) = alldays_indices( jj + 1 , 2 : end ) .+ sum( logged_data( ix , 7 : end ) , 1 ) ;
endfor

for jj = 0 : 20 ## market closes at 17:00 EST
ix = find( ( days_num == 6 ) .* ( logged_data( : , 5 ) == jj ) ) ;
running_denom( jj + 1 , : ) = running_denom( jj + 1 , : ) + numel( ix ) ;
friday_indices( jj + 1 , 2 : end ) = sum( logged_data( ix , 7 : end ) , 1 ) ./ numel( ix ) ;
alldays_indices( jj + 1 , 2 : end ) = alldays_indices( jj + 1 , 2 : end ) .+ sum( logged_data( ix , 7 : end ) , 1 ) ;
endfor

alldays_indices( : , 2 : end ) = alldays_indices( : , 2 : end ) ./ running_denom ;

monday_indices( : , 2 : end ) = cumsum( monday_indices( : , 2 : end ) ) ;
tuesday_indices( : , 2 : end ) = cumsum( tuesday_indices( : , 2 : end ) ) ;
wednesday_indices( : , 2 : end ) = cumsum( wednesday_indices( : , 2 : end ) ) ;
thursday_indices( : , 2 : end ) = cumsum( thursday_indices( : , 2 : end ) ) ;
friday_indices( : , 2 : end ) = cumsum( friday_indices( : , 2 : end ) ) ;
alldays_indices( : , 2 : end ) = cumsum( alldays_indices( : , 2 : end ) ) ;

if ( ishandle(1) )
 clf(1) ;
endif
figure( 1 ) ;
h1 = axes( 'position' , [ 0.03 , 0.54 , 0.30 , 0.43 ] ) ; plot( monday_indices(:,3) , 'k' , 'linewidth' , 2 , ...
monday_indices(:,4) , 'c' , 'linewidth' , 2 , ...
monday_indices(:,5) , 'b' , 'linewidth' , 2 , ...
monday_indices(:,6) , 'r' , 'linewidth' , 2 , ...
monday_indices(:,11) , 'g' , 'linewidth' , 2 ) ; xlim([0 23]) ; grid minor on ; title( 'MONDAY' ) ;
legend( 'CAD' , 'CHF' , 'EUR' , 'GBP' , 'USD' , 'location' , 'north' , 'orientation' , 'horizontal' ) ;
vline( 7 , 'r' ) ; vline( 12 , 'g' ) ;

h2 = axes( 'position' , [ 0.36 , 0.54 , 0.30 , 0.43 ] ) ; plot( tuesday_indices(:,3) , 'k' , 'linewidth' , 2 , ...
tuesday_indices(:,4) , 'c' , 'linewidth' , 2 , ...
tuesday_indices(:,5) , 'b' , 'linewidth' , 2 , ...
tuesday_indices(:,6) , 'r' , 'linewidth' , 2 , ...
tuesday_indices(:,11) , 'g' , 'linewidth' , 2 ) ; xlim([0 23]) ; grid minor on ; title( 'TUESDAY' ) ;
legend( 'CAD' , 'CHF' , 'EUR' , 'GBP' , 'USD' , 'location' , 'north' , 'orientation' , 'horizontal' ) ;
vline( 7 , 'r' ) ; vline( 12 , 'g' ) ;

h3 = axes( 'position' , [ 0.69 , 0.54 , 0.30 , 0.43 ] ) ; plot( wednesday_indices(:,3) , 'k' , 'linewidth' , 2 , ...
wednesday_indices(:,4) , 'c' , 'linewidth' , 2 , ...
wednesday_indices(:,5) , 'b' , 'linewidth' , 2 , ...
wednesday_indices(:,6) , 'r' , 'linewidth' , 2 , ...
wednesday_indices(:,11) , 'g' , 'linewidth' , 2 ) ; xlim([0 23]) ; grid minor on ; title( 'WEDNESDAY' ) ;
legend( 'CAD' , 'CHF' , 'EUR' , 'GBP' , 'USD' , 'location' , 'north' , 'orientation' , 'horizontal' ) ;
vline( 7 , 'r' ) ; vline( 12 , 'g' ) ;

h4 = axes( 'position' , [ 0.03 , 0.04 , 0.30 , 0.43 ] ) ; plot( thursday_indices(:,3) , 'k' , 'linewidth' , 2 , ...
thursday_indices(:,4) , 'c' , 'linewidth' , 2 , ...
thursday_indices(:,5) , 'b' , 'linewidth' , 2 , ...
thursday_indices(:,6) , 'r' , 'linewidth' , 2 , ...
thursday_indices(:,11) , 'g' , 'linewidth' , 2 ) ; xlim([0 23]) ; grid minor on ; title( 'THURSDAY' ) ;
legend( 'CAD' , 'CHF' , 'EUR' , 'GBP' , 'USD' , 'location' , 'north' , 'orientation' , 'horizontal' ) ;
vline( 7 , 'r' ) ; vline( 12 , 'g' ) ;

h5 = axes( 'position' , [ 0.36 , 0.04 , 0.30 , 0.43 ] ) ; plot( friday_indices(:,3) , 'k' , 'linewidth' , 2 , ...
friday_indices(:,4) , 'c' , 'linewidth' , 2 , ...
friday_indices(:,5) , 'b' , 'linewidth' , 2 , ...
friday_indices(:,6) , 'r' , 'linewidth' , 2 , ...
friday_indices(:,11) , 'g' , 'linewidth' , 2 ) ; xlim([0 21]) ; grid minor on ; title( 'FRIDAY' ) ;
legend( 'CAD' , 'CHF' , 'EUR' , 'GBP' , 'USD' , 'location' , 'north' , 'orientation' , 'horizontal' ) ;
vline( 7 , 'r' ) ; vline( 12 , 'g' ) ;

h6 = axes( 'position' , [ 0.69 , 0.04 , 0.30 , 0.43 ] ) ; plot( alldays_indices(:,3) , 'k' , 'linewidth' , 2 , ...
alldays_indices(:,4) , 'c' , 'linewidth' , 2 , ...
alldays_indices(:,5) , 'b' , 'linewidth' , 2 , ...
alldays_indices(:,6) , 'r' , 'linewidth' , 2 , ...
alldays_indices(:,11) , 'g' , 'linewidth' , 2 ) ; xlim([0 23]) ; grid minor on ; title( 'ALL DAYS COMBINED' ) ;
legend( 'CAD' , 'CHF' , 'EUR' , 'GBP' , 'USD' , 'location' , 'north' , 'orientation' , 'horizontal' ) ;
vline( 7 , 'r' ) ; vline( 12 , 'g' ) ;

if ( ishandle(2) )
 clf(2) ;
endif
figure( 2 ) ;
h1 = axes( 'position' , [ 0.03 , 0.54 , 0.30 , 0.43 ] ) ; plot( monday_indices(:,2) , 'k' , 'linewidth' , 2 , ...
monday_indices(:,7) , 'c' , 'linewidth' , 2 , ...
monday_indices(:,8) , 'b' , 'linewidth' , 2 , ...
monday_indices(:,9) , 'r' , 'linewidth' , 2 , ...
monday_indices(:,10) , 'g' , 'linewidth' , 2 ) ; xlim([0 23]) ; grid minor on ; title( 'MONDAY' ) ;
legend( 'AUD' , 'HKD' , 'JPY' , 'NZD' , 'SGD' , 'location' , 'north' , 'orientation' , 'horizontal' ) ;
vline( 7 , 'r' ) ; vline( 12 , 'g' ) ;

h2 = axes( 'position' , [ 0.36 , 0.54 , 0.30 , 0.43 ] ) ; plot( tuesday_indices(:,2) , 'k' , 'linewidth' , 2 , ...
tuesday_indices(:,7) , 'c' , 'linewidth' , 2 , ...
tuesday_indices(:,8) , 'b' , 'linewidth' , 2 , ...
tuesday_indices(:,9) , 'r' , 'linewidth' , 2 , ...
tuesday_indices(:,10) , 'g' , 'linewidth' , 2 ) ; xlim([0 23]) ; grid minor on ; title( 'TUESDAY' ) ;
legend( 'AUD' , 'HKD' , 'JPY' , 'NZD' , 'SGD' , 'location' , 'north' , 'orientation' , 'horizontal' ) ;
vline( 7 , 'r' ) ; vline( 12 , 'g' ) ;

h3 = axes( 'position' , [ 0.69 , 0.54 , 0.30 , 0.43 ] ) ; plot( wednesday_indices(:,2) , 'k' , 'linewidth' , 2 , ...
wednesday_indices(:,7) , 'c' , 'linewidth' , 2 , ...
wednesday_indices(:,8) , 'b' , 'linewidth' , 2 , ...
wednesday_indices(:,9) , 'r' , 'linewidth' , 2 , ...
wednesday_indices(:,10) , 'g' , 'linewidth' , 2 ) ; xlim([0 23]) ; grid minor on ; title( 'WEDNESDAY' ) ;
legend( 'AUD' , 'HKD' , 'JPY' , 'NZD' , 'SGD' , 'location' , 'north' , 'orientation' , 'horizontal' ) ;
vline( 7 , 'r' ) ; vline( 12 , 'g' ) ;

h4 = axes( 'position' , [ 0.03 , 0.04 , 0.30 , 0.43 ] ) ; plot( thursday_indices(:,2) , 'k' , 'linewidth' , 2 , ...
thursday_indices(:,7) , 'c' , 'linewidth' , 2 , ...
thursday_indices(:,8) , 'b' , 'linewidth' , 2 , ...
thursday_indices(:,9) , 'r' , 'linewidth' , 2 , ...
thursday_indices(:,10) , 'g' , 'linewidth' , 2 ) ; xlim([0 23]) ; grid minor on ; title( 'THURSDAY' ) ;
legend( 'AUD' , 'HKD' , 'JPY' , 'NZD' , 'SGD' , 'location' , 'north' , 'orientation' , 'horizontal' ) ;
vline( 7 , 'r' ) ; vline( 12 , 'g' ) ;

h5 = axes( 'position' , [ 0.36 , 0.04 , 0.30 , 0.43 ] ) ; plot( friday_indices(:,2) , 'k' , 'linewidth' , 2 , ...
friday_indices(:,7) , 'c' , 'linewidth' , 2 , ...
friday_indices(:,8) , 'b' , 'linewidth' , 2 , ...
friday_indices(:,9) , 'r' , 'linewidth' , 2 , ...
friday_indices(:,10) , 'g' , 'linewidth' , 2 ) ; xlim([0 21]) ; grid minor on ; title( 'FRIDAY' ) ;
legend( 'AUD' , 'HKD' , 'JPY' , 'NZD' , 'SGD' , 'location' , 'north' , 'orientation' , 'horizontal' ) ;
vline( 7 , 'r' ) ; vline( 12 , 'g' ) ;

h6 = axes( 'position' , [ 0.69 , 0.04 , 0.30 , 0.43 ] ) ; plot( alldays_indices(:,2) , 'k' , 'linewidth' , 2 , ...
alldays_indices(:,7) , 'c' , 'linewidth' , 2 , ...
alldays_indices(:,8) , 'b' , 'linewidth' , 2 , ...
alldays_indices(:,9) , 'r' , 'linewidth' , 2 , ...
alldays_indices(:,10) , 'g' , 'linewidth' , 2 ) ; xlim([0 23]) ; grid minor on ; title( 'ALL DAYS COMBINED' ) ;
legend( 'AUD' , 'HKD' , 'JPY' , 'NZD' , 'SGD' , 'location' , 'north' , 'orientation' , 'horizontal' ) ;
vline( 7 , 'r' ) ; vline( 12 , 'g' ) ;
to conduct a quick visual analysis. This builds upon my recent work on fx pairs via oanda api and currency strength, and uses hourly data since June 2012.

This produces 24 hour seasonality charts of CAD, CHF, EUR, GBP and USD, i.e. the European and North American currencies.
The x-axis is in British Summer Time (BST) hours, the vertical red and green lines indicate 7:00am opens in London and New York respectively, all charts end at 17:00 New York (EST) time and the y-axis is hourly log returns. The individual currency seasonality lines are the cummulative cross-sectional means at BST 00, BST 01 ... etc. per weekday and all days combined (see subchart titles). BST Sunday evenings' returns prior to Monday trading are not included.

A similar chart for the Asian time zone currencies of AUD, HKD, JPY, NZD and SGD is also produced.
The function allows charts with a user selected data begin date to be plotted, but the illustrations above use all data available to me, i.e. hourly data since 2012.

It seems to me that, as indicated in my highlighting above, there is definite intraday forex seasonalilty in play. However, readers should be cautioned that the above is only a general tendency based on the last 9 years or so of hourly data. A more recent "data snapshot" of only data since the beginning of 2020 can tell a slightly different story:
look at GBP (red line) on Tuesdays and Wednesdays, for example. As always with stuff one reads online, even the extremely high quality stuff on this blog 😊, Caveat emptor.

Friday, 10 July 2020

Time Warp Edit Distance as a Loss Function

Some time ago I posted about the time warp edit distance (twed) and recently I've revisited this as a possible loss function. The basic idea is to use this during algorithm training to match a "perfect equity curve" rather than use the usual loss measurements such as mean squared error for regression or cross entropy for classification.

As a proof of concept I've been playing around with this Octave script
clear all ;
1 ;

function J = twed_loss( x )
 global price ;
 global returns ;
 global perfect_equity_curve ; 
 fast_ma = sma( price , round( x( 1 ) ) ) ;
 slow_ma = sma( price , round( x( 2 ) ) ) ;
 test_position_vector = sign( fast_ma .- slow_ma ) ;
 test_position_vector = shift( test_position_vector , 2 ) ;
 test_position_vector( 1 : 3 ) = 0 ;
 test_equity_curve = cumsum( returns .* test_position_vector ) ;
 x_axis = ( 1 : numel( perfect_equity_curve ) )' ;
 J = twed( perfect_equity_curve , x_axis , test_equity_curve , x_axis , 1 , 0.001 ) ;
endfunction

## create a perfectly predictable price
global price = sinewave( 200 , 20 )' .+ 5 ;
global returns = [ 0 ; diff( price ) ] ;
perfect_position_vector = sign( returns ) ;
perfect_position_vector = shift( perfect_position_vector , 2 ) ; 
perfect_position_vector( 1 : 3 ) = 0 ;
global perfect_equity_curve = cumsum( returns .* perfect_position_vector ) ;

## now do the Baysian training
## set up the parameters for bayesopt
params.n_iterations = 300 ; ## 190
params.n_init_samples = 10 ; ## 10
params.n_iter_relearn = 1 ; ## Number of iterations between re-learning kernel parameters. That is, kernel learning ocur 1 out of n_iter_relearn iterations. 
## Ideally, the best precision is obtained when the kernel parameters are learned every iteration (n_iter_relearn=1). 
## However, this learning part is computationally expensive and implies a higher cost per iteration. If n_iter_relearn=0, then there is no relearning. [Default 50]
params.crit_name = 'cEI' ;
params.surr_name = 'sStudentTProcessNIG' ;
params.noise = 1e-6 ;
params.kernel_name = 'kMaternARD5' ;
params.kernel_hp_mean = [ 1 ] ;
params.kernel_hp_std = [ 10 ] ;
params.verbose_level = 0 ; ## Negative -> Error -> stdout 0 -> Warning -> stdout 1 -> Info -> stdout 2 -> Debug -> stdout
                           ## 3 -> Warning -> log file 4 -> Info -> log file 5 -> Debug -> log file 5 -> Error -> log file
params.load_save_flag = 0 ; ## 1-Load data, 2-Save data, 3-Load and append data. Other values, no file saving or restore [Default 0]
params.log_filename = '/home/dekalog/Documents/octave/twed/bayeopt.log' ; % Name/path of the log file 
## (if applicable, verbose_level>=3) [Default "bayesopt.log"]
params.load_filename = '/home/dekalog/Documents/octave/twed/bayeopt.log' ;
params.save_filename = '/home/dekalog/Documents/octave/twed/bayeopt.log' ;

lb = [ 2 3 ] ;
## upper bounds
ub = [ 30 30 ] ;
nDimensions = length( lb ) ;
[ xmin , fmin ] = bayesoptcont( 'twed_loss' , nDimensions , params , lb , ub ) ;
round( xmin )
fmin

fast_ma = sma( price , round( xmin(1) ) ) ;
slow_ma = sma( price , round( xmin(2) ) ) ;
figure(1) ; plot(price,'k','linewidth',2,fast_ma,'r','linewidth',2,slow_ma,'b','linewidth',2 ) ;
title( 'PRICE AND MA CROSSOVER SIGNALS' ) ; legend( 'PRICE' , 'FAST MA' , 'SLOW MA' ) ;
test_position_vector = sign( fast_ma .- slow_ma ) ;
test_position_vector = shift( test_position_vector , 2 ) ;
test_position_vector( 1 : 3 ) = 0 ;
test_equity_curve = cumsum( returns .* test_position_vector ) ; 
figure(2) ; plot(perfect_equity_curve,'b','linewidth',2,test_equity_curve,'r','linewidth',2) ;
title( 'EQUITY CURVES' ) ; legend( 'PERFECT EQUITY CURVE' , 'TEST EQUITY CURVE' ) ;
which produces plots such as this
and this,
which both show a fast and slow moving average crossover system on sine wave "price" of period 20, optimised to match equity curves such as below via the twed loss.
What is interesting about this is that the moving average lengths usually converge to more or less the expected theoretical optimum, with the required change of sign of signal, where the crossovers indicate peaks and troughs in price and hence perfect entry and exit signals.

However, sometimes the solution looks like this,
which is an 11 period fast moving average and a 10 period slow one, quite a contrarian solution compared to the theoretical optimum, but actually giving a lower twed loss.

I quite like the idea of optimising for what we actually care about, i.e. the equity curve, whilst at the same time possibly uncovering unique solutions. It seems that the twed loss shows promise.

More in due course.

Thursday, 18 June 2020

More Work on RVFL Networks

Back in November last year I posted about Random Vector Functional Link (RVFL) networks here and here. Since then, along with my recent work on Oanda's API Octave functions and Market/Volume Profile visualisation, I have continued looking at RVFL networks and this post is an update on this work.

The "random" in RVFL means random initialisation of weights that are then fixed. It seems to me that it might be possible to do better than random by having some principled way of weight initialisation. To this end I have used the Penalised MATLAB Toolbox on features derived from my ideal cyclic tau embedding function to at first train a Generalized Linear Model with the Lasso penalty and then the Ridge penalty over thousands of sets of Monte Carlo generated, ideal cyclic prices and such prices with trends. The best weights for each set of prices were recorded in an array and then the mean weight (and standard deviation) taken. This set of mean weights is intended to replace the random weights in a RVFL network designed to predict the probability of "price" being at a cyclic turning point using the above cyclic tau embedding features.

Of course these weights could be considered a trained model in and of themselves, and the following screenshots show "out of sample" performance on Monte Carlo generated ideal prices that were not used in the training of the mean weights.
The black line is the underlying cyclic price and the red, blue and green lines are the mean weight model probabilities for cyclic peaks, troughs or neither respectively. Points where the peak/trough probabilities exceed the neither probabilities are marked by the red and blue vertical lines. Similarly, we have prices trending up in a cyclic fashion
and also trending down
In the cases of the last two trending markets only the swing highs and lows are indicated. The reason for this is that during training, based on my "expert knowledge" of the cyclic tau features used, it is unreasonable to expect these features to accurately capture the end of an up leg in a bull trend or the end of a down leg in a bear trend - hence these were not presented as a positive class during training.

As I said above the motivation for this is to get a more meaningful hidden layer in a RVFL network. This hidden layer will consist of seven Sigmoid functions which each give a probability of price being at or not being at a cyclic turn, conditional upon the type of market the input weights were trained on.

More in due course.

Saturday, 6 June 2020

Downloading FX Pairs via Oanda API to Calculate Currency Strength Indicator

In the past I have posted a series of blog posts about a Currency Strength Indicator (here, here, here and here). This blog post gives an Octave function to use Oanda's API to download all the 10 minute OHLC data required to calculate the above strength indicators on the 10 minute time frame.
## Copyright (C) 2020 dekalog
## 
## This program is free software: you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
## 
## This program is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
## 
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see
## .

## -*- texinfo -*- 
## @deftypefn {} {@var{retval} =} get_currency_index_10m_pairs()
##
## This function gets the date and time value of the last currency index update for
## 10 minute bars by reading the last line of the file at:
##
## "/home/path/to/file"
##
## and then downloads all the currencies required to calculate new values for
## new currency index calculations, via looped Oanda API calls. 
## 
##The RETVAL is a matrix of GMT dates in the form
## YYYY:MM:DD:HH:MM in the first 5 columns, followed by the 45 required
## currency candlestick close values.
##
## @seealso{}
## @end deftypefn

## Author: dekalog 
## Created: 2020-06-01

function retval = get_currency_index_10m_pairs()
 
## cell array of currency crosses to iterrate over to get the complete set 
## of currency crosses to create a currency index
iter_vec = {'AUD_CAD','AUD_CHF','AUD_HKD','AUD_JPY','AUD_NZD','AUD_SGD',...
'AUD_USD','CAD_CHF','CAD_HKD','CAD_JPY','CAD_SGD','CHF_HKD','CHF_JPY',...
'EUR_AUD','EUR_CAD','EUR_CHF','EUR_GBP','EUR_HKD','EUR_JPY','EUR_NZD',...
'EUR_SGD','EUR_USD','GBP_AUD','GBP_CAD','GBP_CHF','GBP_HKD','GBP_JPY',...
'GBP_NZD','GBP_SGD','GBP_USD','HKD_JPY','NZD_CAD','NZD_CHF','NZD_HKD',...
'NZD_JPY','NZD_SGD','NZD_USD','SGD_CHF','SGD_HKD','SGD_JPY','USD_CAD',...
'USD_CHF','USD_HKD','USD_JPY','USD_SGD'} ;

## read last line of current 10min_currency_indices
unix_command = [ "tail -1" , " " , "/home/path/to/file" ] ;
[ ~ , data ] = system( unix_command ) ;
data = strsplit( data , ',' ) ; ## gives a cell arrayfun of characters
## zero pad singular month representations, i.e. 1 to 01
if ( numel( data{ 2 } == 1 ) )
data{ 2 } = [ '0' , data{ 2 } ] ;
endif
## and also zero pad singular dates
if ( numel( data{ 3 } == 1 ) )
data{ 3 } = [ '0' , data{ 3 } ] ;
endif
## and also zero pad singular hours
if ( numel( data{ 4 } == 1 ) )
data{ 4 } = [ '0' , data{ 4 } ] ;
endif
## and also zero pad singular minutes
if ( numel( data{ 5 } == 1 ) )
data{ 5 } = [ '0' , data{ 5 } ] ;
endif
 
## set up the headers
Hquery = [ 'curl -s -H "Content-Type: application/json"' ] ; ## -s is silent mode for Curl for no paging to terminal
Hquery = [ Hquery , ' -H "Authorization: Bearer XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"' ] ;
query_begin = [ Hquery , ' "https://api-fxtrade.oanda.com/v3/instruments/' ] ;

## get time from last line of data
query_time = [ data{1} , '-' , data{2} , '-' , data{3} , 'T' , data{4} , '%3A' , data{5} , '%3A00.000000000Z&granularity=M10"' ] ;

## initialise with AUD_CAD
## construct the API call for particular cross
query = [ query_begin , iter_vec{ 1 } , '/candles?includeFirst=true&price=M&from=' , query_time ] ;
## call to use external Unix systems/Curl and return result
[ ~ , ret_JSON ] = system( query , RETURN_OUTPUT = 'TRUE' ) ;
## convert the returned JSON object to Octave structure
S = load_json( ret_JSON ) ;
## parse the returned structure S
if ( strcmp( fieldnames( S( 1 ) ) , 'errorMessage' ) == 0 ) ## no errorMessage in S
end_ix = numel( S.candles ) ; ## how many candles?
if ( S.candles{ end }.complete == 0 ) end_ix = end_ix - 1 ; endif ## account for incomplete candles
## create retval
retval = zeros( end_ix , 50 ) ; ## 45 currencies plus YYYY:MM:DD:HH:MM columns
for ii = 1 : end_ix
 date_time = strsplit( S.candles{ ii }.time , { '-' , 'T' , ':' } ) ;
 retval( ii , 1 ) = str2double( date_time( 1 , 1 ) ) ; ## year
 retval( ii , 2 ) = str2double( date_time( 1 , 2 ) ) ; ## month
 retval( ii , 3 ) = str2double( date_time( 1 , 3 ) ) ; ## day
 retval( ii , 4 ) = str2double( date_time( 1 , 4 ) ) ; ## hour
 retval( ii , 5 ) = str2double( date_time( 1 , 5 ) ) ; ## min
 retval( ii , 6 ) = str2double( S.candles{ ii }.mid.c ) ; ## candle close price 
endfor ## end of ii loop
else
error( 'Initialisation with AUD_CAD has failed.' ) ; 
endif ## end of strcmp if

for ii = 2 : numel( iter_vec )
## construct the API call for particular cross
query = [ query_begin , iter_vec{ ii } , '/candles?includeFirst=true&price=M&from=' , query_time ] ;
## call to use external Unix systems/Curl and return result
[ ~ , ret_JSON ] = system( query , RETURN_OUTPUT = 'TRUE' ) ;
## convert the returned JSON object to Octave structure
S = load_json( ret_JSON ) ;
## parse the returned structure S
if ( strcmp( fieldnames( S( 1 ) ) , 'errorMessage' ) == 0 ) ## no errorMessage in S
end_ix = numel( S.candles ) ; ## how many candles?
if ( S.candles{ end }.complete == 0 ) end_ix = end_ix - 1 ; endif ## account for incomplete candles
temp_retval = zeros( end_ix , 6 ) ;
for jj = 1 : end_ix
 date_time = strsplit( S.candles{ jj }.time , { '-' , 'T' , ':' } ) ;
 temp_retval( jj , 1 ) = str2double( date_time( 1 , 1 ) ) ; ## year
 temp_retval( jj , 2 ) = str2double( date_time( 1 , 2 ) ) ; ## month
 temp_retval( jj , 3 ) = str2double( date_time( 1 , 3 ) ) ; ## day
 temp_retval( jj , 4 ) = str2double( date_time( 1 , 4 ) ) ; ## hour
 temp_retval( jj , 5 ) = str2double( date_time( 1 , 5 ) ) ; ## min
 temp_retval( jj , 6 ) = str2double( S.candles{ jj }.mid.c ) ; ## candle close price  
endfor ## end of jj loop

## checks dates and times allignment before writing to retval
date_time_diffs_1 = setdiff( retval( : , 1 : 5 ) , temp_retval( : , 1 : 5 ) , 'rows' ) ; 
date_time_diffs_2 = setdiff( temp_retval( : , 1 : 5 ) , retval( : , 1 : 5 ) , 'rows' ) ;

 if ( isempty( date_time_diffs_1 ) && isempty( date_time_diffs_2 ) ) 
 ## there are no differences between retval dates and temp_retval dates 
 retval( : , ii + 5 ) = temp_retval( : , 6 ) ;
 
 elseif ( ~isempty( date_time_diffs_1 ) || ~isempty( date_time_diffs_2 ) )
 ## implies a difference between the date_times of retval and temp_retval, so merge them
 
 dn_retval = datenum( [ retval(:,1) , retval(:,2) , retval(:,3) , retval(:,4) , retval(:,5) ] ) ;
 dn_temp_retval = datenum( [ temp_retval(:,1) , temp_retval(:,2) , temp_retval(:,3) , temp_retval(:,4) , temp_retval(:,5) ] ) ;
 new_dn = unique( [ dn_retval ; dn_temp_retval ] ) ; new_date_vec = datevec( new_dn ) ; new_date_vec( : , 6 ) = [] ; 
 new_retval = [ new_date_vec , zeros( size( new_date_vec , 1 ) , 45 ) ] ;
 [ TF , S_IDX ] = ismember( new_retval( : , 1 : 5 ) , retval( : , 1 : 5 ) , 'rows' ) ;
 TF_ix = find( TF ) ; new_retval( TF_ix , 6 : end ) = retval( : , 6 : end ) ;
 [ TF , S_IDX ] = ismember( new_retval( : , 1 : 5 ) , temp_retval( : , 1 : 5 ) , 'rows' ) ;
 TF_ix = find( TF ) ; new_retval( TF_ix , ii + 5 ) = temp_retval( : , 6 ) ; 
 retval = new_retval ; 
 clear new_retval new_dn dn_temp_retval dn_retval date_time_diffs_1 date_time_diffs_2 ;
 
 else 
 error( 'Mismatch between dates and times for writing to retval.' ) ; 
 endif ## TF == S_IDX check

endif ## end of strcmp if

endfor ## ii loop

endfunction
At the moment there are almost 50 separate API calls nested within a loop, which of course is non-vectorised and inefficient, and if I find out how to make a batch API call to do this I shall rewrite the function.

This function is called in a script which uses the output matrix "retval" to then calculate the various currency strengths as outlined in the above linked posts. The total running time for this script is approximately 40 seconds from first call to appending to the index file on disk. I wrote this function to leverage my new found Oanda API knowledge to avoid having to accumulate an ever growing set of files on disk containing the raw 10 minute data.

I hope readers find this useful.

Wednesday, 20 May 2020

An Improved Volume Profile Chart with Levels

Without much ado, here is the code
## Copyright (C) 2020 dekalog
## 
## This program is free software: you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
## 
## This program is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
## 
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see
## .

## -*- texinfo -*- 
## @deftypefn {} {@var{retval} =} market_profile_plot (@var{cross}, @var{n_bars})
##
## Plot a Market Profile Chart of CROSS of the last N_BARS.
##
## @seealso{}
## @end deftypefn

## Author: dekalog 
## Created: 2020-05-11

function market_profile_plot( curr_cross , n_days )

pkg load statistics ; 
cd /path/to/data/folder ;
price_name = tolower( curr_cross ) ;

if ( strcmp( price_name , 'aud_jpy' ) || strcmp( price_name , 'eur_jpy' ) || strcmp( price_name , 'gbp_jpy' ) || ...
     strcmp( price_name , 'usd_jpy' ) )
 tick_size = 0.001 ;
 round_digit = 3 ;
elseif ( strcmp( price_name , 'xau_usd' ) )
 tick_size = 0.1 ;
 round_digit = 1 ;
elseif ( strcmp( price_name , 'xag_usd' ) )
 tick_size = 0.01 ;
 round_digit = 2 ; 
else
 tick_size = 0.0001 ;
 round_digit = 4 ;
endif

## get price data of *_ohlc_10m
unix_command = [ "wc" , " " , "-l" , " " , [ price_name , '_ohlc_10m' ] ] ;
## the 'wc' with '-l' flag command counts the number of lines in [ price_name , '_ohlc_20m' ] } 
[ ~ , system_out ] = system( unix_command ) ;
cstr = strsplit( system_out , " " ) ; 
lines_in_file = str2double( cstr( 1 , 1 ) ) ;

## read *_ohlc_10m file
price_data = dlmread( [ price_name , '_ohlc_10m' ] , ',' , [ lines_in_file - ( n_days * 144 + 18 ) , 0 , lines_in_file , 21 ] ) ;
## get the earliest London open on a Sunday, if any
sun_open_ix = find( ( price_data( : , 11 ) == 1 ) .* ( price_data( : , 9 ) == 22 ) .* ( price_data( : , 10 ) == 0 ) ) ;
## get weekday closes
end_ix = find( ( price_data( : , 15 ) == 16 ) .* ( price_data( : , 16 ) == 50  ) ) ;
delete_ix = unique( [ sun_open_ix ; end_ix ] ) ;
## delete uuwanted data
price_data( 1 : delete_ix( 1 ) , : ) = [] ; end_ix = end_ix .- delete_ix( 1 ) ; open_ix = end_ix .+ 1 ; 
end_ix( end_ix == 0 ) = [] ; end_ix( end_ix > size( price_data , 1 ) ) = [] ;
open_ix( open_ix == 0 ) = [] ; open_ix( open_ix > size( price_data , 1 ) ) = [] ;

## give names to data
open = price_data(:,18) ; high = price_data(:,19) ; low = price_data(:,20) ; close = price_data(:,21) ; vol = price_data(:,22) ;
high_round = floor( high ./ tick_size .+ 0.5 ) .* tick_size ;
low_round = floor( low ./ tick_size .+ 0.5 ) .* tick_size ;
max_tick_range = max( high_round .- low_round ) / tick_size ;
upper_val = high ; lower_val = low ;

## create y and x axes for chart
y_max = max( high_round ) + max_tick_range * tick_size ;
y_min = min( low_round ) - max_tick_range * tick_size ;
y_ax = ( y_min : tick_size : y_max )' ;
end_x_ax_freespace = 5 ;

## create container
all_vp = zeros( n_days , numel( y_ax ) ) ; all_mp = all_vp ;

if ( n_days == 1 )

[ all_vp(1,:) , vp_val ] = pcolor_background( y_ax , high , low , vol , tick_size ) ;
vp_z = repmat( all_vp( 1 , : ) , numel( high ) + end_x_ax_freespace , 1 ) ;
lower_val( : ) = vp_val( 1 ) ; upper_val( : ) = vp_val( 2 ) ; 

elseif ( n_days >= 2 )

vp_z = zeros( numel( high ) + end_x_ax_freespace , size( all_vp , 2 ) ) ;

 for ii = 1 : numel( end_ix ) 
 [ all_vp(ii,:) , vp_val ] = pcolor_background( y_ax , high(open_ix(ii):end_ix(ii)) , low(open_ix(ii):end_ix(ii)) , ...
                                                        vol(open_ix(ii):end_ix(ii)) , tick_size ) ;
 vp_z(open_ix(ii):end_ix(ii),:) = repmat( all_vp(ii,:)./max(all_vp(ii,:)) , numel( high(open_ix(ii):end_ix(ii)) ) , 1 ) ;
 lower_val( open_ix(ii) : end_ix(ii) ) = vp_val( 1 ) ; upper_val( open_ix(ii) : end_ix(ii) ) = vp_val( 2 ) ;
 endfor

[ all_vp(end,:) , vp_val ] = pcolor_background( y_ax , high(open_ix(end):end) , low(open_ix(end):end) , ...
                                                         vol(open_ix(end):end) , tick_size ) ;
vp_z( open_ix( end ) : end , : ) = repmat( all_vp( end , : ) ./ max( all_vp( end , : ) ) , ...
                                            numel( high( open_ix( end ) : end ) ) + end_x_ax_freespace , 1 ) ;
lower_val( open_ix( end ) : end ) = vp_val( 1 ) ; upper_val( open_ix( end ) : end ) = vp_val( 2 ) ;
endif

## create the background ( best choices - viridis and ocean? )
x_ax = ( 1 : 1 : numel( open ) + end_x_ax_freespace )' ;
colormap( 'viridis' ) ; figure( 10 ) ; pcolor( x_ax , y_ax , vp_z' ) ; shading interp ; axis tight ;

## plot the individual volume profiles
hold on ;

scale_factor = ( 1 / max(max(all_vp) ) ) * 72 ;
for ii = 1 : numel( open_ix )
figure( 10 ) ; fill( all_vp( ii , : ) .* scale_factor .+ open_ix( ii ) , y_ax' , [99;99;99]./255 ) ;
endfor

## plot candlesticks
figure( 10 ) ; candle_mp( high , low , close , open ) ;

## plot upper and lower boundaries of value area
hold on ; figure( 10 ) ; plot( lower_val , 'b' , 'linewidth' , 2 , upper_val , 'r' , 'linewidth' , 2 ) ; hold off ;

## Plot vertical lines for London open at 7am
london_ix = find( ( price_data( : , 9 ) == 7 ) .* ( price_data( : , 10 ) == 0 ) ) ;
if ( ~isempty( london_ix ) )
 for ii = 1 : numel( london_ix )
  figure( 10 ) ; vline( london_ix( ii ) , 'g' ) ;
 endfor
endif

endfunction
which calls this
## Copyright (C) 2020 dekalog
## 
## This program is free software: you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
## 
## This program is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
## 
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see
## .

## -*- texinfo -*- 
## @deftypefn {} {@var{vp_z}, @var{vp_val} =} pcolor_background (@var{y_ax}, @var{high}, @var{low}, @var{vol}, @var{tick_size})
##
## @seealso{}
## @end deftypefn

## Author: dekalog 
## Created: 2020-05-13

function [ vp_z , vp_val ] = pcolor_background ( y_ax , high , low , vol , tick_size )

vp_z = zeros( 1 , numel( y_ax ) ) ; ##tpo_z = vp_z ;
vol( vol <= 1 ) = 2 ; ## no single point vol distributions
vp_val = zeros( 2 , 1 ) ;

 for ii = 1 : numel( high )

 ## the volume profile, vp_z
 ticks = norminv( linspace(0,1,vol(ii)+2) , (high(ii) + low(ii))/2 , (high(ii) - low(ii))*0.25 ) ;
 ticks = floor( ticks( 2 : end - 1 ) ./ tick_size .+ 0.5 ) .* tick_size ;
 unique_ticks = unique( ticks ) ;

  if ( numel( unique_ticks ) > 1 )
  [ N , X ] = hist( ticks , unique( ticks ) ) ;
  [ ~ , N_ix ] = max( N ) ; tick_ix = X( N_ix ) ;
  [ ~ , centre_tick ] = min( abs( y_ax .- tick_ix ) ) ;
  vp_z(1,centre_tick-N_ix+1:centre_tick+(numel(N)-N_ix)) = vp_z(1,centre_tick-N_ix+1:centre_tick+(numel(N)-N_ix)).+ N ;
  elseif ( numel( unique_ticks ) == 1 )
  [ ~ , centre_tick ] = min( abs( y_ax .- unique_ticks ) ) ;
  vp_z( 1 , centre_tick ) = vp_z( 1 , centre_tick ) + vol( ii ) ;
  endif

 endfor

[ ~ , vp_val_centre_ix ] = max( vp_z ) ;
sum_vp_cutoff = 0.7 * sum( vp_z ) ;
count = 1 ;

while ( count ~= 0 )
 
 sum_vp_z = sum( vp_z( max( vp_val_centre_ix - count , 1 ) : min( vp_val_centre_ix + count , numel( vp_z ) ) ) ) ;
 if ( sum_vp_z >= sum_vp_cutoff )
  vp_val( 1 , 1 ) = y_ax( max( vp_val_centre_ix - count , 1 ) ) ;             ## lower
  vp_val( 2 , 1 ) = y_ax( min( vp_val_centre_ix + count , numel( vp_z ) ) ) ; ## upper
  count = 0 ;
 else
  count = count + 1 ;
 endif

 endwhile

endfunction
and this
function hhh=vline(x,in1,in2)
% function h=vline(x, linetype, label)
% 
% Draws a vertical line on the current axes at the location specified by 'x'.  Optional arguments are
% 'linetype' (default is 'r:') and 'label', which applies a text label to the graph near the line.  The
% label appears in the same color as the line.
%
% The line is held on the current axes, and after plotting the line, the function returns the axes to
% its prior hold state.
%
% The HandleVisibility property of the line object is set to "off", so not only does it not appear on
% legends, but it is not findable by using findobj.  Specifying an output argument causes the function to
% return a handle to the line, so it can be manipulated or deleted.  Also, the HandleVisibility can be 
% overridden by setting the root's ShowHiddenHandles property to on.
%
% h = vline(42,'g','The Answer')
%
% returns a handle to a green vertical line on the current axes at x=42, and creates a text object on
% the current axes, close to the line, which reads "The Answer".
%
% vline also supports vector inputs to draw multiple lines at once.  For example,
%
% vline([4 8 12],{'g','r','b'},{'l1','lab2','LABELC'})
%
% draws three lines with the appropriate labels and colors.
% 
% By Brandon Kuczenski for Kensington Labs.
% brandon_kuczenski@kensingtonlabs.com
% 8 November 2001
if length(x)>1  % vector input
    for I=1:length(x)
        switch nargin
        case 1
            linetype='r:';
            label='';
        case 2
            if ~iscell(in1)
                in1={in1};
            end
            if I>length(in1)
                linetype=in1{end};
            else
                linetype=in1{I};
            end
            label='';
        case 3
            if ~iscell(in1)
                in1={in1};
            end
            if ~iscell(in2)
                in2={in2};
            end
            if I>length(in1)
                linetype=in1{end};
            else
                linetype=in1{I};
            end
            if I>length(in2)
                label=in2{end};
            else
                label=in2{I};
            end
        end
        h(I)=vline(x(I),linetype,label);
    end
else
    switch nargin
    case 1
        linetype='r:';
        label='';
    case 2
        linetype=in1;
        label='';
    case 3
        linetype=in1;
        label=in2;
    end
    
    
    
    g=ishold(gca);
    hold on
    y=get(gca,'ylim');
    h=plot([x x],y,linetype);
    if length(label)
        xx=get(gca,'xlim');
        xrange=xx(2)-xx(1);
        xunit=(x-xx(1))/xrange;
        if xunit<0 .8="" code="" color="" else="" end="" g="=0" get="" h="" handlevisibility="" hhh="h;" hold="" if="" label="" nargout="" off="" set="" tag="" text="" vline="" x-.05="" x="" xrange="" y="">
and produces charts such as this,
which is a 10 minute ohlc chart of the last 3 days, including "today's" ongoing price action. The number of days is a function input, and the horizontal blue and red lines indicate the upper and lower extremes of the value area. The vertical green lines indicate the London opening bar (7am BST) and each set of levels ends at the New York closing bar (5pm EST).

Further examples are last 10 days
and last month
Enjoy!


Monday, 18 May 2020

A Volume Profile With Levels Chart

Just a quick post to illustrate the latest of my ongoing chart iterations which combines a levels chart, as I have recently been posting about, but with the addition of a refined methodology of creating the horizontal histograms to more clearly represent the volumes over distinct periods.
The main change is to replace the use of the Octave barh function with the fill function. A minimal working example of this plotting is given in the code box below.
## get price data of *_ohlc_10m
unix_command = [ "wc" , " " , "-l" , " " , [ price_name , '_ohlc_10m' ] ] ;
## the 'wc' with '-l' flag command counts the number of lines in [ price_name , '_ohlc_20m' ] } 
[ ~ , system_out ] = system( unix_command ) ;
cstr = strsplit( system_out , " " ) ; 
lines_in_file = str2double( cstr( 1 , 1 ) ) ;

## read *_ohlc_10m file
price_data = dlmread( [ price_name , '_ohlc_10m' ] , ',' , [ lines_in_file - n_bars , 0 , lines_in_file , 21 ] ) ;
open = price_data(:,18) ; high = price_data(:,19) ; low = price_data(:,20) ; close = price_data(:,21) ; vol = price_data(:,22) ;
high_round = floor( high ./ tick_size .+ 0.5 ) .* tick_size ;
low_round = floor( low ./ tick_size .+ 0.5 ) .* tick_size ;
max_tick_range = max( high_round .- low_round ) / tick_size ;

## create y and x axes for chart
y_max = max( high_round ) + max_tick_range * tick_size ;
y_min = min( low_round ) - max_tick_range * tick_size ;
y_ax = ( y_min : tick_size : y_max )' ;
end_x_ax_freespace = 5 ;

all_vp = zeros( 3 , numel( y_ax ) ) ;

all_vp( 1 , : ) = pcolor_background( y_ax , high(1:50) , low(1:50) , vol(1:50) , tick_size ) ;
all_vp( 2 , : ) = pcolor_background( y_ax , high(51:100) , low(51:100) , vol(51:100) , tick_size ) ;
all_vp( 3 , : ) = pcolor_background( y_ax , high(100:150) , low(100:150) , vol(100:150) , tick_size ) ;

vp_z = repmat( sum( all_vp , 1 ) , numel( high ) + end_x_ax_freespace , 1 ) ;

x_ax = ( 1 : 1 : numel( open ) + end_x_ax_freespace )' ;
colormap( 'viridis' ) ; figure( 20 ) ; pcolor( x_ax , y_ax , vp_z' ) ; shading interp ; axis tight ;

## plot the individual volume profiles
hold on ;
scale_factor = 0.18 ; 
fill( all_vp( 1 , : ) .* scale_factor , y_ax' , [99;99;99]./255 ) ; 
fill( all_vp( 2 , : ) .* scale_factor .+ 50 , y_ax' , [99;99;99]./255 ) ;
fill( all_vp( 3 , : ) .* scale_factor .+ 100 , y_ax' , [99;99;99]./255 ) ;

## plot candlesticks
candle_mp( high , low , close , open ) ;
hold off;
I hope readers find this new way of plotting profile charts useful - I certainly am pretty pleased with it.