# How to generate a random price series with a specified range and correlation with an actual price?

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.

-
How can a time series be both random and correlated? You'd be better off just using historical data. –  chrisaycock Nov 28 '11 at 18:37
I see this as a distinct question as you are looking to calibrate the model with a specific Information Coefficient. –  Quant Guy Nov 28 '11 at 22:27
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. –  Tal Fishman Dec 5 '11 at 20:45
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. –  justin-- Nov 1 '12 at 4:07

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.

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") ;
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' ) ;


-
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. –  Tal Fishman Jan 3 '12 at 15:29
@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. –  Hansen Dec 21 '13 at 17:20

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

-

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".

-
None of these methods show to put the other restriction. The price series should be within a range. –  Suminda Sirinath Salpitikorala Nov 29 '11 at 9:14
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 –  Bob Jansen Nov 29 '11 at 12:35
These discards will be computationally expensive. –  Suminda Sirinath Salpitikorala Nov 29 '11 at 15:52
Maybe not discard them but reflect at bounds? –  Alexey Kalmykov Dec 1 '11 at 15:20

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).

-
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. –  Suminda Sirinath Salpitikorala Nov 29 '11 at 16:00

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/

-
Is it possible for some one to post how exactly this is done. Also keeping in mind the range criteria. –  Suminda Sirinath Salpitikorala Nov 29 '11 at 18:44
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. –  babelproofreader Nov 29 '11 at 23:16
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. –  babelproofreader Nov 29 '11 at 23:21
Please let us know if you can work this out. Will be definitely interested to hear. –  Samik R Dec 6 '11 at 18:03
@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$. –  Hansen Dec 21 '13 at 16:02