using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Game.Extensions;
namespace Game
{
///
/// Provides a method for simulating a stock market.
///
public static class StockMarket
{
///
/// Simulates changes in the stock market over time.
///
///
/// The collection of companies that will participate in the market.
///
///
/// An infinite sequence of trading days. Each day represents the
/// state of the stock market at the start of that day.
///
public static IEnumerable Simulate(ImmutableArray companies)
{
var random = new Random();
var cyclicParameters = EnumerableExtensions.Zip(
Trends(random, 1, 5),
PriceSpikes(random, companies.Length, 1, 5),
PriceSpikes(random, companies.Length, 1, 5),
(trend, company1, company2) => (trend, positiveSpike: company1, negativeSpike: company2));
return cyclicParameters.SelectAndAggregate(
new TradingDay
{
Companies = companies
},
(parameters, previousDay) => previousDay with
{
Companies = previousDay.Companies.Map(
(company, index) => AdjustSharePrice(
random,
company,
parameters.trend,
parameters.positiveSpike == index,
parameters.negativeSpike == index))
});
}
///
/// Creates a copy of a company with a randomly adjusted share price,
/// based on the given parameters.
///
///
/// The random number generator.
///
///
/// The company to adjust.
///
///
/// The slope of the overall market price trend.
///
///
/// True if the function should simulate a positive spike in the
/// company's share price.
///
///
/// True if the function should simulate a negative spike in the
/// company's share price.
///
///
/// The adjusted company.
///
private static Company AdjustSharePrice(Random random, Company company, double trend, bool positiveSpike, bool negativeSpike)
{
var boost = random.Next(4) * 0.25;
var spikeAmount = 0.0;
if (positiveSpike)
spikeAmount = 10;
if (negativeSpike)
spikeAmount = spikeAmount - 10;
var priceChange = (int)(trend * company.SharePrice) + boost + (int)(3.5 - (6 * random.NextDouble())) + spikeAmount;
var newPrice = company.SharePrice + priceChange;
if (newPrice < 0)
newPrice = 0;
return company with { SharePrice = newPrice };
}
///
/// Generates an infinite sequence of market trends.
///
///
/// The random number generator.
///
///
/// The minimum number of days each trend should last.
///
///
/// The maximum number of days each trend should last.
///
public static IEnumerable Trends(Random random, int minDays, int maxDays) =>
random.Integers(minDays, maxDays + 1).SelectMany(daysInCycle => Enumerable.Repeat(GenerateTrend(random), daysInCycle));
///
/// Generates a random value for the market trend.
///
///
/// The random number generator.
///
///
/// A trend value in the range [-0.1, 0.1].
///
private static double GenerateTrend(Random random) =>
((int)(random.NextDouble() * 10 + 0.5) / 100.0) * (random.Next(2) == 0 ? 1 : -1) ;
///
/// Generates an infinite sequence of price spikes.
///
///
/// The random number generator.
///
///
/// The number of companies.
///
///
/// The minimum number of days in between price spikes.
///
///
/// The maximum number of days in between price spikes.
///
///
/// An infinite sequence of random company indexes and null values.
/// A non-null value means that the corresponding company should
/// experience a price spike.
///
private static IEnumerable PriceSpikes(Random random, int companyCount, int minDays, int maxDays) =>
random.Integers(minDays, maxDays + 1)
.SelectMany(
daysInCycle => Enumerable.Range(0, daysInCycle),
(daysInCycle, dayNumber) => dayNumber == 0 ? random.Next(companyCount) : default(int?));
}
}