Skip to content

Commit

Permalink
feat: add portfolio return taxes
Browse files Browse the repository at this point in the history
  • Loading branch information
chriskelly committed Oct 25, 2023
1 parent 0c9ea89 commit d77afe0
Show file tree
Hide file tree
Showing 5 changed files with 27 additions and 33 deletions.
20 changes: 11 additions & 9 deletions app/data/taxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class Taxes(util.FloatRepr):
income: float
medicare: float
social_security: float
portfolio: float = -1
portfolio: float

def __float__(self):
return float(
Expand All @@ -152,19 +152,17 @@ def __float__(self):


def calc_taxes(
total_income: Income, controller: JobIncomeController, state: State
total_income: Income,
job_income_controller: JobIncomeController,
state: State,
portfolio_return: float,
) -> Taxes:
"""Calculates taxes for a given interval
Args:
total_income (Income)
controller (JobIncomeController)
state (State)
Returns:
Taxes: Attributes: income, medicare, social_security, portfolio
"""
taxable_income = controller.get_taxable_income(state.interval_idx)
taxable_income = job_income_controller.get_taxable_income(state.interval_idx)
job_income_tax = _calc_income_taxes(interval_income=taxable_income, state=state)
pension_income_tax = 0.8 * _calc_income_taxes(
interval_income=total_income.social_security_user
Expand All @@ -175,7 +173,11 @@ def calc_taxes(
return Taxes(
income=job_income_tax + pension_income_tax,
medicare=-total_income.job_income * MEDICARE_TAX_RATE,
social_security=_social_security_tax(controller, state),
social_security=_social_security_tax(job_income_controller, state),
# Assumes user tax-loss harvestes, so tax is always has opposite sign of return
# There is technically an annual limit to harvesting, but losses carry over,
# so for simplicity we assume it is always harvested the year it is incurred.
portfolio=-portfolio_return * state.user.portfolio.tax_rate,
)


Expand Down
4 changes: 2 additions & 2 deletions app/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,15 +216,15 @@ class Portfolio(BaseModel):
Attributes
current_net_worth (float): Defaults to 0
drawdown_tax_rate (float): Defaults to 0.1
tax_rate (float): Defaults to 0.1
annuity (AnnuityConfig): Defaults to None
allocation_strategy (AllocationOptions)
"""

current_net_worth: float = 0
drawdown_tax_rate: float = 0.1
tax_rate: float = 0.1
annuity: Optional[AnnuityConfig] = None
allocation_strategy: AllocationOptions

Expand Down
22 changes: 11 additions & 11 deletions app/models/financial/state_change.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,23 +129,22 @@ def _gen_net_transactions(self) -> _NetTransactions:
portfolio_return = self._state.net_worth * np.dot(
self._economic_data.asset_rates, self._allocation
)
costs = self._gen_costs(income)
annuity = self._controllers.annuity.make_annuity_transaction(
state=self._state,
is_working=self._controllers.job_income.is_working(
self._state.interval_idx
),
initial_net_transaction=income.job_income + costs,
)
costs = self._gen_costs(income, portfolio_return)

return _NetTransactions(
income=income,
portfolio_return=portfolio_return,
costs=costs,
annuity=annuity,
annuity=self._controllers.annuity.make_annuity_transaction(
state=self._state,
is_working=self._controllers.job_income.is_working(
self._state.interval_idx
),
initial_net_transaction=income.job_income + costs,
),
)

def _gen_costs(self, income: Income) -> _Costs:
def _gen_costs(self, income: Income, portfolio_return: float) -> _Costs:
spending = _calc_spending(
state=self._state,
config=self._state.user.spending,
Expand All @@ -162,7 +161,8 @@ def _gen_costs(self, income: Income) -> _Costs:
),
taxes=calc_taxes(
total_income=income,
controller=self._controllers.job_income,
job_income_controller=self._controllers.job_income,
state=self._state,
portfolio_return=portfolio_return,
),
)
12 changes: 2 additions & 10 deletions app/models/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,6 @@ def as_dataframes(self) -> list[pd.DataFrame]:
"""
Returns a list of pandas DataFrames, where each DataFrame
represents a trial in the simulator.
Each DataFrame contains the following columns:
- Date: The date of the interval.
- Net Worth: The net worth of the interval.
- Inflation: The inflation rate of the interval.
- Job Income: The job income of the interval.
- SS User: The social security income of the interval for the user.
- SS Partner: The social security income of the interval for the partner.
- Pension: The pension income of the interval.
- Portfolio Return: The portfolio return of the interval.
- Annuity: The annuity income of the interval.
"""
columns = [
"Date",
Expand All @@ -115,6 +105,7 @@ def as_dataframes(self) -> list[pd.DataFrame]:
"Income Taxes",
"Medicare Taxes",
"Social Security Taxes",
"Portfolio Taxes",
"Total Taxes",
"Total Costs",
"Portfolio Return",
Expand All @@ -138,6 +129,7 @@ def as_dataframes(self) -> list[pd.DataFrame]:
interval.state_change_components.net_transactions.costs.taxes.income,
interval.state_change_components.net_transactions.costs.taxes.medicare,
interval.state_change_components.net_transactions.costs.taxes.social_security,
interval.state_change_components.net_transactions.costs.taxes.portfolio,
interval.state_change_components.net_transactions.costs.taxes,
interval.state_change_components.net_transactions.costs,
interval.state_change_components.net_transactions.portfolio_return,
Expand Down
2 changes: 1 addition & 1 deletion tests/sample_configs/full_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ net_worth_target: 1500

portfolio:
current_net_worth: 250
drawdown_tax_rate: 0.1
tax_rate: 0.1
annuity:
net_worth_target: 500
contribution_rate: 0.1
Expand Down

0 comments on commit d77afe0

Please sign in to comment.