Reputation: 66320
I am creating a stock trading simulator where the last days's trade price is taken as opening price and simulated through out the current day.
For that I am generating random double numbers that may be somewhere -5% of lastTradePrice and 5% above the lastTradePrice. However after around 240 iterations I see how the produced double number gets smaller and smaller closing to zero.
Random rand = new Random();
Thread.Sleep(rand.Next(0,10));
Random random = new Random();
double lastTradeMinus5p = model.LastTradePrice - model.LastTradePrice * 0.05;
double lastTradePlus5p = model.LastTradePrice + model.LastTradePrice * 0.05;
model.LastTradePrice = random.NextDouble() * (lastTradePlus5p - lastTradeMinus5p) + lastTradeMinus5p;
As you can see I am trying to get random seed by utilising Thread.sleep()
. And yet its not truly randomised. Why is there this tendency to always produce smaller numbers?
Update:
The math itself is actually fine, despite the downwards trend as Jon has proven it. Getting random double numbers between range is also explained here.
The real problem was the seed of Random
. I have followed Jon's advice to keep the same Random
instance across the thread for all three prices. And this already is producing better results; the price is actually bouncing back upwards. I am still investigating and open to suggestions how to improve this. The link Jon has given provides an excellent article how to produce a random instance per thread.
Btw the whole project is open source if you are interested. (Using WCF, WPF in Browser, PRISM 4.2, .NET 4.5 Stack)
The TransformPrices
call is happening here on one separate thread.
This is what happens if I keep the same instance of random:
And this is generated via RandomProvider.GetThreadRandom();
as pointed out in the article:
Upvotes: 9
Views: 660
Reputation: 1500595
Firstly, calling Thread.Sleep
like this is not a good way of getting a different seed. It would be better to use a single instance of Random
per thread. See my article on randomness for some suggested approaches.
However, your code is also inherently biased downwards. Suppose we "randomly" get 0.0 and 1.0 from the random number generator, starting with a price of $100. That will give:
Now we can equally randomly get 1.0 and 0.0:
Note how we've got down in both cases, despite this being "fair". If the value increases, that means it can go down further on the next roll of the dice, so to speak... but if the value decreases, it can't bounce back as far.
EDIT: To give an idea of how a "reasonably fair" RNG is still likely to give a decreasing value, here's a little console app:
using System;
class Test
{
static void Main()
{
Random random = new Random();
int under100 = 0;
for (int i = 0; i < 100; i++)
{
double price = 100;
double sum = 0;
for (int j = 0; j < 1000; j++)
{
double lowerBound = price * 0.95;
double upperBound = price * 1.05;
double sample = random.NextDouble();
sum += sample;
price = sample * (upperBound - lowerBound) + lowerBound;
}
Console.WriteLine("Average: {0:f2} Price: {1:f2}", sum / 1000, price);
if (price < 100)
{
under100++;
}
}
Console.WriteLine("Samples with a final price < 100: {0}", under100);
}
}
On my machine, the "average" value is always very close to 0.5 (rarely less then 0.48 or more than 0.52) but the majority of "final prices" are always below 100 - about 65-70% of them.
Upvotes: 11
Reputation: 3950
According to your code, I can derive it in a simpler version as below:
Random rand = new Random();
Thread.Sleep(rand.Next(0,10));
Random random = new Random();
double lastTradeMinus5p = model.LastTradePrice * 0.95; // model.LastTradePrice - model.LastTradePrice * 0.05 => model.LastTradePrice * ( 1 - 0.05 )
double lastTradePlus5p = model.LastTradePrice * 1.05; // model.LastTradePrice + model.LastTradePrice * 0.05 => model.LastTradePrice * ( 1 + 0.05 )
model.LastTradePrice = model.LastTradePrice * ( random.NextDouble() * 0.1 + 0.95 ) // lastTradePlus5p - lastTradeMinus5p => ( model.LastTradePrice * 1.05 ) - ( model.LastTradePrice * 0.95 ) => model.LastTradePrice * ( 1.05 - 0.95)
So you are taking model.LastTradePrice
times a fractional number(between 0 to 1) times 0.1 which will always decrease more to zero
, but increase less to 1
!
The litle fraction positive part comes because of the + 0.95
part with the zero-tending
random.NextDouble() * 0.1
Upvotes: 0
Reputation: 5144
Your math should be:
double lastTradeMinus5p = model.LastTradePrice * 0.95;
double lastTradePlus5p = model.LastTradePrice * (1/0.95);
UPDATE: As Dialecticus pointed out, you should probably use some other distribution than this one:
random.NextDouble() * (lastTradePlus5p - lastTradeMinus5p)
Also, your range of 5% seems pretty narrow to me.
Upvotes: 1
Reputation: 19111
Quick guess: This is a math-thing, and not really related to the random generator.
When you reduce the trade price by 5%, you get a resulting value that is lower than that which you began with (obviously!).
The problem is that when you then increase the trade price by 5% of that new value, those 5% will be a smaller value than the 5% you reduced by previously, since you started out with a smaller value this time. Get it?
I obviously haven't verified this, but I have strong hunch this is your problem. When you repeat these operations a bunch of times, the effect will get noticeable over time.
Upvotes: 1
Reputation: 9725
I think this is mainly because the random number generator you are using is technically pants.
For better 'randomness' use RNGCryptoServiceProvider to generate the random numbers instead. It's technically a pseudo-random number generated, but the quality of 'randomness' is much higher (suitable for cryptographic purposes).
//The following sample uses the Cryptography class to simulate the roll of a dice.
using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
class RNGCSP
{
private static RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider();
// Main method.
public static void Main()
{
const int totalRolls = 25000;
int[] results = new int[6];
// Roll the dice 25000 times and display
// the results to the console.
for (int x = 0; x < totalRolls; x++)
{
byte roll = RollDice((byte)results.Length);
results[roll - 1]++;
}
for (int i = 0; i < results.Length; ++i)
{
Console.WriteLine("{0}: {1} ({2:p1})", i + 1, results[i], (double)results[i] / (double)totalRolls);
}
rngCsp.Dispose();
Console.ReadLine();
}
// This method simulates a roll of the dice. The input parameter is the
// number of sides of the dice.
public static byte RollDice(byte numberSides)
{
if (numberSides <= 0)
throw new ArgumentOutOfRangeException("numberSides");
// Create a byte array to hold the random value.
byte[] randomNumber = new byte[1];
do
{
// Fill the array with a random value.
rngCsp.GetBytes(randomNumber);
}
while (!IsFairRoll(randomNumber[0], numberSides));
// Return the random number mod the number
// of sides. The possible values are zero-
// based, so we add one.
return (byte)((randomNumber[0] % numberSides) + 1);
}
private static bool IsFairRoll(byte roll, byte numSides)
{
// There are MaxValue / numSides full sets of numbers that can come up
// in a single byte. For instance, if we have a 6 sided die, there are
// 42 full sets of 1-6 that come up. The 43rd set is incomplete.
int fullSetsOfValues = Byte.MaxValue / numSides;
// If the roll is within this range of fair values, then we let it continue.
// In the 6 sided die case, a roll between 0 and 251 is allowed. (We use
// < rather than <= since the = portion allows through an extra 0 value).
// 252 through 255 would provide an extra 0, 1, 2, 3 so they are not fair
// to use.
return roll < numSides * fullSetsOfValues;
}
}
Upvotes: 0