For starters, you have a very subtle bug that would cause incorrect results if you happened to start this late in the evening on December 31! You are calling
DateTime.Now.Year
in lots of places. That could change during running, leading to incorrect results! You have already captured
DateTime.Now
into
dt
near the beginning of
Main()
, use
that everywhere else, like:
for (int i = dt.Year; i <= dt.Year + 20; i++)
Since this won't help with your performance issue, I'll leave this suggestion at this point. ;-)
Performance:
This was an interesting problem so I spent
(more than I should have) time on it.
It appears that for Year 1, an iteration's starting value is the previous iteration's value, while for the remaining years an iteration's starting value is the previous year's value for the same iteration #.
So, first run a Year 1 loop, chaining the starting values from iteration to iteration. As each iteration ends, then start a Task that computes the remaining years of that iteration.
For 1000 iterations, this will
enable about a 1000x parallelism!
NOTE: You will
not get this much parallelism unless you have more than 1000 cores! But it should be able to use just about as much parallel capability as the system has to offer.
Another consideration is that any collections that are not year-specific will probably need to be changed to thread-safe collections (unless they aren't used until everything else is done).
So here's what I came up with:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ConsoleApplication11
{
class Program
{
const int Iterations = 100;
const int Years = 20;
const double InitialValue = 1000;
static void Main(string[] args)
{
Reference.dividendP = 0.11;
Reference.interestP = 0.07;
Reference.feeP = 0.05;
List<CurrentAllocation> lstca = new List<CurrentAllocation>() {
new CurrentAllocation { ID = 1, aloc = 0.3 },
new CurrentAllocation { ID = 2, aloc = 0.2 },
new CurrentAllocation { ID = 3, aloc = 0.4 },
new CurrentAllocation { ID = 4, aloc = 0.1 }
};
DateTime dt = DateTime.Now;
Console.WriteLine(dt);
double pval = InitialValue;
Task<List<LinearGrowth>>[] workers = new Task<List<LinearGrowth>>[Iterations];
List<LinearGrowth>[] growthByYear = new List<LinearGrowth>[Years];
List<LinearGrowth> firstYearGrowth = new List<LinearGrowth>(Iterations);
Random rand = new Random(CalculationMethods.MakeSeed());
for (int iter = 0; iter < Iterations; iter++)
{
LinearGrowth lg = CalculationMethods.Montecarlo(lstca, pval, rand);
firstYearGrowth.Add(lg);
workers[iter] = CalculationMethods.SpinUpGrowthOverYearsTask(dt.Year + 1, Years - 1, pval, lstca);
pval = lg.endingvalue;
}
Task.WaitAll(workers);
growthByYear[0] = firstYearGrowth;
for (int yearOffset = 1; yearOffset < Years; yearOffset++)
{
growthByYear[yearOffset] = new List<LinearGrowth>(Iterations);
}
foreach (var iterTask in workers)
{
var yearsResults = iterTask.Result;
int yearOffset = 1;
foreach (var result in yearsResults)
{
growthByYear[yearOffset++].Add(result);
}
}
List<Probability> lstprob = new List<Probability>();
int year = dt.Year;
foreach (var yearGrowth in growthByYear)
{
Probability objprob = new Probability();
objprob.year = year;
objprob.yearvalues = yearGrowth.OrderBy(o => o.endingvalue).ToList();
lstprob.Add(objprob);
Console.WriteLine("Year: {0}\n5th: {1} 50th: {2} 95th: {3}",
year++,
objprob.yearvalues[4].endingvalue,
objprob.yearvalues[49].endingvalue,
objprob.yearvalues[94].endingvalue);
}
DateTime end = DateTime.Now;
Console.WriteLine("Ending time: {0}", end);
TimeSpan t = end - dt;
Console.WriteLine("Elapsed time: {0}", t);
Console.ReadLine();
}
}
public class CalculationMethods
{
public static int MakeSeed()
{
long ticks = DateTime.Now.Ticks;
return (int)((ticks >> 32) ^ ticks);
}
public static Task<List<LinearGrowth>> SpinUpGrowthOverYearsTask(int startYear, int numberOfYears, double initial, IEnumerable<CurrentAllocation> currentAllocations)
{
Random ran = new Random(MakeSeed());
return Task.Run<List<LinearGrowth>>(() => GrowthOverYears(startYear, numberOfYears, initial, currentAllocations, ran));
}
public static List<LinearGrowth> GrowthOverYears(int startYear, int numberOfYears, double initial, IEnumerable<CurrentAllocation> currentAllocations, Random ran)
{
List<LinearGrowth> result = new List<LinearGrowth>(numberOfYears);
for (int yearOffset = 0; yearOffset < numberOfYears; yearOffset++)
{
LinearGrowth lg = CalculationMethods.Montecarlo(currentAllocations, initial, ran);
result.Add(lg);
initial = lg.endingvalue;
}
return result;
}
public static LinearGrowth Montecarlo(IEnumerable<CurrentAllocation> currentAllocations, double pv, Random ran)
{
LinearGrowth lg = new LinearGrowth();
double allocation, interest, dividend, fees, annualreturn;
double sum_int, sum_div, sum_fee, sum_alloc, tax;
sum_int = sum_div = sum_fee = tax = sum_alloc = 0;
foreach (var ca in currentAllocations)
{
allocation = pv * ca.aloc;
annualreturn = allocation * ran.NextDouble();
interest = allocation * Reference.interestP;
dividend = allocation * Reference.dividendP;
fees = allocation * Reference.feeP;
sum_int += interest;
sum_div += dividend;
sum_fee += fees;
sum_alloc += annualreturn;
}
tax = (sum_int + sum_div) * 0.15;
lg.startingvalue = pv;
lg.fees = sum_fee;
lg.tax = tax;
lg.endingvalue = pv + sum_int + sum_div - tax - sum_fee;
return lg;
}
}
public class LinearGrowth
{
public double startingvalue { get; set; }
public double fees { get; set; }
public double tax { get; set; }
public double endingvalue { get; set; }
}
public class Reference
{
public static double interestP { get; set; }
public static double dividendP { get; set; }
public static double feeP { get; set; }
}
public class Probability
{
public int year { get; set; }
public List<LinearGrowth> yearvalues { get; set; }
}
public class CurrentAllocation
{
public int ID { get; set; }
public double aloc { get; set; }
}
}
Note: when you divide this into multiple
Task
s, there's no guarantee that the
Console.WriteLine(...)
from each
Task
, will all come out in the same sequence as sequential.
Caveat emptor! (Above, I removed all of the
Console.WriteLine(...)
from the tasks.)
Enjoy!