【问题标题】:Multiple iterations of random double numbers tend to get smaller随机双数的多次迭代往往会变小
【发布时间】:2023-03-27 12:41:01
【问题描述】:

我正在创建一个股票交易模拟器,其中将最后一天的交易价格作为开盘价并在当天进行模拟。

为此,我正在生成可能是 lastTradePrice 的 -5% 和 lastTradePrice 以上 5% 的随机双精度数。然而,在大约 240 次迭代之后,我看到生成的双精度数如何变得越来越小,接近于零。

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;

如您所见,我正在尝试通过使用Thread.sleep() 来获取随机种子。然而,它并没有真正随机化。为什么会出现这种总是产生较小数字的趋势?

更新:

尽管乔恩已经证明了下降趋势,但数学本身实际上很好。 here 也解释了在范围之间获取随机双数。

真正的问题是Random 的种子。我已按照 Jon 的建议在线程中为所有三个价格保持相同的 Random 实例。这已经产生了更好的结果;价格实际上正在向上反弹。我仍在调查并接受如何改进这一点的建议。 Jon 提供的链接提供了一篇很棒的文章,如何为每个线程生成一个随机实例。

顺便说一句,如果您有兴趣,整个project开源。 (在浏览器中使用 WCF、WPF、PRISM 4.2、.NET 4.5 堆栈)

TransformPrices 调用发生在一个单独的线程上 here

如果我保持相同的随机实例会发生这种情况:

正如文章中所指出的,这是通过RandomProvider.GetThreadRandom(); 生成的:

【问题讨论】:

  • 这是在循环内吗?请尝试使用相同的Random 实例,不要每次都重新创建。
  • 我不完全确定Thread.Sleep(rand.Next(0,10)); 的确切点。另外能否请您确认一下这张图显示的是什么:random 的演变或model.LastTradePrice 的演变?
  • model.LastTradePrice 是双份吗?
  • 你的数学产生了这个结果,而不是随机数生成器。
  • 如果增值税为 25%,我以 100 美元购买了一个苹果,我支付了多少增值税(美元)?

标签: c# .net random double


【解决方案1】:

首先,像这样调用Thread.Sleep 不是获得不同种子的好方法。每个线程最好使用Random 的单个实例。请参阅我的article on randomness 了解一些建议的方法。

但是,您的代码也天生就向下倾斜。假设我们“随机”从随机数生成器中得到 0.0 和 1.0,价格为 100 美元。这将给出:

  • 第 0 天:100 美元
  • 第 1 天:95 美元(-5% = 5 美元)
  • 第 2 天:99.75 美元(+5% = 4.75 美元)

现在我们可以同样随机地得到 1.0 和 0.0:

  • 第 0 天:100 美元
  • 第 1 天:105 美元(+5% = 5 美元)
  • 第 2 天:99.75 美元(-5% = 5.25 美元)

请注意我们在这两种情况下的表现如何,尽管这是“公平的”。如果值增加,这意味着它可以在下一次掷骰子时进一步下降,可以这么说......但如果值减少,它就不能反弹到那么远。

编辑:为了说明“合理公平”的 RNG 仍然可能会降低价值,这里有一个小控制台应用程序:

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);
    }
}

在我的机器上,“平均”值总是非常接近 0.5(很少低于 0.48 或高于 0.52),但大多数“最终价格”总是低于 100 - 其中大约 65-70%。

【讨论】:

  • 感谢您的解释。虽然我同意数学可能有问题,但您在代码中保留一个随机实例的建议对我有很大帮助。请参阅更新的问题。 (顺便说一句,您的链接不起作用)仅供参考,我将项目作为开源项目,如果您热衷于查看该方法的调用方式,也许您对如何改进数学有更好的想法:github.com/houmie/StockTrading/blob/master/Service/…我总是开放建议和改进。干杯
  • @Hooman:现在修复了链接。 (它只是翻了一番。)如果您最终可能有多个线程同时调用该方法,那么它们仍然可能以相同的种子结束。按照固定链接查看如何避免这种情况。
  • 感谢这篇文章很棒。 RandomProvider.GetThreadRandom(); 非常有用,我现在正在使用它。我已经更新了问题中的结果。因此,即使数学显示呈下降趋势,它也不一定会下降,正如两个新的屏幕截图所证明的那样。例如在您的示例中,如果您获得两次1.0,那么您最终会在第 2 天获得 110.25。您同意吗?谢谢
  • @Hooman:是的,如果你得到 1.0 两次,你得到 110.25。但关键是,使用 fair 随机数生成器,由于我的回答中显示的原因,我希望它仍然会下降。很容易检查 - 调用 NextDouble() 并将结果存储在 double 中,因此您可以将其用于现有计算并且您可以对所有值求和。我怀疑在 240 次迭代后,这些值的总和约为 120。如果您保持相同的逻辑,我对您的新图表感到惊讶。我会自己调查的:)
  • @Hooman:请参阅我对控制台应用程序的编辑,这表明我的推理“使用此数学会趋于下降,即使 RNG 是公平的”似乎得到了证实。
【解决方案2】:

快速猜测:这是一个数学问题,与随机生成器没有真正的关系。

当您将交易价格降低 5% 时,您得到的结果值低于您开始时的值(显然!)。

问题在于,当您随后将交易价格增加 值的 5% 时,这 5% 的值将小于您之前减少的 5%,因为您刚开始这次的值较小。明白了吗?

我显然没有验证这一点,但我有强烈的预感这是你的问题。当您多次重复这些操作时,效果会随着时间的推移而变得明显。

【讨论】:

    【解决方案3】:

    你的数学应该是:

    double lastTradeMinus5p = model.LastTradePrice * 0.95;
    double lastTradePlus5p = model.LastTradePrice * (1/0.95);
    

    更新:正如 Dialecticus 指出的那样,您可能应该使用除此之外的其他发行版:

    random.NextDouble() * (lastTradePlus5p - lastTradeMinus5p)
    

    另外,5% 的范围对我来说似乎很窄。

    【讨论】:

    • 不错,但还不够。这两个限制之间的随机值应该不是均匀分布的,它们目前的表达式为random.NextDouble() * (lastTradePlus5p - lastTradeMinus5p)
    • 同意,他可能应该使用正态(高斯)分布或类似的东西。
    【解决方案4】:

    我认为这主要是因为您使用的随机数生成器在技术上是裤子。

    为了更好的“随机性”,请改用 RNGCryptoServiceProvider 生成随机数。从技术上讲,它是一个生成的伪随机数,但“随机”的质量要高得多(适用于加密目的)。

    Taken from here

    //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;
        }
    }
    

    【讨论】:

      【解决方案5】:

      根据您的代码,我可以将其导出为更简单的版本,如下所示:

      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)
      

      因此,您将 model.LastTradePrice 乘以小数(0 到 1 之间)乘以 0.1,这将始终为 decrease more to zero,但 increase less to 1

      由于+ 0.95 部分与zero-tending random.NextDouble() * 0.1 一起出现了小部分正数部分

      【讨论】:

      • 您错过了 OP 代码最后一行末尾的 + lastTradeMinus5p; 部分。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-04-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-27
      • 1970-01-01
      • 2014-03-21
      相关资源
      最近更新 更多