Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dashboard): add layered evolution graphs #150

Merged
merged 21 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7b9f2a2
Change credit card to negative amount
gcoue Nov 6, 2023
1983b81
Rollback source_finary.py
gcoue Nov 6, 2023
f97a7c7
Change credit card to negative amount
gcoue Nov 6, 2023
721e36a
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Nov 7, 2023
a079da7
Add a flag to allow the potfolio restitution for each simulation step
gcoue Nov 8, 2023
2594dd2
Add a flag to allow the potfolio restitution for each simulation step
gcoue Nov 8, 2023
4f0b012
Merge branch 'simu_for_each_step' of https://github.com/gcoue/finalyn…
gcoue Nov 8, 2023
90d32b5
Merge branch 'simu_for_each_step' of https://github.com/gcoue/finalyn…
gcoue Nov 8, 2023
2fd5a07
Merge branch 'simu_for_each_step' of https://github.com/gcoue/finalyn…
gcoue Nov 8, 2023
fc196dd
feat(dashboard): add portfolio simulation evolution
gcoue Jan 3, 2024
84e064d
Remove comments
gcoue Jan 3, 2024
207b7e9
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Jan 4, 2024
465d4e7
chore: update full example
MadeInPierre Jan 13, 2024
66feb95
ci: manual update to v1.22.3
MadeInPierre Jan 13, 2024
2e8a0b4
fix(fetch): change credit card to negative amount (#147)
gcoue Jan 13, 2024
a6608c0
1.22.4
invalid-email-address Jan 13, 2024
9c5252b
ci: add permissions for semantic release
MadeInPierre Jan 13, 2024
1d365a4
ci: manual bump to v1.22.4
MadeInPierre Jan 13, 2024
d250f9a
Merge branch 'main' into dashboard_time_evolution
MadeInPierre Jan 13, 2024
f961f03
refactor: fix lint errors
MadeInPierre Jan 13, 2024
b095b39
refactor: small tweaks & simplifications
MadeInPierre Jan 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion finalynx/analyzer/asset_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,34 @@ class AnalyzeAssetClasses(Analyzer):

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."""
sum of investments corresponding to each class. Two-layer dictionary with classes and subclasses."""
return self._recursive_merge(self.node)

def analyze_flat(self) -> 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_merge_flat(self.node)

def _recursive_merge_flat(self, node: Node) -> 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_merge_flat(child).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
162 changes: 162 additions & 0 deletions finalynx/analyzer/asset_subclass.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
from typing import Any
from typing import Dict

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 AnalyzeAssetSubclasses(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. Two-layer dictionary with classes and subclasses."""
return self._recursive_merge(self.node)

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

def _recursive_merge_flat(self, node: Node) -> 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_merge_flat(child).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)}'.")
46 changes: 46 additions & 0 deletions finalynx/analyzer/lines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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 analyze(self) -> 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_merge(self.node)

def _recursive_merge(self, node: Node) -> 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_merge(child).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)}'.")
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_frequency = str(args["--metric-frequency"])
if args["--theme"]:
theme_name = str(args["--theme"])
if theme_name not in finalynx.theme.AVAILABLE_THEMES:
Expand Down
49 changes: 48 additions & 1 deletion finalynx/dashboard/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from typing import Set

from finalynx.analyzer.asset_class import AnalyzeAssetClasses
from finalynx.analyzer.asset_subclass import AnalyzeAssetSubclasses
from finalynx.analyzer.envelopes import AnalyzeEnvelopes
from finalynx.analyzer.investment_state import AnalyzeInvestmentStates
from finalynx.portfolio.folder import Folder
Expand Down Expand Up @@ -181,7 +182,53 @@ 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_etats_enveloppes = ui.chart(
timeline.chart_timeline(
"Envelope States Evolution",
timeline._log_env_states,
{
"Unknown": "#434348",
"Closed": "#999999",
"Locked": "#F94144",
"Taxed": "#F9C74F",
"Free": "#7BB151",
},
)
if timeline
else {}
)
self.chart_enveloppes = ui.chart(
timeline.chart_timeline("Envelopes Evolution", timeline._log_enveloppe_values)
if timeline
else {}
)
self.chart_asset_classes = ui.chart(
timeline.chart_timeline(
"Asset Classes Evolution",
timeline._log_assets_classes_values,
AnalyzeAssetClasses.ASSET_COLORS_FINARY,
)
if timeline
else {}
)
self.chart_subasset_classes = ui.chart(
timeline.chart_timeline(
"Asset Subclasses Evolution",
timeline._log_assets_subclasses_values,
AnalyzeAssetSubclasses.SUBASSET_COLORS_FINARY,
)
if timeline
else {}
)
self.chart_lines = ui.chart(
timeline.chart_timeline(
"Line-by-line Evolution",
timeline._log_lines_values,
visible_by_default=False,
)
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
Loading