From 00138dd2fcaf2226402b5d1881ee9993624c8cc7 Mon Sep 17 00:00:00 2001 From: MadeInPierre Date: Sat, 13 Jan 2024 15:50:33 +0100 Subject: [PATCH 1/6] chore: update full example --- examples/full_example.py | 118 +++++++++++++++++++++++--------- finalynx/fetch/source_finary.py | 2 +- 2 files changed, 85 insertions(+), 35 deletions(-) diff --git a/examples/full_example.py b/examples/full_example.py index f473f4b..cce8abf 100755 --- a/examples/full_example.py +++ b/examples/full_example.py @@ -15,8 +15,22 @@ # noreorder from datetime import date from rich import inspect, print, pretty, traceback # noqa -from finalynx import TargetRange, TargetMin, TargetMax, TargetRatio, TargetGlobalRatio # noqa -from finalynx import Sidecar, Folder, Line, LinePerf, Bucket, SharedFolder, Portfolio, FolderDisplay # noqa +from finalynx import ( + TargetRange, + TargetMin, + TargetMax, + TargetRatio, +) # noqa +from finalynx import ( + Sidecar, + Folder, + Line, + LinePerf, + Bucket, + SharedFolder, + Portfolio, + FolderDisplay, +) # noqa from finalynx import Envelope, PEA, PEE, AV, PER from finalynx import AssetClass, AssetSubclass from finalynx import Simulation, AddLineAmount, Event, Salary @@ -31,24 +45,36 @@ """ (Optional) Custom shortcuts in variables used below to control the config quickly. """ - short_display = FolderDisplay.COLLAPSED # Display style for all short-term folders - medium_term_amount = 20000 # Amount of money to keep for medium-term (i.e. Livrets in this config) + short_display = FolderDisplay.EXPANDED # Display style for all short-term folders + medium_term_amount = ( + 20000 # Amount of money to keep for medium-term (i.e. Livrets in this config) + ) date_retirement = date(2063, 7, 1) # Define envelopes used in the portfolio bank_lbp = Envelope("La Banque Postale", "LBP") bank_n26 = Envelope("N26", "N26") - bank_boursorama = Envelope("Boursorama", "BOU") + bank_boursorama = Envelope("BoursoBank", "BOU") - pea = PEA("Bourse Direct", "PEA", date(2022, 7, 1), key="PEA") - pee = PEE("Natixis", "PEE", date(2023, 4, 1), date_unlock=date(2023, 11, 22), key="PEE Natixis") + pea = PEA( + "Bourse Direct", "PEA", date(2022, 7, 1), key="MR LACLAU PIERRE (Compte PEA)" + ) + pee = PEE( + "Natixis", + "PEE", + date(2023, 4, 1), + date_unlock=date(2023, 11, 22), + key="Plan d'Epargne Entreprise", + ) av_linxea = AV("Linxea Spirit 2", "LIX", date(2022, 7, 1), key="LINXEA Spirit 2") av_goodvest = AV("Goodvest", "GOO", date(2022, 7, 1)) av_ramify = AV("Ramify", "RAM", date(2022, 7, 1), key="Ramify AV") per_linxea = PER("Linxea Spirit PER", "PER", date(2022, 7, 1), date_retirement) - per_prefon = PER("Prefon", "PRF", date(2022, 7, 1), date_retirement, key="Autres actifs") + per_prefon = PER( + "Prefon", "PRF", date(2022, 7, 1), date_retirement, key="Autres actifs" + ) at_home = Envelope("At Home", "PHY", key="Metaux precieux") @@ -249,27 +275,27 @@ asset_class=AssetClass.STOCK, asset_subclass=AssetSubclass.ETF, target=TargetRatio(50), - perf=LinePerf(8), + perf=LinePerf(6), children=[ Folder( "USA", target=TargetRatio(50), children=[ Line( - "[italic]PE500[/] - SP500", - key="7030926", + "SP500 [italic](PE500)[/]", + key="13577960", target=TargetRatio(50), envelope=pea, ), Line( - "[italic]xxxxx[/] - SP500 ESG", - key="8804142", + "SP500 ESG [italic](xxxxx)[/]", + key="13578020", target=TargetRatio(30), envelope=av_linxea, ), Line( - "[italic]RS2K[/] - Russell 2000", - key="11358415", + "Russell 2000 [italic](RS2K)[/]", + key="13577964", target=TargetRatio(20), envelope=pea, ), @@ -280,14 +306,14 @@ target=TargetRatio(30), children=[ Line( - "[italic]PABZ[/] - Europe 600 ESG", - key="7492719", + "Europe 600 ESG [italic](PABZ)[/]", + key="13577963", target=TargetRatio(80), envelope=pea, ), Line( - "[italic]ETZ[/] - Europe 600", - key="7030927", + " Europe 600 [italic](ETZ)[/]", + key="13577961", target=TargetRatio(20), envelope=pea, ), @@ -298,19 +324,19 @@ target=TargetRatio(20), children=[ Line( - "[italic]PAEEM[/] - Emerging markets", - key="7036606", + "Emerging markets [italic](PAEEM)[/]", + key="13577962", target=TargetRatio(50), envelope=pea, ), Line( - "[italic]xxxxx[/] - Emerging markets ESG", + "Emerging markets ESG [italic](xxxxx)[/]", key="8804145", target=TargetRatio(30), envelope=av_linxea, ), Line( - "[italic]PTPXE[/] - Japon", + "Japon [italic](PTPXE)[/]", key="", target=TargetRatio(20), newline=True, @@ -389,6 +415,27 @@ ), ], ), + Folder( + "Bloqué", + perf=LinePerf(0, skip=True), + children=[ + Line( + "PEE (à récupérer)", + AssetClass.CASH, + AssetSubclass.MONETARY, + key="13009544", + envelope=pee, + ), + Line( + "Shares To Win Stellantis (à récupérer)", + AssetClass.STOCK, + AssetSubclass.STOCK_SHARE, + key="13417344", + envelope=pee, + newline=True, + ), + ], + ), Folder( "Retraite", perf=LinePerf(0, skip=True), @@ -430,18 +477,11 @@ key="CCP Boursorama", envelope=bank_boursorama, ), - Line( - "PEE (à récupérer)", - AssetClass.CASH, - AssetSubclass.MONETARY, - key="10117145", - envelope=pee, - ), Line( "Liquidités PEA (à investir)", AssetClass.CASH, AssetSubclass.LIQUIDITY, - key="7024202", + key="13577959", envelope=pea, target=TargetMax(0), ), @@ -509,9 +549,19 @@ ], simulation=Simulation( events=[ - Salary(livreta, income=2300, expenses=1400, end_date=date(2024, 11, 30)), - Event(AddLineAmount(livreta, 3500), planned_date=date(2024, 4, 10), name="Prime"), - Event(AddLineAmount(livreta, 3500), planned_date=date(2025, 4, 10), name="Prime"), + Salary( + livreta, income=2300, expenses=1400, end_date=date(2024, 11, 30) + ), + Event( + AddLineAmount(livreta, 3500), + planned_date=date(2024, 4, 10), + name="Prime", + ), + Event( + AddLineAmount(livreta, 3500), + planned_date=date(2025, 4, 10), + name="Prime", + ), Salary( livreta, income=3500, diff --git a/finalynx/fetch/source_finary.py b/finalynx/fetch/source_finary.py index 4a441c5..5d76b0d 100644 --- a/finalynx/fetch/source_finary.py +++ b/finalynx/fetch/source_finary.py @@ -179,7 +179,7 @@ def _fetch_data(self, tree: Tree) -> None: def _process_account(self, dict_account: Dict[str, Any], tree: Tree) -> None: account_name = dict_account["name"] - node = tree.add(account_name) + node = tree.add(account_name if not dict_account["fiats"] else dict_account["institution"]["name"]) for item in dict_account["fiats"]: self._register_fetchline( From c2d7d8286ad289222fecafcdf4456356a7ffcebc Mon Sep 17 00:00:00 2001 From: MadeInPierre Date: Sat, 13 Jan 2024 15:52:51 +0100 Subject: [PATCH 2/6] ci: manual update to v1.22.3 --- docs/conf.py | 2 +- finalynx/__meta__.py | 2 +- pyproject.toml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 92d3d21..e76d392 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,7 +22,7 @@ author = "Pierre Laclau" # The full version, including alpha/beta/rc tags -release = "1.22.2" +release = "1.22.3" # -- General configuration --------------------------------------------------- diff --git a/finalynx/__meta__.py b/finalynx/__meta__.py index 92d3599..c0a6679 100644 --- a/finalynx/__meta__.py +++ b/finalynx/__meta__.py @@ -6,7 +6,7 @@ Metadata information about Finalynx. This file is used by Fynalinx and updated by the CI/CD pipeline. """ -__version__ = "1.22.2" +__version__ = "1.22.3" __author__ = "Pierre Laclau (MadeInPierre)" diff --git a/pyproject.toml b/pyproject.toml index 68f3eea..4971ba1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "finalynx" -version = "1.22.2" +version = "1.22.3" description = "A command line investment assistant to organize your portfolio and simulate its future to reach your life goals." authors = ["MadeInPierre "] license = "GPLv3" @@ -56,7 +56,7 @@ build_command = "pip install poetry && poetry build" [tool.commitizen] name = "cz_conventional_commits" -version = "1.22.2" +version = "1.22.3" tag_format = "v$version" [tool.mypy] From d9478db0bcd4db01887ddee5ed542dcc6204116b Mon Sep 17 00:00:00 2001 From: gcoue Date: Sat, 13 Jan 2024 16:50:52 +0100 Subject: [PATCH 3/6] fix(fetch): change credit card to negative amount (#147) * Change credit card to negative amount * Rollback source_finary.py * Change credit card to negative amount * [pre-commit.ci lite] apply automatic fixes * Add a flag to allow the potfolio restitution for each simulation step * Add a flag to allow the potfolio restitution for each simulation step * fix: correct the recurrence function to be more precise on Yearly recurrence * [pre-commit.ci lite] apply automatic fixes * style: minor refactoring --- finalynx/assistant.py | 16 ++++++++++++++++ finalynx/fetch/source_finary.py | 17 ++++++++++++++--- finalynx/simulator/recurrence.py | 3 ++- finalynx/simulator/timeline.py | 3 +++ finalynx/usage.py | 1 + 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/finalynx/assistant.py b/finalynx/assistant.py index 6b6c0cd..b98970a 100644 --- a/finalynx/assistant.py +++ b/finalynx/assistant.py @@ -125,6 +125,9 @@ def __init__( # Initialize the simulation timeline with the initial user events self._timeline = Timeline(simulation, self.portfolio, self.buckets) if simulation else None + # Store the portfolio renders for each simulation date (if enabled) + self._timeline_renders: List[Any] = [] + def add_source(self, source: SourceBaseLine) -> None: """Register a source, either defined in your own config or from the available Finalynx sources using `from finalynx.fetch.source_any import SourceAny`.""" @@ -190,6 +193,8 @@ def _parse_args(self) -> None: self.active_sources = str(args["--sources"]).split(",") if args["--future"] and self.simulation: self.simulation.print_final = True + if args["--each-step"] and self.simulation: + self.simulation.print_each_step = True if args["--sim-steps"] and self.simulation: self.simulation.step_years = int(args["--sim-steps"]) if args["--theme"]: @@ -234,6 +239,11 @@ def run(self) -> None: # Add the simulation summary to the performance panel in the console dict_panels["performance"].add(self.simulate()) + # If enabled by the user, print the portfolio at each simulation date + if self.simulation.print_each_step: + for element in self._timeline_renders: + renders.append(element) + # If enabled by the user, print the final portfolio after the simulation if self.simulation.print_final: renders.append(f"\nYour portfolio in {self.simulation.end_date}:") @@ -299,8 +309,14 @@ def append_worth(year: int, amount: float) -> None: self._timeline.goto(date(year, 12, 31)) if (year - date.today().year) % self.simulation.step_years == 0: + # Append the portfolio's worth to the Worth tree append_worth(year, self.portfolio.get_amount()) + # Render each intermediate simulation step + if self.simulation.print_each_step: + title = "Your portfolio in [bold]" + str(year) + "-12-31:[/]" + self._timeline_renders.append(Panel(self.render_mainframe(), title=title)) + # Run until the end date and append the final result self._timeline.run() append_worth(self._timeline.current_date.year, self.portfolio.get_amount()) diff --git a/finalynx/fetch/source_finary.py b/finalynx/fetch/source_finary.py index 5d76b0d..afb9870 100644 --- a/finalynx/fetch/source_finary.py +++ b/finalynx/fetch/source_finary.py @@ -84,7 +84,10 @@ def _authenticate(self) -> Optional[Session]: if os.path.exists(finary_uapi.constants.COOKIE_FILENAME): os.remove(finary_uapi.constants.COOKIE_FILENAME) if os.path.exists(finary_uapi.constants.CREDENTIAL_FILE): - if not Confirm.ask("Reuse saved credentials? Otherwise, they will also be deleted.", default=True): + if not Confirm.ask( + "Reuse saved credentials? Otherwise, they will also be deleted.", + default=True, + ): os.remove(finary_uapi.constants.CREDENTIAL_FILE) # Get the user credentials if there's no session yet (through environment variables or manual input) @@ -171,7 +174,10 @@ def _fetch_data(self, tree: Tree) -> None: raise ValueError("Finary signin failed.") # Call the API and parse the response into `FetchLine` instances - with console.status(f"[bold {TH().ACCENT}]Fetching investments from Finary...", spinner_style=TH().ACCENT): + with console.status( + f"[bold {TH().ACCENT}]Fetching investments from Finary...", + spinner_style=TH().ACCENT, + ): response = ff.get_holdings_accounts(session) if response["message"] == "OK": for dict_account in response["result"]: @@ -182,12 +188,17 @@ def _process_account(self, dict_account: Dict[str, Any], tree: Tree) -> None: node = tree.add(account_name if not dict_account["fiats"] else dict_account["institution"]["name"]) for item in dict_account["fiats"]: + if dict_account["bank_account_type"]["subtype"] == "credit": + amount = -item["display_current_value"] + else: + amount = item["display_current_value"] + self._register_fetchline( tree_node=node, name=account_name, id=item["id"], account=dict_account["institution"]["name"], - amount=item["display_current_value"], + amount=amount, currency=item["fiat"]["symbol"], ) diff --git a/finalynx/simulator/recurrence.py b/finalynx/simulator/recurrence.py index a0d372c..fd0f747 100644 --- a/finalynx/simulator/recurrence.py +++ b/finalynx/simulator/recurrence.py @@ -36,7 +36,8 @@ def __init__( months = months if months is not None else 0 years = years if years is not None else 0 - self._delta = timedelta(days, weeks=4 * months + 52 * years) + # Add decimals to stay on the same day (otherwise Yearly goes to 30/12) + self._delta = timedelta(days, weeks=4.3452 * months + 52.1429 * years + 0.1429) def _next_date(self, current_date: date) -> date: return current_date + self._delta diff --git a/finalynx/simulator/timeline.py b/finalynx/simulator/timeline.py index 9ffe991..8bce40d 100644 --- a/finalynx/simulator/timeline.py +++ b/finalynx/simulator/timeline.py @@ -35,6 +35,9 @@ class Simulation: # Whether to print the final portfolio state in the console after the simulation print_final: bool = False + # Whether to print the final portfolio state in the console after the simulation + print_each_step: bool = False + # Display the portfolio's worth in the console every `step` years step_years: int = 5 diff --git a/finalynx/usage.py b/finalynx/usage.py index f431846..fb393be 100644 --- a/finalynx/usage.py +++ b/finalynx/usage.py @@ -56,5 +56,6 @@ def main_filter(message: str) -> str: --sim-steps=int Display the simulated portfolio's worth every X years, defaults to 5 --future Print the portfolio after the simulation has finished + --each-step Print the portfolio for each step of the simulation """ From 3f1de3823d9555faf3f3e499a1efd1560f275d05 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 13 Jan 2024 15:53:52 +0000 Subject: [PATCH 4/6] 1.22.4 Automatically generated by python-semantic-release --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 012fa55..86f5034 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,41 @@ +## v1.22.4 (2024-01-13) + +### Chore + +* chore: update full example ([`00138dd`](https://github.com/MadeInPierre/finalynx/commit/00138dd2fcaf2226402b5d1881ee9993624c8cc7)) + +### Ci + +* ci: manual update to v1.22.3 ([`c2d7d82`](https://github.com/MadeInPierre/finalynx/commit/c2d7d8286ad289222fecafcdf4456356a7ffcebc)) + +* ci: use trusted auth for PyPI publishing ([`ea2a814`](https://github.com/MadeInPierre/finalynx/commit/ea2a814b091d3c47987645b1ce9ac1bead5b61f8)) + +### Fix + +* fix(fetch): change credit card to negative amount (#147) + +* Change credit card to negative amount + +* Rollback source_finary.py + +* Change credit card to negative amount + +* [pre-commit.ci lite] apply automatic fixes + +* Add a flag to allow the potfolio restitution for each simulation step + +* Add a flag to allow the potfolio restitution for each simulation step + +* fix: correct the recurrence function to be more precise on Yearly recurrence + +* [pre-commit.ci lite] apply automatic fixes + +* style: minor refactoring ([`d9478db`](https://github.com/MadeInPierre/finalynx/commit/d9478db0bcd4db01887ddee5ed542dcc6204116b)) + + ## v1.22.3 (2023-09-18) ### Chore From 7e986a57fd9db0f6ce098bef59d88b164d94d68c Mon Sep 17 00:00:00 2001 From: MadeInPierre Date: Sat, 13 Jan 2024 17:05:55 +0100 Subject: [PATCH 5/6] ci: add permissions for semantic release --- .github/workflows/semantic-release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/semantic-release.yml b/.github/workflows/semantic-release.yml index 41ec049..e3dcb36 100644 --- a/.github/workflows/semantic-release.yml +++ b/.github/workflows/semantic-release.yml @@ -55,6 +55,10 @@ jobs: needs: pre-commit if: github.event_name == 'push' && github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, 'chore(release):') runs-on: ubuntu-latest + permissions: + issues: write + id-token: write + contents: write steps: - uses: actions/setup-python@v2 with: From 8e8ec723eb6f3db2f03170cf28a155b9c08c3f2f Mon Sep 17 00:00:00 2001 From: MadeInPierre Date: Sat, 13 Jan 2024 17:10:55 +0100 Subject: [PATCH 6/6] ci: manual bump to v1.22.4 --- docs/conf.py | 2 +- finalynx/__meta__.py | 2 +- pyproject.toml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index e76d392..23cc2c2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,7 +22,7 @@ author = "Pierre Laclau" # The full version, including alpha/beta/rc tags -release = "1.22.3" +release = "1.22.4" # -- General configuration --------------------------------------------------- diff --git a/finalynx/__meta__.py b/finalynx/__meta__.py index c0a6679..3a76528 100644 --- a/finalynx/__meta__.py +++ b/finalynx/__meta__.py @@ -6,7 +6,7 @@ Metadata information about Finalynx. This file is used by Fynalinx and updated by the CI/CD pipeline. """ -__version__ = "1.22.3" +__version__ = "1.22.4" __author__ = "Pierre Laclau (MadeInPierre)" diff --git a/pyproject.toml b/pyproject.toml index 4971ba1..e72f1dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "finalynx" -version = "1.22.3" +version = "1.22.4" description = "A command line investment assistant to organize your portfolio and simulate its future to reach your life goals." authors = ["MadeInPierre "] license = "GPLv3" @@ -56,7 +56,7 @@ build_command = "pip install poetry && poetry build" [tool.commitizen] name = "cz_conventional_commits" -version = "1.22.3" +version = "1.22.4" tag_format = "v$version" [tool.mypy]