public void Apply(IPortfolioTransaction transaction, IHolding holding, ICashAccount cashAccount)
        {
            var aquisition = transaction as Aquisition;

            if (aquisition == null)
            {
                throw new ArgumentException("Expected transaction to be an Aquisition");
            }

            if (!aquisition.Stock.IsEffectiveAt(aquisition.Date))
            {
                throw new StockNotActiveException("Stock is not active");
            }

            decimal cost       = aquisition.Units * aquisition.AveragePrice;
            decimal amountPaid = cost + aquisition.TransactionCosts;
            decimal costBase   = amountPaid;

            holding.AddParcel(aquisition.Date, aquisition.Date, aquisition.Units, amountPaid, costBase, transaction);

            if (aquisition.CreateCashTransaction)
            {
                var asxCode = aquisition.Stock.Properties[aquisition.Date].AsxCode;
                cashAccount.Transfer(aquisition.Date, -cost, String.Format("Purchase of {0}", asxCode));

                if (aquisition.TransactionCosts > 0.00m)
                {
                    cashAccount.FeeDeducted(aquisition.Date, aquisition.TransactionCosts, String.Format("Brokerage for purchase of {0}", asxCode));
                }
            }
        }
예제 #2
0
        public void Apply(IPortfolioTransaction transaction, IHolding holding, ICashAccount cashAccount)
        {
            var returnOfCapital = transaction as ReturnOfCapital;

            if (returnOfCapital == null)
            {
                throw new ArgumentException("Expected transaction to be an ReturnOfCapital");
            }

            if (!holding.IsEffectiveAt(returnOfCapital.RecordDate))
            {
                throw new NoSharesOwnedException("No holdings");
            }

            // Reduce cost base of parcels
            decimal totalAmount = 0;

            foreach (var parcel in holding.Parcels(returnOfCapital.RecordDate))
            {
                var costBaseReduction = parcel.Properties[returnOfCapital.RecordDate].Units * returnOfCapital.Amount;
                parcel.Change(returnOfCapital.RecordDate, 0, 0.00m, costBaseReduction, transaction);

                totalAmount += costBaseReduction;
            }

            if (returnOfCapital.CreateCashTransaction)
            {
                var asxCode = returnOfCapital.Stock.Properties[returnOfCapital.RecordDate].AsxCode;
                cashAccount.Transfer(returnOfCapital.Date, totalAmount, String.Format("Return of capital for {0}", asxCode));
            }
        }
예제 #3
0
 public ParcelAudit(Date date, int unitCountChange, decimal costBaseChange, decimal amountChange, IPortfolioTransaction transaction)
 {
     Date            = date;
     UnitCountChange = unitCountChange;
     CostBaseChange  = costBaseChange;
     AmountChange    = amountChange;
     Transaction     = transaction;
 }
예제 #4
0
        public void Apply(IPortfolioTransaction transaction, IHolding holding, ICashAccount cashAccount)
        {
            var incomeReceived = transaction as IncomeReceived;

            if (incomeReceived == null)
            {
                throw new ArgumentException("Expected transaction to be an IncomeReceived");
            }

            if (!holding.IsEffectiveAt(incomeReceived.RecordDate))
            {
                throw new NoSharesOwnedException("No holdings");
            }

            // Handle any tax deferred amount recieved
            if (incomeReceived.TaxDeferred > 0)
            {
                var parcels = holding.Parcels(incomeReceived.RecordDate);

                // Apportion amount between parcels
                var apportionedAmounts = parcels.Select(x => new ApportionedCurrencyValue()
                {
                    Units = x.Properties[incomeReceived.RecordDate].Units
                }).ToArray();
                MathUtils.ApportionAmount(incomeReceived.TaxDeferred, apportionedAmounts);

                // Reduce cost base of parcels
                var i = 0;
                foreach (var parcel in parcels)
                {
                    parcel.Change(incomeReceived.RecordDate, 0, 0.00m, apportionedAmounts[i++].Amount, transaction);
                }
            }

            if (incomeReceived.CreateCashTransaction)
            {
                var asxCode = incomeReceived.Stock.Properties[incomeReceived.RecordDate].AsxCode;
                cashAccount.Transfer(incomeReceived.Date, incomeReceived.CashIncome, String.Format("Distribution for {0}", asxCode));
            }

            var drpCashBalance = holding.DrpAccount.Balance(incomeReceived.Date);

            var drpAccountCredit = incomeReceived.DrpCashBalance - drpCashBalance;

            if (drpAccountCredit != 0.00m)
            {
                holding.AddDrpAccountAmount(incomeReceived.Date, drpAccountCredit);
            }
        }
        public void Apply(IPortfolioTransaction transaction, IHolding holding, ICashAccount cashAccount)
        {
            var openingBalance = transaction as OpeningBalance;

            if (openingBalance == null)
            {
                throw new ArgumentException("Expected transaction to be an OpeningBalance");
            }

            if (!openingBalance.Stock.IsEffectiveAt(openingBalance.Date))
            {
                throw new StockNotActiveException("Stock is not active");
            }

            holding.AddParcel(openingBalance.Date, openingBalance.AquisitionDate, openingBalance.Units, openingBalance.CostBase, openingBalance.CostBase, transaction);
        }
예제 #6
0
        public void Apply(IPortfolioTransaction transaction, IHolding holding, ICashAccount cashAccount)
        {
            var costBaseAdjustment = transaction as CostBaseAdjustment;

            if (costBaseAdjustment == null)
            {
                throw new ArgumentException("Expected transaction to be an CostBaseAdjustment");
            }

            if (!holding.IsEffectiveAt(costBaseAdjustment.Date))
            {
                throw new NoSharesOwnedException("No holdings");
            }

            // Adjust cost base of parcels
            foreach (var parcel in holding.Parcels(costBaseAdjustment.Date))
            {
                var costBaseReduction = (parcel.Properties[costBaseAdjustment.Date].CostBase * (1 - costBaseAdjustment.Percentage)).ToCurrency(RoundingRule.Round);
                parcel.Change(costBaseAdjustment.Date, 0, 0.00m, -costBaseReduction, transaction);
            }
        }
        public void Apply(IPortfolioTransaction transaction, IHolding holding, ICashAccount cashAccount)
        {
            var cashTransaction = transaction as CashTransaction;

            if (cashTransaction == null)
            {
                throw new ArgumentException("Expected transaction to be a CashTransaction");
            }

            var description = "";

            if (cashTransaction.Comment != "")
            {
                description = cashTransaction.Comment;
            }
            else if (cashTransaction.CashTransactionType == BankAccountTransactionType.Deposit)
            {
                description = "Deposit";
            }
            else if (cashTransaction.CashTransactionType == BankAccountTransactionType.Fee)
            {
                description = "Fee";
            }
            else if (cashTransaction.CashTransactionType == BankAccountTransactionType.Interest)
            {
                description = "Interest";
            }
            else if (cashTransaction.CashTransactionType == BankAccountTransactionType.Transfer)
            {
                description = "Transfer";
            }
            else if (cashTransaction.CashTransactionType == BankAccountTransactionType.Withdrawl)
            {
                description = "Withdrawl";
            }

            cashAccount.AddTransaction(cashTransaction.Date, cashTransaction.Amount, description, cashTransaction.CashTransactionType);
        }
        public void Apply(IPortfolioTransaction transaction, IHolding holding, ICashAccount cashAccount)
        {
            var unitCountAdjustment = transaction as UnitCountAdjustment;

            if (unitCountAdjustment == null)
            {
                throw new ArgumentException("Expected transaction to be an UnitCountAdjustment");
            }

            if (!holding.IsEffectiveAt(unitCountAdjustment.Date))
            {
                throw new NoSharesOwnedException("No holdings");
            }

            // Adjust unit count of parcels
            var ratio = (decimal)unitCountAdjustment.NewUnits / (decimal)unitCountAdjustment.OriginalUnits;

            foreach (var parcel in holding.Parcels(unitCountAdjustment.Date))
            {
                var units = (int)Math.Ceiling(parcel.Properties[unitCountAdjustment.Date].Units * ratio);
                parcel.Change(unitCountAdjustment.Date, units, 0.00m, 0.00m, transaction);
            }
        }
예제 #9
0
        private void OnCgtEventOccured(Date eventDate, IReadOnlyStock stock, int units, decimal costBase, decimal amountReceived, decimal capitalGain, CgtMethod cgtMethod, IPortfolioTransaction transaction)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            var handler = CgtEventOccurred;

            if (handler != null)
            {
                var e = new CgtEventArgs()
                {
                    EventDate      = eventDate,
                    Stock          = stock,
                    Units          = units,
                    CostBase       = costBase,
                    AmountReceived = amountReceived,
                    CapitalGain    = capitalGain,
                    CgtMethod      = cgtMethod,
                    Transaction    = transaction
                };

                handler(this, e);
            }
        }
예제 #10
0
        public void DisposeOfParcel(Guid parcelId, Date date, int units, decimal amount, decimal capitalGain, CgtMethod cgtMethod, IPortfolioTransaction transaction)
        {
            if (!_Parcels.TryGetValue(parcelId, out var parcel))
            {
                throw new ArgumentException("Parcel is not part of this holding");
            }

            var parcelProperties = parcel.Properties[date];

            if (units > parcelProperties.Units)
            {
                throw new NotEnoughSharesForDisposal("Not enough shares in parcel");
            }

            // Adjust Parcel
            decimal costBaseChange;
            decimal amountChange;

            if (units == parcelProperties.Units)
            {
                amountChange   = parcelProperties.Amount;
                costBaseChange = parcelProperties.CostBase;
            }
            else
            {
                amountChange   = (parcelProperties.Amount * ((decimal)units / parcelProperties.Units)).ToCurrency(RoundingRule.Round);
                costBaseChange = (parcelProperties.CostBase * ((decimal)units / parcelProperties.Units)).ToCurrency(RoundingRule.Round);
            }
            parcel.Change(date, -units, -amountChange, -costBaseChange, transaction);

            // Adjust holding
            var holdingProperties = Properties[date];
            HoldingProperties newProperties;

            if (units == holdingProperties.Units)
            {
                End(date);
                newProperties = new HoldingProperties(0, 0.00m, 0.00m);
            }
            else
            {
                newProperties = new HoldingProperties(holdingProperties.Units - units, holdingProperties.Amount - amountChange, holdingProperties.CostBase - costBaseChange);
            }
            _Properties.Change(date, newProperties);

            OnCgtEventOccured(date, Stock, units, costBaseChange, amount, capitalGain, cgtMethod, transaction);
        }
예제 #11
0
        public IParcel AddParcel(Date date, Date aquisitionDate, int units, decimal amount, decimal costBase, IPortfolioTransaction transaction)
        {
            var parcel = new Parcel(Guid.NewGuid(), date, aquisitionDate, new ParcelProperties(units, amount, costBase), transaction);

            _Parcels.Add(parcel.Id, parcel);

            var exisingProperties = Properties[date];
            var newProperties     = new HoldingProperties(exisingProperties.Units + units, exisingProperties.Amount + amount, exisingProperties.CostBase + costBase);

            _Properties.Change(date, newProperties);

            return(parcel);
        }
예제 #12
0
        public void Change(Date date, int unitChange, decimal amountChange, decimal costBaseChange, IPortfolioTransaction transaction)
        {
            if (!EffectivePeriod.Contains(date))
            {
                throw new EffectiveDateException("The parcel is not effective at that date");
            }

            var parcelProperties = _Properties[date];

            var newUnits    = parcelProperties.Units + unitChange;
            var newAmount   = parcelProperties.Amount + amountChange;
            var newCostBase = parcelProperties.CostBase + costBaseChange;

            if (newUnits < 0)
            {
                throw new ArgumentException("Units cannot be changed to be less than 0");
            }
            if (newAmount < 0)
            {
                throw new ArgumentException("Amount cannot be changed to be less than 0");
            }
            if (newCostBase < 0)
            {
                throw new ArgumentException("Costbase cannot be changed to be less than 0");
            }

            ParcelProperties newParcelProperties;

            if (newUnits == 0)
            {
                End(date);
                newParcelProperties = new ParcelProperties(0, 0.00m, 0.00m);
            }
            else
            {
                newParcelProperties = new ParcelProperties(newUnits, newAmount, newCostBase);
            }

            _Properties.Change(date, newParcelProperties);

            _Audit.Add(new ParcelAudit(date, unitChange, costBaseChange, amountChange, transaction));
        }
예제 #13
0
        public Parcel(Guid id, Date fromDate, Date aquisitionDate, ParcelProperties properties, IPortfolioTransaction transaction)
            : base(id)
        {
            Start(fromDate);

            AquisitionDate = aquisitionDate;
            _Properties.Change(fromDate, properties);

            _Audit.Add(new ParcelAudit(fromDate, properties.Units, properties.CostBase, properties.Amount, transaction));
        }
예제 #14
0
        public void Apply(IPortfolioTransaction transaction, IHolding holding, ICashAccount cashAccount)
        {
            var disposal = transaction as Disposal;

            if (disposal == null)
            {
                throw new ArgumentException("Expected transaction to be a Disposal");
            }

            if (!holding.IsEffectiveAt(disposal.Date))
            {
                throw new NoSharesOwnedException("No holdings");
            }

            if (holding.Properties[disposal.Date].Units < disposal.Units)
            {
                throw new NotEnoughSharesForDisposal("Not enough shares for disposal");
            }

            // Determine which parcels to sell based on CGT method
            decimal amountReceived = (disposal.Units * disposal.AveragePrice) - disposal.TransactionCosts;

            var cgtCalculator = new CgtCalculator();
            var parcelsSold   = cgtCalculator.Calculate(holding.Parcels(disposal.Date), disposal.Date, disposal.Units, amountReceived, CgtCalculator.GetCgtComparer(disposal.Date, disposal.CgtMethod));

            // Dispose of select parcels
            if (disposal.Stock is StapledSecurity)
            {
                /*     foreach (ParcelSold parcelSold in cgtCalculation.ParcelsSold)
                 *   {
                 *       var childStocks = _StockQuery.GetChildStocks(stock.Id, disposal.TransactionDate);
                 *
                 *       // Apportion amount based on NTA of child stocks
                 *       var amountsReceived = PortfolioUtils.ApportionAmountOverChildStocks(childStocks, disposal.TransactionDate, parcelSold.AmountReceived, _StockQuery);
                 *
                 *       int i = 0;
                 *       foreach (var childStock in childStocks)
                 *       {
                 *           var childParcels = _PortfolioQuery.GetParcelsForStock(childStock.Id, disposal.TransactionDate, disposal.TransactionDate);
                 *
                 *           var childParcel = childParcels.First(x => x.PurchaseId == parcelSold.Parcel.PurchaseId);
                 *           DisposeOfParcel(unitOfWork, childParcel, disposal.TransactionDate, parcelSold.UnitsSold, amountsReceived[i].Amount, transaction.Id);
                 *
                 *           i++;
                 *       }
                 *
                 *   };  */
            }
            else
            {
                foreach (var parcelSold in parcelsSold)
                {
                    holding.DisposeOfParcel(parcelSold.Parcel.Id, disposal.Date, parcelSold.UnitsSold, parcelSold.AmountReceived, parcelSold.CapitalGain, parcelSold.CgtMethod, transaction);
                }
            }

            if (disposal.CreateCashTransaction)
            {
                var cost = disposal.Units * disposal.AveragePrice;

                var asxCode = disposal.Stock.Properties[disposal.Date].AsxCode;
                cashAccount.Transfer(disposal.Date, cost, String.Format("Sale of {0}", asxCode));

                if (disposal.TransactionCosts > 0.00m)
                {
                    cashAccount.FeeDeducted(disposal.Date, disposal.TransactionCosts, String.Format("Brokerage for sale of {0}", asxCode));
                }
            }
        }