18
$\begingroup$

I want to generate a mock price series. I want it to be within a certain range and have a defined correlation with the original price series.

If I choose, say, oil, I want as many time series which have some similar characteristics in terms of correlation but not exact. I do not want the data to wander off in a completely different path from the historic path taken. It is not necessarily co-integrated, correlation will suffice. I would also like these to adhere to the original price range. The series should be random, e.g. each new realization should take a different path.

$\endgroup$
4
  • $\begingroup$ How can a time series be both random and correlated? You'd be better off just using historical data. $\endgroup$ Commented Nov 28, 2011 at 18:37
  • $\begingroup$ I see this as a distinct question as you are looking to calibrate the model with a specific Information Coefficient. $\endgroup$ Commented Nov 28, 2011 at 22:27
  • $\begingroup$ I've edited a bit for clarity and incorporated some of the previous comments. I hope you find the answers given on this forum helpful and informative. $\endgroup$ Commented Dec 5, 2011 at 20:45
  • $\begingroup$ So you wanted to bootstrap your original price series... en.wikipedia.org/wiki/Bootstrapping_%28statistics%29 ...interesting that the answers do that in the frequency domain. $\endgroup$
    – justin--
    Commented Nov 1, 2012 at 4:07

6 Answers 6

1
$\begingroup$

Further to my comments to Samik R's answer above, here is the link to my blog post where my attempts to recreate the method linked to in said answer are given.

Edit to include more information per Tal Fishman's comment

Octave .oct function code

#include <octave/oct.h>
#include <octave/dColVector.h>
#include <octave/CNDArray.h>
#include "MersenneTwister.h"

DEFUN_DLD (permute_vector, args, , "Input is a vector that is to be permuted once")
{
octave_value_list retval;

int nargin = args.length () ;
int vec_length = args(0).length () ;

// check the input arguments
    if ( nargin > 1 )
    {
    error ("Invalid arguments. Input is a vector that is to be permuted once") ;
    return retval ;
    }

    if (vec_length < 2 )
    {
    error ("Invalid arguments. Input is a vector that is to be permuted once") ;
    return retval ;
    }

    if (error_state)
    {
    error ("Invalid arguments. Input is a vector that is to be permuted once") ;
    return retval ;
    }
 // end of input checking

 ComplexNDArray input_vector = args(0).complex_array_value () ;
 ComplexNDArray output_vector = args(0).complex_array_value () ;
 int k1;
 int k2;

 MTRand mtrand1; // Declare the Mersenne Twister Class - will seed from system time

       k1 = vec_length - 1; // initialise prior to shuffling the vector

       while (k1 > 0) // While at least 2 left to shuffle
         {          
         k2 = mtrand1.randInt( k1 ); // Pick an int from 0 through k1 

           if (k2 > k1) // check if random vector index no. k2 is > than max vector    index - should never happen 
              {
              k2 = k1 - 1; // But this is cheap insurance against disaster if it does happen
              }

         output_vector(k1) = input_vector(k2) ; // allocate random pick k2 from input_vector to the k1 vector index of output_vector
         input_vector(k2) = input_vector(k1) ; // replace random pick k2 content of input_vector with content k1 of input_vector
         k1 = k1 - 1; // count down 
         } // Shuffling is complete when this while loop exits

 retval(0) = output_vector ; 

return retval; // Return the output to Octave
} 

Octave script code

clear all

contract = input( "Enter contract symbol e.g. sp: ","s") ;
data = load("-ascii",contract) ;
n = rows(data)
index_begin = input( "Enter index_begin: ") ;
index_end = input( "Enter index_end, value not greater than n: ") ;

close = data(index_begin:index_end,7) ;

% detrend the close vector prior to applying the fft
slope = ( close(end) - close(1) ) / ( length(close) - 1 ) ;
v1 = (0:1:length(close)-1)' ;
detrended_close = close .- ( v1 .* slope ) ;
close_index_begin = close(1)
detrended_close_index_begin = detrended_close(1)
detrended_close_index_end = detrended_close(end)

% create zero padded vector for fft
L2 = 2^nextpow2( length(close) ) ; half_L2 = L2/2 ;
y2 = zeros( 1,L2 ) ; y2( 1:length(close) ) = detrended_close ;

% apply the fft
transform = fft( y2 ) ;

% permute the first half of the transform vector in "chunks" of 10
max_ii_value = floor( half_L2 / 10 ) ;
for ii = 1:max_ii_value
transform( (ii*10):(ii*10)+9 ) = permute_vector( transform( (ii*10):(ii*10)+9 ) ) ;
endfor

% take the inverse fft
ifft_vec = real( ifft( transform ) ) ;

% retrend the ifft_permuted_vec
retrended_ifft_vec = ifft_vec( 1:length(close) )' .+ ( v1 .* slope ) ;

% statistics
correl = corrcoef (close, retrended_ifft_vec)
spear = spearman (close, retrended_ifft_vec)
tau = kendall (close, retrended_ifft_vec)

plot( close,'b',retrended_ifft_vec,'r' ) ; legend( 'close','permute' ) ; 

More information and charts in my blog post.

$\endgroup$
2
  • 1
    $\begingroup$ Great blog post. I just wonder if you'd mind including an excerpt in case your blog ever moves (and the link rots). I also think a juicy excerpt will increase the rate of click-throughs to your blog, if that's something you value. $\endgroup$ Commented Jan 3, 2012 at 15:29
  • $\begingroup$ @TalFishman: babelproofreader has not provided a mathematical rationale for his method. In fact, not only the expected correlation coefficient between the original series and its frequency randomized series is $0$, but the correlation coefficient converges to $0$ in probability as the length of the time series approaches infinity. $\endgroup$
    – Hans
    Commented Dec 21, 2013 at 17:20
9
$\begingroup$

Two ways:

Model the returns using an Ornstein-Uhlenbeck process

You can control the variance of the residual noise in the process to your desired level of correlation. Conceptually you inject gaussian noise into the synthetic OU process to satisfy your requirement.

For example, let's say you have time-series A which is what you are modelling. Time-series B is your synthetic series. You model time-series A via the OU process. Now you have to parametrize the OU process. For example, you can choose how much variance to allow for in time-series B.

We know that the correlation is simply the covariance(a,b)/[ stdev(a) * stdev(b) ]. Therefore, we can solve for this equation in terms of variance(b) so you can plug in your IC and crank out the appropriate level of variance for time-series 'b' in the OU process.

The OU process will give you more flexibility but it is not as straightforward to setup.

Use the Fundamental Law of Active Management

BARRA performs a Monte Carlo for simulating alpha signals to a specific level of Information Coefficient using this technique. Simulating alpha signals with a specific correlation is the same idea as generating a time-series that correlates to a price. The difference is that you would have to convert your original price-series into a return series, apply the procedure to generate a correlated alpha signal, and then integrate the alpha series so it is in the form of price levels instead of returns.

You can find more about the approach here.

Update:

I have added some simple code to demonstrate injecting orthogonal gaussian noise so the resulting series has a specific level of correlation:

# rho = desired correlation
# signals = a matrix where columns are asset returns and rows are periods
# covlist = a list of covariance matrixs corresponding to a given row (i.e. period)
# See example below for illustration

 simpleNormalNoise <- function( signals , covlist , rho ) # one noisy signal per each mu and covariance matrix
    {
    noise   = sapply( 1:ncol( signals ) , function(x) { normals = rnorm( nrow( covlist[[x]])) ; return( normals * sqrt(diag( covlist[[x]] ))) } )
    signals = sapply( 1:ncol( signals ) , function(x) { return( rho*signals[,x] + sqrt( 1-rho*rho ) * noise[,x] ) } )
    return( signals ) 
    }

# Example:
sd1 = 1 # asset1
sd2 = 1 # asset2
cor = 0.8
cov = cor * sd1 * sd2
sample  = matrix( c( sd1^2 , cov , cov , sd2^2 ) , 2 , 2 )
covs    = lapply( 1:1000000 , function(x) { return( sample ) } )
fakemus = sapply( 1:1000000 , function(x) { return( rnorm( 2 ) ) } )

signals = simpleNormalNoise( fakemus, covs, .10 )
sd(t(signals)) #should be ~1
cor(t(signals)) #shoudl be ~0
cor(fakemus[1,],signals[1,]) #~.1
cor(fakemus[2,],signals[2,]) #~.1
mean(signals[1,]) #~ .05*.1
mean(signals[2,]) #~ .08*.1
$\endgroup$
4
$\begingroup$

Wilmott Forums - "How can I simulate correlated random numbers?"

Generating correlated normal variates

Random Correlated Series Generator (using R)

All found with a Google search for "how to generate random correlated series".

$\endgroup$
4
  • $\begingroup$ None of these methods show to put the other restriction. The price series should be within a range. $\endgroup$ Commented Nov 29, 2011 at 9:14
  • $\begingroup$ Apparently this bears repeating: "If you want to make the returns "random", then you will have to generate the whole price paths that meet your correlation criteria and then discard results that don't fit your price criteria." - ricardh $\endgroup$
    – Bob Jansen
    Commented Nov 29, 2011 at 12:35
  • $\begingroup$ These discards will be computationally expensive. $\endgroup$ Commented Nov 29, 2011 at 15:52
  • $\begingroup$ Maybe not discard them but reflect at bounds? $\endgroup$ Commented Dec 1, 2011 at 15:20
3
$\begingroup$

You could create a rescaled stochastic indicator from your randomly generated, correlated series.

1) use whatever software/methodology you want to create your random series with 0.85 correlation to the original data.

2) find the maximum and minimum values of this new series and rescale the series to range between 0 and 1 using this formula; (series_value - min_series_value) / (max_series_value - min_series_value)

3) decide on the range of your restriction e.g. prices range between 35 and 85 for a range of 50, so multiply each value from step 2) by 50 to transform the 0 to 1 range to a 0 to 50 range.

4) add the minimum value of 35 to values from step 3) to shift the series upwards so that your final series has a minimum value of 35, a maximum value of 85 and within the range 35 to 85 your series is essentially the random series generated in step 1).

$\endgroup$
1
  • $\begingroup$ This seams to be fine but I am wondering if there is a way to place bounds on a stochastic series in a more elegant manner. Especially if we do not wait to generate the whole series before using it. In which case we have to use a another form where we contain the stochastic process. $\endgroup$ Commented Nov 29, 2011 at 16:00
2
$\begingroup$

I have seen a technique which uses frequency domain and does pretty much what (I think) you are trying to do. The author does not give the complete details, so you might have to contact him for that, or take a look at the (free) software he has developed. Link here: http://blog.quantumfading.com/2009/08/24/historical-data-randomization-using-the-frequency-domain-preview/

$\endgroup$
5
  • $\begingroup$ Is it possible for some one to post how exactly this is done. Also keeping in mind the range criteria. $\endgroup$ Commented Nov 29, 2011 at 18:44
  • 1
    $\begingroup$ Interesting link! I theorise that one way it could be done is to apply a Fast Fourier Transform (e.g. FFT function in Octave/MATLAB) to the original series, then divide the resulting vector into segments and within each separate segment randomly permute the values, and finally reconstruct the time series by applying the Inverse Fast Fourier Transform (IFFT function). By randomly permuting the values you will not alter the amplitudes of the composite sine waves but will slightly alter their periodicity - this will naturally tend to restrict the range of the new series to that of the original. $\endgroup$ Commented Nov 29, 2011 at 23:16
  • 1
    $\begingroup$ This is such an intriguing approach that I may "knock up" some Octave code on my blog and post a link to it from here. $\endgroup$ Commented Nov 29, 2011 at 23:21
  • $\begingroup$ Please let us know if you can work this out. Will be definitely interested to hear. $\endgroup$
    – Samik R
    Commented Dec 6, 2011 at 18:03
  • $\begingroup$ @babelproofreader, Suminda Sirinath Salpitikorala and Samik R: I do not know how exactly the QuantumFading author does the randomization, however, the way babelproofreader does it dos not generate the required time series. babelproofreader has not provided a mathematical rationale for his method. In fact, the expected covariance between the original series and its frequency randomized series is $0$. $\endgroup$
    – Hans
    Commented Dec 21, 2013 at 16:02
1
$\begingroup$

If you want to make the returns "random", then you will have to generate the whole price paths that meet your correlation criteria and then discard results that don't fit your price criteria. This isn't random. If I know how prices evolve, then I can generate a killer trading rule.

I suggest using historical data. These data are easy to obtain with the quantmod package in R.

$\endgroup$
3
  • $\begingroup$ Historic data will give me only on path! $\endgroup$ Commented Nov 29, 2011 at 15:53
  • $\begingroup$ How? Try AAPL and MSFT. There are about 5000 more. $\endgroup$ Commented Nov 29, 2011 at 16:13
  • $\begingroup$ If I choose AAPL I want as many time series which have some similar characteristics in terms of correlation but not exact. Also adhere to the price range also. The series should be random as in each new realisation should take a different path. $\endgroup$ Commented Nov 29, 2011 at 18:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.