Generating Random Price Paths with Target Correlation and Range
Summary
The document collects approaches to generating synthetic price series that remain within a chosen range while exhibiting a specified, but imperfect, relationship to historical prices. One answer proposes modeling returns with an Ornstein–Uhlenbeck process and tuning residual noise to control correlation. Another suggests generating a signal with a target information coefficient by adding orthogonal Gaussian noise, then integrating returns to obtain price levels. A further proposal rescales a correlated series to a fixed minimum and maximum. A frequency-domain randomization approach is also mentioned, with implementation details deferred to an external source.
These suggestions differ in what they preserve: correlation, stochastic structure, or observed range. The document gives no comparative validation, and the rescaling step can alter distributional properties. A response cautions that selecting paths to satisfy both correlation and range constraints may compromise randomness, and suggests historical data as an alternative. The methods therefore need checks against the specific properties required for simulation; meeting a target correlation and range alone does not establish realistic market behavior.
Key ideas
- An Ornstein–Uhlenbeck return model with adjusted noise is proposed as one way to control correlation.
- Orthogonal Gaussian noise can be combined with an existing signal to target a chosen correlation.
- Rescaling a generated path can impose a minimum and maximum, but it may change other statistical properties.
- Frequency-domain randomization is mentioned as another approach, though the document does not fully explain it.
- Target correlation and range do not by themselves ensure realistic synthetic price behavior.
Tags
Full text
# How to generate a random price series with a specified range and correlation with an actual price?
# 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.
## Answer by babelproofreader (score 1, accepted)
https://quant.stackexchange.com/a/2701
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.
## Answer by Ram Ahluwalia (score 9)
https://quant.stackexchange.com/a/2469
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
```
## Answer by Joshua Chance (score 4)
https://quant.stackexchange.com/a/2457
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".
## Answer by babelproofreader (score 3)
https://quant.stackexchange.com/a/2462
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).
## Answer by Samik R (score 2)
https://quant.stackexchange.com/a/2467
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/
## Answer by Richard Herron (score 1)
https://quant.stackexchange.com/a/2456
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.Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.