private void ApplySplits(Security s, IList <StockSplit> splits, DateTime dateTime) { StockSplit next = splits.FirstOrDefault(); while (next != null && next.Date.Date < dateTime.Date) { ApplySplit(s, next); splits.Remove(next); next = splits.FirstOrDefault(); } }
public async Task PerformStockSplit(ulong userId, StockSplit splitDetails) { var purchaseTask = _stockPurchaseService.GetAllPurchasesForTicker(userId, splitDetails.Ticker); var holdingTask = _holdingService.GetAllHoldingsForUserWithStockDetails(userId); var purchases = await purchaseTask; var holdings = await holdingTask; var requiredHolding = holdings.Where(x => x.Stock.Ticker == splitDetails.Ticker).First(); var purchaseIdsUpdated = UpdatePurchases(purchases, splitDetails); UpdateUserHoldings(requiredHolding, splitDetails, purchaseIdsUpdated); await _stockPurchaseService.UpdatePurchasePriceAndQuantityByPurchaseId(userId, purchases); await _holdingService.UpdateHoldingDetails(userId, requiredHolding); }
private void UpdateUserHoldings(Holdings holding, StockSplit splitDetails, IEnumerable <ulong> purchaseIdsUpdated) { var ratio = splitDetails.NewStockRatio / splitDetails.OldStockRatio; if (holding != null && purchaseIdsUpdated.Count() > 0) { var holdingDetails = holding.HoldingDetails; foreach (var detail in holdingDetails) { if (purchaseIdsUpdated.Contains(detail.PurchaseId)) { detail.Price = detail.Price / ratio; detail.Quantity = detail.Quantity * ratio; } } } }
private void ApplySplit(Security s, StockSplit split) { foreach (AccountHoldings holding in this.byAccount.Values) { decimal total = 0; foreach (SecurityPurchase purchase in holding.GetPurchases(s)) { purchase.UnitsRemaining = (purchase.UnitsRemaining * split.Numerator) / split.Denominator; purchase.CostBasisPerUnit = (purchase.CostBasisPerUnit * split.Denominator) / split.Numerator; total += purchase.UnitsRemaining; } // yikes also have to split the pending sales...? foreach (SecuritySale pending in holding.GetPendingSalesForSecurity(s)) { if (pending.DateSold < split.Date) { pending.UnitsSold = (pending.UnitsSold * split.Numerator) / split.Denominator; pending.SalePricePerUnit = (pending.SalePricePerUnit * split.Denominator) / split.Numerator; } } if (s.SecurityType == SecurityType.Equity) { // companies don't want to deal with fractional stocks, they usually distribute a "cash in lieu" // transaction in this case to compensate you for the rounding error. decimal floor = Math.Floor(total); if (floor != total) { decimal diff = total - floor; decimal adjustment = (total - diff) / total; // distribute this rounding error back into the units remaining so we remember it. foreach (SecurityPurchase purchase in holding.GetPurchases(s)) { purchase.UnitsRemaining = Math.Round(purchase.UnitsRemaining * adjustment, 5); } } } } }
public async Task PerformStockSplit(ulong userId, [FromBody] StockSplit splitDetails) { await _stockSplitService.PerformStockSplit(userId, splitDetails); }
private IEnumerable <ulong> UpdatePurchases(IEnumerable <Purchase> purchases, StockSplit splitDetails) { var applicablePurchases = purchases.Where(x => x.Date < splitDetails.EffectiveDate); var purchaseIdsUpdated = new List <ulong>(); if (splitDetails.LastSplitDate != null) { applicablePurchases = applicablePurchases.Where(x => x.Date >= splitDetails.LastSplitDate); } if (splitDetails.NewStockRatio > splitDetails.OldStockRatio) { var ratio = splitDetails.NewStockRatio / splitDetails.OldStockRatio; foreach (var purchase in applicablePurchases) { purchase.Price = purchase.Price / ratio; purchase.Quantity = purchase.Quantity * ratio; purchaseIdsUpdated.Add(purchase.PurchaseId); } } return(purchaseIdsUpdated); }
public void Create() { string temp = Path.Combine(Path.GetTempPath(), "MyMoney"); Directory.CreateDirectory(temp); string path = Path.Combine(temp, "SampleData.xml"); ProcessHelper.ExtractEmbeddedResourceAsFile("Walkabout.Database.SampleData.xml", path); SampleDatabaseOptions options = new SampleDatabaseOptions(); options.Owner = Application.Current.MainWindow; options.SampleData = path; if (options.ShowDialog() == false) { return; } string zipPath = Path.Combine(temp, "SampleStockQuotes.zip"); ProcessHelper.ExtractEmbeddedResourceAsFile("Walkabout.Database.SampleStockQuotes.zip", zipPath); string quoteFolder = Path.Combine(temp, "StockQuotes"); if (Directory.Exists(quoteFolder)) { Directory.Delete(quoteFolder, true); } System.IO.Compression.ZipFile.ExtractToDirectory(zipPath, temp); foreach (var file in Directory.GetFiles(quoteFolder)) { var target = Path.Combine(this.stockQuotePath, Path.GetFileName(file)); if (!File.Exists(target)) { File.Copy(file, target, true); } } path = options.SampleData; double inflation = options.Inflation; SampleData data = null; XmlSerializer s = new XmlSerializer(typeof(SampleData)); using (XmlReader reader = XmlReader.Create(path)) { data = (SampleData)s.Deserialize(reader); } foreach (SampleSecurity ss in data.Securities) { var history = StockQuoteHistory.Load(quoteFolder, ss.Symbol); if (history != null) { this.quotes[ss.Symbol] = history; this.manager.DownloadLog.AddHistory(history); } } int totalFrequency = data.GetTotalFrequency(); List <SampleTransaction> list = new List <SampleTransaction>(); List <Account> brokerageAccounts = new List <Account>(); foreach (SampleAccount sa in data.Accounts) { // Create all the accounts. Accounts accounts = money.Accounts; Account a = accounts.FindAccount(sa.Name); if (a == null) { a = accounts.AddAccount(sa.Name); } a.Type = sa.Type; if (a.Type == AccountType.Checking) { this.checking = a; } a.TaxStatus = sa.TaxStatus; // Create this many transactions int count = sa.Frequency; // by scaling the payee frequencies to match the above desired count. double ratio = (double)count / (double)totalFrequency; if (a.Type == AccountType.Brokerage || a.Type == AccountType.Retirement) { brokerageAccounts.Add(a); } else { // create flat list of all payees to choose from so it fits the histogram List <SamplePayee> payees = new List <SamplePayee>(); foreach (SamplePayee payee in data.Payees) { switch (payee.Type) { case PaymentType.Debit: case PaymentType.Check: if (sa.Type != AccountType.Checking) { continue; } break; case PaymentType.Credit: if (sa.Type != AccountType.Credit) { continue; } break; } foreach (SampleCategory sc in payee.Categories) { int newFrequency = (int)(sc.Frequency * ratio); for (int i = 0; i < newFrequency; i++) { list.Add(new SampleTransaction() { Account = sa, Payee = payee, Category = sc }); } } } } } money.BeginUpdate(this); // create the securities and stock splits foreach (var sec in data.Securities) { Security stock = money.Securities.FindSecurity(sec.Name, true); if (sec.Splits != null) { foreach (var split in sec.Splits) { var exists = money.StockSplits.FindStockSplitByDate(stock, split.Date); if (exists == null) { StockSplit stockSplit = money.StockSplits.NewStockSplit(); stockSplit.Security = stock; stockSplit.Date = split.Date; stockSplit.Numerator = split.Numerator; stockSplit.Denominator = split.Denominator; } } } } money.EndUpdate(); CreateRandomTransactions(list, inflation); AddPaychecks(options.Employer, options.PayCheck, inflation); // now with any spare cash we can buy stocks. CreateInvestmentSamples(data, brokerageAccounts); // only have to do this because we hid all update events earlier by doing BeginUpdate/EndUpdate on money object. // trigger payee update money.Payees.BeginUpdate(false); money.Payees.EndUpdate(); // trigger category update money.Categories.BeginUpdate(false); money.Categories.EndUpdate(); money.OnLoaded(); }