wbportfolio 1.52.0__py2.py3-none-any.whl → 1.59.4__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of wbportfolio might be problematic. Click here for more details.

Files changed (273) hide show
  1. wbportfolio/admin/__init__.py +3 -1
  2. wbportfolio/admin/indexes.py +1 -1
  3. wbportfolio/admin/orders/__init__.py +2 -0
  4. wbportfolio/admin/orders/order_proposals.py +16 -0
  5. wbportfolio/admin/orders/orders.py +32 -0
  6. wbportfolio/admin/portfolio.py +11 -5
  7. wbportfolio/admin/product_groups.py +1 -1
  8. wbportfolio/admin/products.py +2 -1
  9. wbportfolio/admin/{transactions/rebalancing.py → rebalancing.py} +1 -1
  10. wbportfolio/admin/transactions/__init__.py +0 -2
  11. wbportfolio/admin/transactions/dividends.py +40 -4
  12. wbportfolio/admin/transactions/fees.py +24 -14
  13. wbportfolio/admin/transactions/trades.py +34 -27
  14. wbportfolio/analysis/claims.py +5 -6
  15. wbportfolio/api_clients/ubs.py +162 -0
  16. wbportfolio/constants.py +1 -0
  17. wbportfolio/contrib/company_portfolio/configs/display.py +22 -10
  18. wbportfolio/contrib/company_portfolio/configs/previews.py +3 -3
  19. wbportfolio/contrib/company_portfolio/filters.py +10 -10
  20. wbportfolio/contrib/company_portfolio/models.py +69 -39
  21. wbportfolio/contrib/company_portfolio/scripts.py +7 -2
  22. wbportfolio/contrib/company_portfolio/serializers.py +32 -22
  23. wbportfolio/contrib/company_portfolio/tasks.py +12 -1
  24. wbportfolio/contrib/company_portfolio/tests/conftest.py +2 -2
  25. wbportfolio/defaults/fees/default.py +7 -15
  26. wbportfolio/factories/__init__.py +2 -2
  27. wbportfolio/factories/assets.py +1 -1
  28. wbportfolio/factories/dividends.py +8 -3
  29. wbportfolio/factories/fees.py +8 -4
  30. wbportfolio/factories/orders/__init__.py +2 -0
  31. wbportfolio/factories/orders/order_proposals.py +21 -0
  32. wbportfolio/factories/orders/orders.py +34 -0
  33. wbportfolio/factories/portfolios.py +2 -1
  34. wbportfolio/factories/product_groups.py +3 -3
  35. wbportfolio/factories/products.py +3 -3
  36. wbportfolio/factories/rebalancing.py +1 -1
  37. wbportfolio/factories/trades.py +12 -16
  38. wbportfolio/filters/assets.py +18 -4
  39. wbportfolio/filters/orders/__init__.py +2 -0
  40. wbportfolio/filters/orders/order_proposals.py +55 -0
  41. wbportfolio/filters/orders/orders.py +11 -0
  42. wbportfolio/filters/portfolios.py +38 -1
  43. wbportfolio/filters/positions.py +0 -1
  44. wbportfolio/filters/transactions/__init__.py +1 -2
  45. wbportfolio/filters/transactions/fees.py +5 -12
  46. wbportfolio/filters/transactions/trades.py +16 -8
  47. wbportfolio/filters/transactions/utils.py +42 -0
  48. wbportfolio/import_export/backends/ubs/__init__.py +1 -0
  49. wbportfolio/import_export/backends/ubs/asset_position.py +6 -7
  50. wbportfolio/import_export/backends/ubs/fees.py +10 -20
  51. wbportfolio/import_export/backends/ubs/instrument_price.py +6 -6
  52. wbportfolio/import_export/backends/ubs/trade.py +48 -0
  53. wbportfolio/import_export/backends/utils.py +0 -17
  54. wbportfolio/import_export/handlers/asset_position.py +22 -10
  55. wbportfolio/import_export/handlers/dividend.py +8 -8
  56. wbportfolio/import_export/handlers/fees.py +13 -23
  57. wbportfolio/import_export/handlers/orders.py +71 -0
  58. wbportfolio/import_export/handlers/trade.py +53 -77
  59. wbportfolio/import_export/parsers/default_mapping.py +1 -1
  60. wbportfolio/import_export/parsers/jpmorgan/customer_trade.py +2 -2
  61. wbportfolio/import_export/parsers/jpmorgan/fees.py +4 -4
  62. wbportfolio/import_export/parsers/jpmorgan/strategy.py +59 -85
  63. wbportfolio/import_export/parsers/jpmorgan/valuation.py +2 -2
  64. wbportfolio/import_export/parsers/leonteq/customer_trade.py +5 -5
  65. wbportfolio/import_export/parsers/leonteq/fees.py +11 -7
  66. wbportfolio/import_export/parsers/leonteq/trade.py +2 -6
  67. wbportfolio/import_export/parsers/natixis/d1_fees.py +2 -2
  68. wbportfolio/import_export/parsers/natixis/dividend.py +4 -9
  69. wbportfolio/import_export/parsers/natixis/equity.py +22 -4
  70. wbportfolio/import_export/parsers/natixis/fees.py +7 -9
  71. wbportfolio/import_export/parsers/natixis/utils.py +13 -19
  72. wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py +1 -1
  73. wbportfolio/import_export/parsers/sg_lux/equity.py +10 -10
  74. wbportfolio/import_export/parsers/sg_lux/fees.py +2 -2
  75. wbportfolio/import_export/parsers/sg_lux/perf_fees.py +2 -2
  76. wbportfolio/import_export/parsers/sg_lux/sylk.py +12 -11
  77. wbportfolio/import_export/parsers/sg_lux/utils.py +2 -2
  78. wbportfolio/import_export/parsers/sg_lux/valuation.py +4 -2
  79. wbportfolio/import_export/parsers/societe_generale/strategy.py +5 -5
  80. wbportfolio/import_export/parsers/tellco/customer_trade.py +2 -1
  81. wbportfolio/import_export/parsers/tellco/valuation.py +4 -3
  82. wbportfolio/import_export/parsers/ubs/api/fees.py +2 -2
  83. wbportfolio/import_export/parsers/ubs/api/trade.py +39 -0
  84. wbportfolio/import_export/parsers/ubs/customer_trade.py +7 -5
  85. wbportfolio/import_export/parsers/ubs/equity.py +3 -2
  86. wbportfolio/import_export/parsers/ubs/valuation.py +2 -1
  87. wbportfolio/import_export/parsers/vontobel/customer_trade.py +2 -3
  88. wbportfolio/import_export/parsers/vontobel/historical_customer_trade.py +0 -1
  89. wbportfolio/import_export/parsers/vontobel/management_fees.py +12 -20
  90. wbportfolio/import_export/parsers/vontobel/performance_fees.py +5 -8
  91. wbportfolio/import_export/parsers/vontobel/valuation_api.py +4 -1
  92. wbportfolio/import_export/resources/trades.py +3 -3
  93. wbportfolio/import_export/utils.py +3 -1
  94. wbportfolio/jinja2/wbportfolio/sql/aum_nnm.sql +2 -2
  95. wbportfolio/metric/backends/base.py +2 -2
  96. wbportfolio/migrations/0059_fees_unique_fees.py +1 -1
  97. wbportfolio/migrations/0077_remove_transaction_currency_and_more.py +622 -0
  98. wbportfolio/migrations/0078_trade_drift_factor.py +26 -0
  99. wbportfolio/migrations/0079_alter_trade_drift_factor.py +19 -0
  100. wbportfolio/migrations/0080_alter_trade_drift_factor_alter_trade_weighting.py +19 -0
  101. wbportfolio/migrations/0081_alter_trade_drift_factor.py +19 -0
  102. wbportfolio/migrations/0082_remove_tradeproposal_creator_and_more.py +93 -0
  103. wbportfolio/migrations/0083_order_alter_trade_options_and_more.py +181 -0
  104. wbportfolio/migrations/0084_orderproposal_min_order_value.py +25 -0
  105. wbportfolio/migrations/0085_order_desired_target_weight.py +26 -0
  106. wbportfolio/migrations/0086_orderproposal_total_cash_weight.py +19 -0
  107. wbportfolio/migrations/0087_product_order_routing_custodian_adapter.py +94 -0
  108. wbportfolio/migrations/0088_orderproposal_total_effective_portfolio_contribution.py +19 -0
  109. wbportfolio/migrations/0089_orderproposal_min_weighting.py +71 -0
  110. wbportfolio/migrations/0090_dividendtransaction_price_fx_portfolio_and_more.py +44 -0
  111. wbportfolio/migrations/0091_remove_order_execution_confirmed_and_more.py +32 -0
  112. wbportfolio/migrations/0092_order_quantization_error_alter_orderproposal_status.py +49 -0
  113. wbportfolio/migrations/0093_remove_portfolioportfoliothroughmodel_unique_primary_and_more.py +35 -0
  114. wbportfolio/models/__init__.py +2 -0
  115. wbportfolio/models/adjustments.py +1 -1
  116. wbportfolio/models/asset.py +28 -170
  117. wbportfolio/models/builder.py +323 -0
  118. wbportfolio/models/custodians.py +3 -3
  119. wbportfolio/models/exceptions.py +1 -1
  120. wbportfolio/models/graphs/portfolio.py +1 -1
  121. wbportfolio/models/graphs/utils.py +11 -11
  122. wbportfolio/models/mixins/instruments.py +7 -0
  123. wbportfolio/models/mixins/liquidity_stress_test.py +4 -4
  124. wbportfolio/models/orders/__init__.py +2 -0
  125. wbportfolio/models/orders/order_proposals.py +1414 -0
  126. wbportfolio/models/orders/orders.py +410 -0
  127. wbportfolio/models/portfolio.py +311 -289
  128. wbportfolio/models/portfolio_relationship.py +6 -0
  129. wbportfolio/models/products.py +12 -0
  130. wbportfolio/models/{transactions/rebalancing.py → rebalancing.py} +40 -27
  131. wbportfolio/models/roles.py +4 -10
  132. wbportfolio/models/transactions/__init__.py +0 -4
  133. wbportfolio/models/transactions/claim.py +7 -6
  134. wbportfolio/models/transactions/dividends.py +42 -5
  135. wbportfolio/models/transactions/fees.py +55 -22
  136. wbportfolio/models/transactions/trades.py +121 -442
  137. wbportfolio/models/transactions/transactions.py +78 -158
  138. wbportfolio/models/utils.py +100 -1
  139. wbportfolio/order_routing/__init__.py +35 -0
  140. wbportfolio/order_routing/adapters/__init__.py +65 -0
  141. wbportfolio/order_routing/adapters/ubs.py +195 -0
  142. wbportfolio/order_routing/router.py +33 -0
  143. wbportfolio/order_routing/tests/__init__.py +0 -0
  144. wbportfolio/order_routing/tests/test_router.py +110 -0
  145. wbportfolio/permissions.py +7 -0
  146. wbportfolio/pms/analytics/portfolio.py +17 -9
  147. wbportfolio/pms/analytics/utils.py +9 -0
  148. wbportfolio/pms/trading/__init__.py +0 -1
  149. wbportfolio/pms/trading/optimizer.py +61 -0
  150. wbportfolio/pms/typing.py +198 -63
  151. wbportfolio/rebalancing/base.py +12 -1
  152. wbportfolio/rebalancing/decorators.py +1 -1
  153. wbportfolio/rebalancing/models/composite.py +4 -8
  154. wbportfolio/rebalancing/models/equally_weighted.py +13 -11
  155. wbportfolio/rebalancing/models/market_capitalization_weighted.py +21 -14
  156. wbportfolio/rebalancing/models/model_portfolio.py +14 -18
  157. wbportfolio/risk_management/backends/__init__.py +1 -0
  158. wbportfolio/risk_management/backends/controversy_portfolio.py +2 -2
  159. wbportfolio/risk_management/backends/esg_aggregation_portfolio.py +64 -0
  160. wbportfolio/risk_management/backends/exposure_portfolio.py +4 -4
  161. wbportfolio/risk_management/backends/instrument_list_portfolio.py +3 -3
  162. wbportfolio/risk_management/tests/test_esg_aggregation_portfolio.py +49 -0
  163. wbportfolio/risk_management/tests/test_exposure_portfolio.py +1 -1
  164. wbportfolio/risk_management/tests/test_stop_loss_instrument.py +2 -2
  165. wbportfolio/risk_management/tests/test_stop_loss_portfolio.py +1 -1
  166. wbportfolio/serializers/__init__.py +1 -0
  167. wbportfolio/serializers/orders/__init__.py +2 -0
  168. wbportfolio/serializers/orders/order_proposals.py +115 -0
  169. wbportfolio/serializers/orders/orders.py +283 -0
  170. wbportfolio/serializers/portfolios.py +7 -7
  171. wbportfolio/serializers/positions.py +2 -2
  172. wbportfolio/serializers/rebalancing.py +1 -1
  173. wbportfolio/serializers/signals.py +9 -12
  174. wbportfolio/serializers/transactions/__init__.py +1 -10
  175. wbportfolio/serializers/transactions/claim.py +2 -2
  176. wbportfolio/serializers/transactions/dividends.py +37 -9
  177. wbportfolio/serializers/transactions/fees.py +39 -10
  178. wbportfolio/serializers/transactions/trades.py +55 -157
  179. wbportfolio/tasks.py +43 -5
  180. wbportfolio/tests/analysis/__init__.py +0 -0
  181. wbportfolio/tests/analysis/test_claims.py +85 -0
  182. wbportfolio/tests/conftest.py +12 -12
  183. wbportfolio/tests/models/orders/__init__.py +0 -0
  184. wbportfolio/tests/models/orders/test_order_proposals.py +1046 -0
  185. wbportfolio/tests/models/test_assets.py +7 -3
  186. wbportfolio/tests/models/test_imports.py +9 -13
  187. wbportfolio/tests/models/test_portfolios.py +102 -95
  188. wbportfolio/tests/models/test_products.py +11 -0
  189. wbportfolio/tests/models/test_splits.py +1 -6
  190. wbportfolio/tests/models/test_utils.py +140 -0
  191. wbportfolio/tests/models/transactions/test_fees.py +7 -13
  192. wbportfolio/tests/models/transactions/test_rebalancing.py +5 -5
  193. wbportfolio/tests/models/transactions/test_trades.py +0 -20
  194. wbportfolio/tests/pms/test_analytics.py +22 -3
  195. wbportfolio/tests/rebalancing/test_models.py +51 -57
  196. wbportfolio/tests/signals.py +10 -20
  197. wbportfolio/tests/tests.py +3 -1
  198. wbportfolio/tests/viewsets/test_products.py +1 -0
  199. wbportfolio/urls.py +10 -13
  200. wbportfolio/viewsets/__init__.py +9 -4
  201. wbportfolio/viewsets/assets.py +3 -204
  202. wbportfolio/viewsets/charts/__init__.py +6 -1
  203. wbportfolio/viewsets/charts/assets.py +344 -154
  204. wbportfolio/viewsets/configs/buttons/__init__.py +2 -2
  205. wbportfolio/viewsets/configs/buttons/assets.py +1 -1
  206. wbportfolio/viewsets/configs/buttons/mixins.py +4 -4
  207. wbportfolio/viewsets/configs/buttons/portfolios.py +45 -1
  208. wbportfolio/viewsets/configs/buttons/products.py +32 -2
  209. wbportfolio/viewsets/configs/display/__init__.py +2 -5
  210. wbportfolio/viewsets/configs/display/assets.py +6 -19
  211. wbportfolio/viewsets/configs/display/fees.py +3 -3
  212. wbportfolio/viewsets/configs/display/portfolios.py +5 -5
  213. wbportfolio/viewsets/configs/display/products.py +1 -1
  214. wbportfolio/viewsets/configs/display/rebalancing.py +2 -2
  215. wbportfolio/viewsets/configs/display/reconciliations.py +4 -4
  216. wbportfolio/viewsets/configs/display/trades.py +1 -189
  217. wbportfolio/viewsets/configs/endpoints/__init__.py +3 -7
  218. wbportfolio/viewsets/configs/endpoints/fees.py +2 -2
  219. wbportfolio/viewsets/configs/endpoints/trades.py +0 -41
  220. wbportfolio/viewsets/configs/menu/__init__.py +1 -1
  221. wbportfolio/viewsets/configs/menu/orders.py +11 -0
  222. wbportfolio/viewsets/configs/titles/__init__.py +2 -3
  223. wbportfolio/viewsets/configs/titles/fees.py +4 -8
  224. wbportfolio/viewsets/esg.py +3 -5
  225. wbportfolio/viewsets/mixins.py +5 -1
  226. wbportfolio/viewsets/orders/__init__.py +6 -0
  227. wbportfolio/viewsets/orders/configs/__init__.py +4 -0
  228. wbportfolio/viewsets/orders/configs/buttons/__init__.py +2 -0
  229. wbportfolio/viewsets/orders/configs/buttons/order_proposals.py +188 -0
  230. wbportfolio/viewsets/orders/configs/buttons/orders.py +113 -0
  231. wbportfolio/viewsets/orders/configs/displays/__init__.py +2 -0
  232. wbportfolio/viewsets/orders/configs/displays/order_proposals.py +157 -0
  233. wbportfolio/viewsets/orders/configs/displays/orders.py +232 -0
  234. wbportfolio/viewsets/orders/configs/endpoints/__init__.py +2 -0
  235. wbportfolio/viewsets/orders/configs/endpoints/order_proposals.py +21 -0
  236. wbportfolio/viewsets/orders/configs/endpoints/orders.py +28 -0
  237. wbportfolio/viewsets/orders/configs/titles/__init__.py +0 -0
  238. wbportfolio/viewsets/orders/configs/titles/orders.py +0 -0
  239. wbportfolio/viewsets/orders/order_proposals.py +252 -0
  240. wbportfolio/viewsets/orders/orders.py +277 -0
  241. wbportfolio/viewsets/portfolios.py +36 -12
  242. wbportfolio/viewsets/positions.py +3 -2
  243. wbportfolio/viewsets/products.py +6 -6
  244. wbportfolio/viewsets/{transactions/rebalancing.py → rebalancing.py} +2 -2
  245. wbportfolio/viewsets/transactions/__init__.py +3 -14
  246. wbportfolio/viewsets/transactions/fees.py +22 -22
  247. wbportfolio/viewsets/transactions/trades.py +1 -180
  248. {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/METADATA +3 -1
  249. {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/RECORD +252 -203
  250. {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/WHEEL +1 -1
  251. wbportfolio/admin/transactions/transactions.py +0 -38
  252. wbportfolio/factories/transactions.py +0 -22
  253. wbportfolio/fdm/tasks.py +0 -13
  254. wbportfolio/filters/transactions/transactions.py +0 -99
  255. wbportfolio/models/transactions/expiry.py +0 -7
  256. wbportfolio/models/transactions/trade_proposals.py +0 -704
  257. wbportfolio/pms/trading/handler.py +0 -161
  258. wbportfolio/serializers/transactions/expiry.py +0 -18
  259. wbportfolio/serializers/transactions/trade_proposals.py +0 -76
  260. wbportfolio/serializers/transactions/transactions.py +0 -85
  261. wbportfolio/tests/models/transactions/test_trade_proposals.py +0 -410
  262. wbportfolio/viewsets/configs/buttons/trade_proposals.py +0 -66
  263. wbportfolio/viewsets/configs/display/trade_proposals.py +0 -100
  264. wbportfolio/viewsets/configs/display/transactions.py +0 -55
  265. wbportfolio/viewsets/configs/endpoints/trade_proposals.py +0 -18
  266. wbportfolio/viewsets/configs/endpoints/transactions.py +0 -14
  267. wbportfolio/viewsets/configs/menu/transactions.py +0 -9
  268. wbportfolio/viewsets/configs/titles/transactions.py +0 -9
  269. wbportfolio/viewsets/signals.py +0 -43
  270. wbportfolio/viewsets/transactions/trade_proposals.py +0 -139
  271. wbportfolio/viewsets/transactions/transactions.py +0 -122
  272. /wbportfolio/{fdm → api_clients}/__init__.py +0 -0
  273. {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/licenses/LICENSE +0 -0
@@ -2,23 +2,15 @@ from contextlib import suppress
2
2
  from datetime import date, datetime
3
3
 
4
4
  import pandas as pd
5
- import plotly.graph_objects as go
6
5
  from django.db.models import Exists, F, OuterRef, Q, Sum
7
6
  from django.utils.dateparse import parse_date
8
7
  from django.utils.functional import cached_property
9
- from plotly.subplots import make_subplots
10
8
  from wbcore import viewsets
11
- from wbcore.contrib.currency.models import Currency, CurrencyFXRates
9
+ from wbcore.contrib.currency.models import CurrencyFXRates
12
10
  from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
13
- from wbcore.filters import DjangoFilterBackend
14
11
  from wbcore.pandas import fields as pf
15
12
  from wbcore.permissions.permissions import InternalUserPermissionMixin
16
13
  from wbcore.serializers import decorator
17
- from wbcore.utils.date import get_date_interval_from_request
18
- from wbcore.utils.figures import (
19
- get_default_timeserie_figure,
20
- get_hovertemplate_timeserie,
21
- )
22
14
  from wbcore.utils.strings import format_number
23
15
  from wbfdm.contrib.metric.viewsets.mixins import InstrumentMetricMixin
24
16
  from wbfdm.models import Instrument
@@ -27,11 +19,8 @@ from wbportfolio.filters import (
27
19
  AssetPositionFilter,
28
20
  AssetPositionInstrumentFilter,
29
21
  AssetPositionPortfolioFilter,
30
- AssetPositionUnderlyingInstrumentChartFilter,
31
22
  CashPositionPortfolioFilterSet,
32
- CompositionContributionChartFilter,
33
23
  CompositionModelPortfolioPandasFilter,
34
- ContributionChartFilter,
35
24
  )
36
25
  from wbportfolio.import_export.resources.assets import AssetPositionResource
37
26
  from wbportfolio.metric.backends.portfolio_base import (
@@ -69,16 +58,12 @@ from .configs import (
69
58
  AssetPositionPortfolioEndpointConfig,
70
59
  AssetPositionPortfolioTitleConfig,
71
60
  AssetPositionTitleConfig,
72
- AssetPositionUnderlyingInstrumentChartEndpointConfig,
73
- AssetPositionUnderlyingInstrumentChartTitleConfig,
74
61
  CashPositionPortfolioDisplayConfig,
75
62
  CashPositionPortfolioEndpointConfig,
76
63
  CashPositionPortfolioTitleConfig,
77
64
  CompositionModelPortfolioPandasDisplayConfig,
78
65
  CompositionModelPortfolioPandasEndpointConfig,
79
66
  CompositionModelPortfolioPandasTitleConfig,
80
- ContributorPortfolioChartEndpointConfig,
81
- ContributorPortfolioChartTitleConfig,
82
67
  )
83
68
  from .mixins import UserPortfolioRequestPermissionMixin
84
69
 
@@ -133,7 +118,7 @@ class AssetPositionModelViewSet(
133
118
  weighting = queryset.aggregate(s=Sum(F("weighting")))["s"]
134
119
  aggregates.update(
135
120
  {
136
- "weighting": {"Σ": format_number(weighting, decimal=4)},
121
+ "weighting": {"Σ": format_number(weighting, decimal=8)},
137
122
  "total_value_fx_usd": {"Σ": format_number(total_value_fx_usd)},
138
123
  }
139
124
  )
@@ -204,7 +189,7 @@ class AssetPositionPortfolioModelViewSet(InstrumentMetricMixin, AssetPositionMod
204
189
  total_value_fx_portfolio = queryset.aggregate(s=Sum(F("total_value_fx_portfolio")))["s"]
205
190
  aggregates = super().get_aggregates(queryset, paginated_queryset)
206
191
  aggregates["total_value_fx_portfolio"] = {"Σ": format_number(total_value_fx_portfolio)}
207
- aggregates["weighting"] = {"Σ": format_number(weighting, decimal=4)}
192
+ aggregates["weighting"] = {"Σ": format_number(weighting, decimal=8)}
208
193
  return aggregates
209
194
 
210
195
  def get_queryset(self):
@@ -344,192 +329,6 @@ class CashPositionPortfolioPandasAPIView(
344
329
  return AssetPosition.objects.none()
345
330
 
346
331
 
347
- # ##### CHART VIEWS #####
348
-
349
-
350
- class ContributorPortfolioChartView(UserPortfolioRequestPermissionMixin, viewsets.ChartViewSet):
351
- filterset_class = ContributionChartFilter
352
- filter_backends = (DjangoFilterBackend,)
353
- IDENTIFIER = "wbportfolio:portfolio-contributor"
354
- queryset = AssetPosition.objects.all()
355
-
356
- title_config_class = ContributorPortfolioChartTitleConfig
357
- endpoint_config_class = ContributorPortfolioChartEndpointConfig
358
-
359
- ROW_HEIGHT: int = 20
360
-
361
- @property
362
- def min_height(self):
363
- if hasattr(self, "nb_rows"):
364
- return self.nb_rows * self.ROW_HEIGHT
365
- return "300px"
366
-
367
- @cached_property
368
- def hedged_currency(self) -> Currency | None:
369
- if "hedged_currency" in self.request.GET:
370
- with suppress(Currency.DoesNotExist):
371
- return Currency.objects.get(pk=self.request.GET["hedged_currency"])
372
-
373
- @cached_property
374
- def show_lookthrough(self) -> bool:
375
- return self.portfolio.is_composition and self.request.GET.get("show_lookthrough", "false").lower() == "true"
376
-
377
- def get_filterset_class(self, request):
378
- if self.portfolio.is_composition:
379
- return CompositionContributionChartFilter
380
- return ContributionChartFilter
381
-
382
- def get_plotly(self, queryset):
383
- fig = go.Figure()
384
- data = []
385
- if self.show_lookthrough:
386
- d1, d2 = get_date_interval_from_request(self.request)
387
- for _d in pd.date_range(d1, d2):
388
- for pos in self.portfolio.get_lookthrough_positions(_d.date()):
389
- data.append(
390
- [
391
- pos.date,
392
- pos.initial_price,
393
- pos.initial_currency_fx_rate,
394
- pos.underlying_instrument_id,
395
- pos.weighting,
396
- ]
397
- )
398
- else:
399
- data = queryset.annotate_hedged_currency_fx_rate(self.hedged_currency).values_list(
400
- "date", "price", "hedged_currency_fx_rate", "underlying_instrument", "weighting"
401
- )
402
- df = Portfolio.get_contribution_df(data).rename(columns={"group_key": "underlying_instrument"})
403
- if not df.empty:
404
- df = df[["contribution_total", "contribution_forex", "underlying_instrument"]].sort_values(
405
- by="contribution_total", ascending=True
406
- )
407
-
408
- df["instrument_id"] = df.underlying_instrument.map(
409
- dict(Instrument.objects.filter(id__in=df["underlying_instrument"]).values_list("id", "name_repr"))
410
- )
411
- df_forex = df[["instrument_id", "contribution_forex"]]
412
- df_forex = df_forex[df_forex.contribution_forex != 0]
413
-
414
- contribution_equity = df.contribution_total - df.contribution_forex
415
-
416
- text_forex = df_forex.contribution_forex.apply(lambda x: f"{x:,.2%}")
417
- text_equity = contribution_equity.apply(lambda x: f"{x:,.2%}")
418
- setattr(self, "nb_rows", df.shape[0])
419
- fig.add_trace(
420
- go.Bar(
421
- y=df.instrument_id,
422
- x=contribution_equity,
423
- name="Contribution Equity",
424
- orientation="h",
425
- marker=dict(
426
- color="rgba(247,110,91,0.6)",
427
- line=dict(color="rgb(247,110,91,1.0)", width=2),
428
- ),
429
- text=text_equity.values,
430
- textposition="auto",
431
- )
432
- )
433
- fig.add_trace(
434
- go.Bar(
435
- y=df_forex.instrument_id,
436
- x=df_forex.contribution_forex,
437
- name="Contribution Forex",
438
- orientation="h",
439
- marker=dict(
440
- color="rgba(58, 71, 80, 0.6)",
441
- line=dict(color="rgba(58, 71, 80, 1.0)", width=2),
442
- ),
443
- text=text_forex.values,
444
- textposition="outside",
445
- )
446
- )
447
- fig.update_layout(
448
- barmode="relative",
449
- xaxis=dict(showgrid=False, showline=False, zeroline=False, tickformat=".2%"),
450
- yaxis=dict(showgrid=False, showline=False, zeroline=False, tickmode="linear"),
451
- margin=dict(b=0, r=20, l=20, t=0, pad=20),
452
- paper_bgcolor="rgba(0,0,0,0)",
453
- plot_bgcolor="rgba(0,0,0,0)",
454
- font=dict(family="roboto", size=12, color="black"),
455
- bargap=0.3,
456
- )
457
- # fig = get_horizontal_barplot(df, x_label="contribution_total", y_label="name")
458
- return fig
459
-
460
- def parse_figure_dict(self, figure_dict: dict[str, any]) -> dict[str, any]:
461
- figure_dict = super().parse_figure_dict(figure_dict)
462
- figure_dict["style"]["minHeight"] = self.min_height
463
- return figure_dict
464
-
465
- def get_queryset(self):
466
- if self.has_portfolio_access:
467
- return super().get_queryset().filter(portfolio=self.portfolio)
468
- return AssetPosition.objects.none()
469
-
470
-
471
- class AssetPositionUnderlyingInstrumentChartViewSet(UserPortfolioRequestPermissionMixin, viewsets.ChartViewSet):
472
- IDENTIFIER = "wbportfolio:assetpositionchart"
473
-
474
- queryset = AssetPosition.objects.all()
475
-
476
- title_config_class = AssetPositionUnderlyingInstrumentChartTitleConfig
477
- endpoint_config_class = AssetPositionUnderlyingInstrumentChartEndpointConfig
478
- filterset_class = AssetPositionUnderlyingInstrumentChartFilter
479
-
480
- def get_queryset(self):
481
- return AssetPosition.objects.filter(
482
- underlying_instrument__in=self.instrument.get_descendants(include_self=True)
483
- )
484
-
485
- def get_plotly(self, queryset):
486
- fig = make_subplots(specs=[[{"secondary_y": True}]])
487
- fig = get_default_timeserie_figure(fig)
488
- if queryset.exists():
489
- df_price = (pd.DataFrame(queryset.values("date", "price_fx_usd"))).groupby("date").first()
490
-
491
- df_price = df_price.where(pd.notnull(df_price), 0)
492
- fig.add_trace(
493
- go.Scatter(
494
- x=df_price.index, y=df_price.price_fx_usd, mode="lines", marker_color="green", name="Price"
495
- ),
496
- secondary_y=False,
497
- )
498
-
499
- df_weight = pd.DataFrame(queryset.values("date", "weighting", "portfolio__name"))
500
- df_weight = df_weight.where(pd.notnull(df_weight), 0)
501
- df_weight = df_weight.groupby(["date", "portfolio__name"]).sum().reset_index()
502
- for portfolio_name, df_tmp in df_weight.groupby("portfolio__name"):
503
- fig.add_trace(
504
- go.Scatter(
505
- x=df_tmp.date,
506
- y=df_tmp.weighting,
507
- hovertemplate=get_hovertemplate_timeserie(is_percent=True),
508
- mode="lines",
509
- name=f"Allocation: {portfolio_name}",
510
- ),
511
- secondary_y=True,
512
- )
513
-
514
- # Set x-axis title
515
- fig.update_xaxes(title_text="Date")
516
- # Set y-axes titles
517
- fig.update_yaxes(
518
- title_text="<b>Price</b>",
519
- secondary_y=False,
520
- titlefont=dict(color="green"),
521
- tickfont=dict(color="green"),
522
- )
523
- fig.update_yaxes(
524
- title_text="<b>Portfolio Allocation (%)</b>",
525
- secondary_y=True,
526
- titlefont=dict(color="blue"),
527
- tickfont=dict(color="blue"),
528
- )
529
-
530
- return fig
531
-
532
-
533
332
  class CompositionModelPortfolioPandasView(
534
333
  UserPortfolioRequestPermissionMixin, InternalUserPermissionMixin, ExportPandasAPIViewSet
535
334
  ):
@@ -1 +1,6 @@
1
- from .assets import DistributionChartViewSet, DistributionTableViewSet
1
+ from .assets import (
2
+ DistributionChartViewSet,
3
+ DistributionTableViewSet,
4
+ AssetPositionUnderlyingInstrumentChartViewSet,
5
+ ContributorPortfolioChartView
6
+ )