Skip to content

Commit

Permalink
feat(dashboard): add portfolio simulation evolution
Browse files Browse the repository at this point in the history
  • Loading branch information
gcoue committed Jan 3, 2024
1 parent 2fd5a07 commit fc196dd
Show file tree
Hide file tree
Showing 9 changed files with 403 additions and 27 deletions.
26 changes: 26 additions & 0 deletions finalynx/analyzer/asset_class.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import date
from typing import Any
from typing import Dict

Expand Down Expand Up @@ -72,6 +73,31 @@ def analyze(self) -> Dict[str, Any]:
sum of investments corresponding to each class."""
return self._recursive_merge(self.node)

def analyzeTime(self, target_date: date) -> Dict[str, float]:
""":returns: A dictionary with keys as the asset class names and values as the
sum of investments corresponding to each class."""
return self._recursive_mergeTime(self.node, target_date)

def _recursive_mergeTime(self, node: Node, target_date: date) -> Dict[str, Any]:
"""Internal method for recursive searching."""
total = {c.value: 0.0 for c in AssetClass}

# Lines simply return their own amount
if isinstance(node, Line):
total[node.asset_class.value] = node.get_amount()
return total

# Folders merge what the children return
elif isinstance(node, Folder):
for child in node.children:
for key, value in self._recursive_mergeTime(child, target_date).items():
total[key] += value
return total

# Safeguard for future versions
else:
raise ValueError(f"Unknown node type '{type(node)}'.")

def _recursive_merge(self, node: Node) -> Dict[str, Any]:
"""Internal method for recursive searching."""
result: Dict[str, Any] = {
Expand Down
34 changes: 34 additions & 0 deletions finalynx/analyzer/envelopes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from datetime import date
from typing import Any
from typing import Dict

from finalynx.portfolio.constants import EnvelopeClass

from ..portfolio import Folder
from ..portfolio import Line
from ..portfolio import Node
Expand All @@ -19,6 +22,37 @@ def analyze(self) -> Dict[str, float]:
sum of investments corresponding to each class."""
return self._recursive_merge(self.node)

def analyzeTime(self, target_date: date) -> Dict[str, float]:
""":returns: A dictionary with keys as the asset class names and values as the
sum of investments corresponding to each class."""
return self._recursive_mergeTime(self.node, target_date)

def _recursive_mergeTime(self, node: Node, target_date: date) -> Dict[str, Any]:
"""Internal method for recursive searching."""
total = {}

# Lines simply return their own amount
if isinstance(node, Line):
if node.envelope:
total[node.envelope.name] = node.get_amount()
else:
total["Unknown"] = node.get_amount()
return total

# Folders merge what the children return
elif isinstance(node, Folder):
for child in node.children:
for key, value in self._recursive_mergeTime(child,target_date).items():
if key in total.keys():
total[key] += value
else:
total[key] = value
return total

# Safeguard for future versions
else:
raise ValueError(f"Unknown node type '{type(node)}'.")

def _recursive_merge(self, node: Node) -> Dict[str, float]:
"""Internal method for recursive searching."""
total = {}
Expand Down
47 changes: 47 additions & 0 deletions finalynx/analyzer/lines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from datetime import date
from typing import Any
from typing import Dict

from ..portfolio import Folder
from ..portfolio import Line
from ..portfolio import Node
from .analyzer import Analyzer


class AnalyzeLines(Analyzer):
"""Aims to agglomerate the children's pf lines and return
the amount represented by each line.
:returns: a dictionary with lines as keys and the
corresponding total amount contained in the children.
"""

def analyzeTime(self, target_date: date) -> Dict[str, float]:
""":returns: A dictionary with keys as the asset class names and values as the
sum of investments corresponding to each class."""
return self._recursive_mergeTime(self.node, target_date)

def _recursive_mergeTime(self, node: Node, target_date: date) -> Dict[str, Any]:
"""Internal method for recursive searching."""
total = {}

# Lines simply return their own amount
if isinstance(node, Line):
if node.name:
total[node.name] = node.get_amount()
else:
total["Unknown"] = node.get_amount()
return total

# Folders merge what the children return
elif isinstance(node, Folder):
for child in node.children:
for key, value in self._recursive_mergeTime(child,target_date).items():
if key in total.keys():
total[key] += value
else:
total[key] = value
return total

# Safeguard for future versions
else:
raise ValueError(f"Unknown node type '{type(node)}'.")
165 changes: 165 additions & 0 deletions finalynx/analyzer/subasset_class.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
from datetime import date
from typing import Any
from typing import Dict

import numpy as np

from ..portfolio import AssetClass
from ..portfolio import AssetSubclass
from ..portfolio import Folder
from ..portfolio import Line
from ..portfolio import Node
from .analyzer import Analyzer


class AnalyzeSubAssetClasses(Analyzer):
"""Aims to agglomerate the children's Sub asset classes and return
the amount represented by each Sub asset class.
:returns: a dictionary with Sub asset classes as keys and the
corresponding total amount contained in the children.
"""

SUBASSET_COLORS_FINARY = {
# Cash
"Comptes courants": "#eed7b4",
"Monétaire": "#eed7b4",
"Liquidités": "#eed7b4",
# Guaranteed investments (mostly french)
"Livrets": "#b966f5",
"Livrets imposables": "#b966f5",
"Fonds euro": "#b966f5",
# Bonds
"Fonds datés": "#87bc45",
# Stocks
"Titres vifs": "#3a84de",
"ETF": "#3a84de",
# Real estate
"Immobilier physique": "#deab5e",
"SCPI": "#deab5e",
"SCI": "#deab5e",
# Metals
"Or": "#77cfac",
"Argent": "#77cfac",
"Matières premières": "#77cfac",
# Cryptos
"L1": "#bdcf32",
"Stablecoins": "#bdcf32",
"DeFi": "#bdcf32",
# Passives
"Véhicule": "#434348",
"Passif": "#434348",
# Exotics
"Forêts": "#228c83",
"Art": "#228c83",
"Watches": "#228c83",
"Crowdlending": "#228c83",
"Startup": "#228c83",
# Diversified
"Diversifié": "#b54093",
"OPCVM": "#b54093",
# Unknown (default)
"Unknown": "#b54053",
}

SUBASSET_COLORS_CUSTOM = {
# Cash
"Comptes courants": "#eed7b4",
"Monétaire": "#eed7b4",
"Liquidités": "#eed7b4",
# Guaranteed investments (mostly french)
"Livrets": "#b966f5",
"Livrets imposables": "#b966f5",
"Fonds euro": "#b966f5",
# Bonds
"Fonds datés": "#87bc45",
# Stocks
"Titres vifs": "#3a84de",
"ETF": "#3a84de",
# Real estate
"Immobilier physique": "#deab5e",
"SCPI": "#deab5e",
"SCI": "#deab5e",
# Metals
"Or": "#77cfac",
"Argent": "#77cfac",
"Matières premières": "#77cfac",
# Cryptos
"L1": "#bdcf32",
"Stablecoins": "#bdcf32",
"DeFi": "#bdcf32",
# Passives
"Véhicule": "#434348",
"Passif": "#434348",
# Exotics
"Forêts": "#228c83",
"Art": "#228c83",
"Watches": "#228c83",
"Crowdlending": "#228c83",
"Startup": "#228c83",
# Diversified
"Diversifié": "#b54093",
"OPCVM": "#b54093",
# Unknown (default)
"Unknown": "#b54053",
}

def analyze(self) -> Dict[str, Any]:
""":returns: A dictionary with keys as the asset class names and values as the
sum of investments corresponding to each class."""
return self._recursive_merge(self.node)

def analyzeTime(self, target_date: date) -> Dict[str, float]:
""":returns: A dictionary with keys as the Sub asset class names and values as the
sum of investments corresponding to each class."""
return self._recursive_mergeTime(self.node, target_date)

def _recursive_mergeTime(self, node: Node, target_date: date) -> Dict[str, Any]:
"""Internal method for recursive searching."""
total = {}

# Lines simply return their own amount
if isinstance(node, Line):
total[node.asset_subclass.value] = node.get_amount()
return total

# Folders merge what the children return
elif isinstance(node, Folder):
for child in node.children:
for key, value in self._recursive_mergeTime(child, target_date).items():
if key in total.keys():
total[key] += value
else:
total[key] = value
# for subkey, subvalue in value["subclasses"].items():
# total[subkey] += subvalue
return total

# Safeguard for future versions
else:
raise ValueError(f"Unknown node type '{type(node)}'.")

def _recursive_merge(self, node: Node) -> Dict[str, Any]:
"""Internal method for recursive searching."""
result: Dict[str, Any] = {
c.value: {"total": 0.0, "subclasses": {s.value: 0.0 for s in AssetSubclass}} for c in AssetClass
}

# Lines simply return their own amount
if isinstance(node, Line):
result[node.asset_class.value]["total"] = node.get_amount()
result[node.asset_class.value]["subclasses"][node.asset_subclass.value] = node.get_amount()
return result

# Folders merge what the children return
elif isinstance(node, Folder):
for child in node.children:
for key, subdict in self._recursive_merge(child).items():
result[key]["total"] += subdict["total"]

for subkey, subvalue in subdict["subclasses"].items():
result[key]["subclasses"][subkey] += subvalue
return result

# Safeguard for future versions
else:
raise ValueError(f"Unknown node type '{type(node)}'.")
2 changes: 2 additions & 0 deletions finalynx/assistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ def _parse_args(self) -> None:
self.simulation.print_each_step = True
if args["--sim-steps"] and self.simulation:
self.simulation.step_years = int(args["--sim-steps"])
if args["--metric-frequency"] and self.simulation:
self.simulation.metrics_record_freqency = str(args["--metric-frequency"])
if args["--theme"]:
theme_name = str(args["--theme"])
if theme_name not in finalynx.theme.AVAILABLE_THEMES:
Expand Down
19 changes: 18 additions & 1 deletion finalynx/dashboard/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from finalynx.analyzer.asset_class import AnalyzeAssetClasses
from finalynx.analyzer.envelopes import AnalyzeEnvelopes
from finalynx.analyzer.investment_state import AnalyzeInvestmentStates
from finalynx.analyzer.subasset_class import AnalyzeSubAssetClasses
from finalynx.portfolio.folder import Folder
from finalynx.portfolio.folder import FolderDisplay
from finalynx.portfolio.line import Line
Expand Down Expand Up @@ -181,7 +182,23 @@ def _on_select_color_map(data: Any) -> None:
)
with ui.row():
self.chart_envelopes = ui.chart(AnalyzeEnvelopes(self.selected_node).chart())
self.chart_simulation = ui.chart(timeline.chart() if timeline else {})
#self.chart_simulation = ui.chart(timeline.chart() if timeline else {})
self.chart_etats_enveloppes = ui.chart(timeline.chartOnTimeline("Evolution des états d'enveloppes",timeline._log_env_states,{
"Unknown": "#434348",
"Closed": "#999999",
"Locked": "#F94144",
"Taxed": "#F9C74F",
"Free": "#7BB151",
}) if timeline else {})
#self.chart_simulation1 = ui.chart(timeline.chartEnveloppeTimeline() if timeline else {})
self.chart_enveloppes = ui.chart(timeline.chartOnTimeline("Evolution des enveloppes",timeline._log_enveloppe_values) if timeline else {})
#self.chart_simulation2 = ui.chart(timeline.OLDchartBucket() if timeline else {})
#self.chart_simulation3 = ui.chart(timeline.chartAssetTimeline() if timeline else {})
self.chart_asset_classes = ui.chart(timeline.chartOnTimeline("Evolution des classes d'actifs",timeline._log_assets_classes_values,AnalyzeAssetClasses.ASSET_COLORS_FINARY) if timeline else {})
self.chart_subasset_classes = ui.chart(timeline.chartOnTimeline("Evolution des sous-classes d'actifs",timeline._log_assets_subclasses_values,AnalyzeSubAssetClasses.SUBASSET_COLORS_FINARY) if timeline else {})
#self.chart_simulation4 = ui.chart(timeline.chartSubAssetTimeline() if timeline else {})
self.chart_lines = ui.chart(timeline.chartOnTimeline("Evolution des lignes du portefeuille", timeline._log_lines_values, visible_by_default=False) if timeline else {})
#self.chart_simulation5 = ui.chart(timeline.chartLinesTimeline() if timeline else {})

ui.run(
title="Finalynx Dashboard",
Expand Down
1 change: 1 addition & 0 deletions finalynx/portfolio/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class AssetSubclass(Enum):

# Passives
VEHICLE = "Véhicule"
PASSIVE = "Passif"

# Unknown (default)
UNKNOWN = "Unknown"
Expand Down
Loading

0 comments on commit fc196dd

Please sign in to comment.