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
@@ -0,0 +1,64 @@
1
+ from typing import Generator
2
+
3
+ from wbcompliance.models.risk_management import backend
4
+ from wbcompliance.models.risk_management.dispatch import register
5
+ from wbcore import serializers as wb_serializers
6
+ from wbfdm.analysis.esg.enums import ESGAggregation
7
+ from wbfdm.analysis.esg.esg_analysis import DataLoader
8
+ from wbfdm.models import Instrument
9
+
10
+ from wbportfolio.pms.typing import Portfolio as PortfolioDTO
11
+
12
+ from .mixins import ActivePortfolioRelationshipMixin
13
+
14
+
15
+ @register("ESG Aggregation Portfolio Rule Backend", rule_group_key="portfolio")
16
+ class RuleBackend(ActivePortfolioRelationshipMixin):
17
+ def __init__(self, *args, **kwargs):
18
+ super().__init__(*args, **kwargs)
19
+ self.esg_aggregation = ESGAggregation[self.esg_aggregation]
20
+
21
+ @classmethod
22
+ def get_serializer_class(cls) -> wb_serializers.Serializer:
23
+ class RuleBackendSerializer(wb_serializers.Serializer):
24
+ esg_aggregation = wb_serializers.ChoiceField(
25
+ choices=ESGAggregation.choices(),
26
+ default=ESGAggregation.GHG_EMISSIONS_SCOPE_1.name,
27
+ )
28
+
29
+ @classmethod
30
+ def get_parameter_fields(cls):
31
+ return [
32
+ "esg_aggregation",
33
+ ]
34
+
35
+ return RuleBackendSerializer
36
+
37
+ def _process_dto(self, portfolio: PortfolioDTO, **kwargs) -> Generator[backend.IncidentResult, None, None]:
38
+ esg_data = self.esg_aggregation.get_esg_data(Instrument.objects.filter(id__in=portfolio.positions_map.keys()))
39
+ df = portfolio.to_df(exclude_cash=True)
40
+ df["total_value"] = (df["price"] * df["shares"] * df["currency_fx_rate"]).astype(float)
41
+ df = df[["total_value", "weighting", "underlying_instrument"]].set_index("underlying_instrument")
42
+ df["weighting"] = df["weighting"] / df["weighting"].sum()
43
+ dataloader = DataLoader(
44
+ df["weighting"].astype(float), esg_data, self.evaluation_date, total_value_fx_usd=df["total_value"]
45
+ )
46
+ metrics = dataloader.compute(self.esg_aggregation)
47
+ for threshold in self.thresholds:
48
+ numerical_range = threshold.numerical_range
49
+ incident_df = metrics[(metrics >= numerical_range[0]) & (metrics < numerical_range[1])]
50
+ for instrument_id, metric in incident_df.to_dict().items():
51
+ instrument = Instrument.objects.get(id=instrument_id)
52
+ breached_value = metric
53
+
54
+ if metric < 0:
55
+ breached_value = f'<span style="color:red">{breached_value}</span>'
56
+ else:
57
+ breached_value = f'<span style="color:green">{breached_value}</span>'
58
+ yield backend.IncidentResult(
59
+ breached_object=instrument,
60
+ breached_object_repr=str(instrument),
61
+ breached_value=breached_value,
62
+ report_details={"Aggregation": self.esg_aggregation.value},
63
+ severity=threshold.severity,
64
+ )
@@ -3,7 +3,7 @@ from typing import Generator
3
3
  from django.db import models
4
4
  from wbcompliance.models.risk_management import backend
5
5
  from wbcompliance.models.risk_management.dispatch import register
6
- from wbcompliance.models.risk_management.rules import RiskIncidentType
6
+ from wbcompliance.models.risk_management.incidents import RiskIncidentType
7
7
  from wbcore import serializers as wb_serializers
8
8
  from wbcore.contrib.currency.models import Currency
9
9
  from wbcore.contrib.currency.serializers import CurrencyRepresentationSerializer
@@ -160,7 +160,7 @@ class RuleBackend(
160
160
 
161
161
  def _process_dto(self, portfolio: PortfolioDTO, **kwargs) -> Generator[backend.IncidentResult, None, None]:
162
162
  if not (df := self._filter_df(portfolio.to_df())).empty:
163
- df = df[[self.group_by.value, self.field.value]].groupby(self.group_by.value).sum().astype(float)
163
+ df = df[[self.group_by.value, self.field.value]].dropna().groupby(self.group_by.value).sum().astype(float)
164
164
  for threshold in self.thresholds:
165
165
  numerical_range = threshold.numerical_range
166
166
  incident_df = df[
@@ -180,7 +180,7 @@ class RuleBackend(
180
180
  breached_value = f'<span style="color:green">{breached_value}</span>'
181
181
  yield backend.IncidentResult(
182
182
  breached_object=obj,
183
- breached_object_repr=obj_repr,
183
+ breached_object_repr=str(obj_repr),
184
184
  breached_value=breached_value,
185
185
  report_details=self.report_details,
186
186
  severity=severity,
@@ -218,7 +218,7 @@ class RuleBackend(
218
218
  obj = Instrument.objects.get(id=pivot_object_id)
219
219
  return obj, str(obj)
220
220
  case self.GroupbyChoices.ASSET_TYPE:
221
- return None, InstrumentType.objects.get(id=pivot_object_id)
221
+ return None, InstrumentType.objects.get(id=pivot_object_id).name
222
222
  case self.GroupbyChoices.CASH:
223
223
  return None, "Cash"
224
224
  case self.GroupbyChoices.COUNTRY:
@@ -64,10 +64,10 @@ class RuleBackend(ActivePortfolioRelationshipMixin):
64
64
  return RuleBackendSerializer
65
65
 
66
66
  def _process_dto(self, portfolio: PortfolioDTO, **kwargs) -> Generator[backend.IncidentResult, None, None]:
67
- for instrument_id, weight in portfolio.positions_map.items():
67
+ for instrument_id in portfolio.positions_map.keys():
68
68
  instrument = Instrument.objects.get(id=instrument_id)
69
- relationships = self.instruments_relationship.filter(instrument=instrument, validated=True)
70
-
69
+ ancestors = instrument.get_ancestors(include_self=True)
70
+ relationships = self.instruments_relationship.filter(instrument__in=ancestors, validated=True)
71
71
  if self.exclude and relationships.exists():
72
72
  report_details = {
73
73
  "Instrument Lists": ", ".join(relationships.values_list("instrument_list__name", flat=True)),
@@ -0,0 +1,49 @@
1
+ from unittest.mock import patch
2
+
3
+ import pandas as pd
4
+ import pytest
5
+ from faker import Faker
6
+ from psycopg.types.range import NumericRange
7
+ from wbcompliance.factories.risk_management import RuleThresholdFactory
8
+ from wbfdm.analysis.esg.esg_analysis import DataLoader
9
+
10
+ from wbportfolio.risk_management.backends.esg_aggregation_portfolio import (
11
+ RuleBackend as ESGAggregationPortfolioBackend,
12
+ )
13
+ from wbportfolio.tests.models.utils import PortfolioTestMixin
14
+
15
+ fake = Faker()
16
+
17
+
18
+ @pytest.mark.django_db
19
+ class TestEsgAggregationPortfolioRuleModel(PortfolioTestMixin):
20
+ @patch.object(DataLoader, "compute")
21
+ def test_eval(
22
+ self,
23
+ mock_fct,
24
+ weekday,
25
+ asset_position_factory,
26
+ portfolio,
27
+ ):
28
+ parameters = {"esg_aggregation": "GHG_EMISSIONS_SCOPE_1"}
29
+ backend = ESGAggregationPortfolioBackend(
30
+ weekday,
31
+ portfolio,
32
+ parameters,
33
+ [RuleThresholdFactory.create(range=NumericRange(lower=0.02, upper=0.03))], # type: ignore
34
+ )
35
+
36
+ a1 = asset_position_factory.create(
37
+ date=weekday,
38
+ portfolio=portfolio,
39
+ ) # Breached position
40
+
41
+ a2 = asset_position_factory.create(
42
+ date=weekday,
43
+ portfolio=portfolio,
44
+ )
45
+ mock_fct.return_value = pd.Series(index=[a1.underlying_quote.id, a2.underlying_quote.id], data=[0.01, 0.025])
46
+ incidents = list(backend.check_rule())
47
+ assert len(incidents) == 1
48
+ incident = incidents[0]
49
+ assert incident.breached_object == a2.underlying_quote
@@ -138,5 +138,5 @@ class TestExposurePortfolioRuleModel(PortfolioTestMixin):
138
138
  incidents = list(exposure_portfolio_backend.check_rule())
139
139
  assert len(incidents) == 1
140
140
  incident = incidents[0]
141
- assert incident.breached_object_repr == i1.instrument_type
141
+ assert incident.breached_object_repr == i1.instrument_type.name
142
142
  assert incident.breached_value == '<span style="color:green">+5.00%</span>'
@@ -92,7 +92,7 @@ class TestStopLossInstrumentRuleModel(PortfolioTestMixin):
92
92
  date=weekday, net_value=500, calculated=False, instrument=benchmark
93
93
  )
94
94
  RelatedInstrumentThroughModel.objects.create(instrument=product, related_instrument=benchmark, is_primary=True)
95
- setattr(stop_loss_instrument_backend, "dynamic_benchmark_type", "PRIMARY_BENCHMARK")
95
+ stop_loss_instrument_backend.dynamic_benchmark_type = "PRIMARY_BENCHMARK"
96
96
 
97
97
  res = list(stop_loss_instrument_backend.check_rule())
98
98
  assert len(res) == 0
@@ -105,7 +105,7 @@ class TestStopLossInstrumentRuleModel(PortfolioTestMixin):
105
105
  assert len(res) == 1
106
106
  assert res[0].breached_object.id == product.id
107
107
 
108
- setattr(stop_loss_instrument_backend, "static_benchmark", benchmark)
108
+ stop_loss_instrument_backend.static_benchmark = benchmark
109
109
  res = list(stop_loss_instrument_backend.check_rule())
110
110
  assert len(res) == 1
111
111
  assert res[0].breached_object.id == product.id
@@ -119,7 +119,7 @@ class TestStopLossPortfolioRuleModel(PortfolioTestMixin):
119
119
  res = list(stop_loss_portfolio_backend.check_rule())
120
120
  assert len(res) == 0
121
121
 
122
- setattr(stop_loss_portfolio_backend, "static_benchmark", benchmark)
122
+ stop_loss_portfolio_backend.static_benchmark = benchmark
123
123
  res = list(stop_loss_portfolio_backend.check_rule())
124
124
  assert len(res) == 1
125
125
  assert res[0].breached_object.id == instrument.id
@@ -27,6 +27,7 @@ from .registers import RegisterModelSerializer, RegisterRepresentationSerializer
27
27
  from .roles import PortfolioRoleModelSerializer, PortfolioRoleProjectModelSerializer
28
28
  from .signals import *
29
29
  from .transactions import *
30
+ from .orders import *
30
31
  from .products import (
31
32
  ProductRepresentationSerializer,
32
33
  ProductCustomerRepresentationSerializer,
@@ -0,0 +1,2 @@
1
+ from .order_proposals import OrderProposalModelSerializer, ReadOnlyOrderProposalModelSerializer, OrderProposalRepresentationSerializer
2
+ from .orders import OrderOrderProposalModelSerializer, ReadOnlyOrderOrderProposalModelSerializer, OrderOrderProposalListModelSerializer
@@ -0,0 +1,115 @@
1
+ from django.contrib.messages import warning
2
+ from django.core.exceptions import ValidationError
3
+ from rest_framework.reverse import reverse
4
+ from wbcore import serializers as wb_serializers
5
+ from wbcore.contrib.directory.serializers import PersonRepresentationSerializer
6
+ from wbcore.serializers import CharField, DefaultFromView
7
+
8
+ from wbportfolio.models import OrderProposal, Portfolio, RebalancingModel
9
+
10
+ from .. import PortfolioRepresentationSerializer, RebalancingModelRepresentationSerializer
11
+
12
+
13
+ class OrderProposalRepresentationSerializer(wb_serializers.RepresentationSerializer):
14
+ _detail = wb_serializers.HyperlinkField(reverse_name="wbportfolio:orderproposal-detail")
15
+
16
+ class Meta:
17
+ model = OrderProposal
18
+ fields = ("id", "trade_date", "status", "_detail")
19
+
20
+
21
+ class OrderProposalModelSerializer(wb_serializers.ModelSerializer):
22
+ _portfolio = PortfolioRepresentationSerializer(source="portfolio")
23
+ rebalancing_model = wb_serializers.PrimaryKeyRelatedField(queryset=RebalancingModel.objects.all(), required=False)
24
+ _rebalancing_model = RebalancingModelRepresentationSerializer(source="rebalancing_model")
25
+ target_portfolio = wb_serializers.PrimaryKeyRelatedField(
26
+ queryset=Portfolio.objects.all(), write_only=True, required=False
27
+ )
28
+ _target_portfolio = PortfolioRepresentationSerializer(source="target_portfolio")
29
+ trade_date = wb_serializers.DateField(
30
+ read_only=lambda view: not view.new_mode, default=DefaultFromView("default_trade_date")
31
+ )
32
+ _creator = PersonRepresentationSerializer(source="creator")
33
+ _approver = PersonRepresentationSerializer(source="approver")
34
+ execution_status_repr = wb_serializers.SerializerMethodField(label="Status", field_class=CharField, read_only=True)
35
+
36
+ def get_execution_status_repr(self, obj):
37
+ repr = obj.execution_status
38
+ if obj.execution_status_detail:
39
+ repr += f" (Custodian: {obj.execution_status_detail})"
40
+ return repr
41
+
42
+ def create(self, validated_data):
43
+ target_portfolio = validated_data.pop("target_portfolio", None)
44
+ rebalancing_model = validated_data.get("rebalancing_model", None)
45
+ if request := self.context.get("request"):
46
+ validated_data["creator"] = request.user.profile
47
+ obj = super().create(validated_data)
48
+
49
+ target_portfolio_dto = None
50
+ if target_portfolio:
51
+ target_portfolio_dto = target_portfolio._build_dto(obj.trade_date)
52
+ elif rebalancing_model:
53
+ target_portfolio_dto = rebalancing_model.get_target_portfolio(
54
+ obj.portfolio, obj.trade_date, obj.last_effective_date
55
+ )
56
+
57
+ try:
58
+ obj.reset_orders(target_portfolio=target_portfolio_dto)
59
+ except ValidationError as e:
60
+ if request := self.context.get("request"):
61
+ warning(request, str(e), extra_tags="auto_close=0")
62
+ return obj
63
+
64
+ @wb_serializers.register_only_instance_resource()
65
+ def additional_resources(self, instance, request, user, **kwargs):
66
+ res = {}
67
+ if instance.status == OrderProposal.Status.CONFIRMED:
68
+ res["replay"] = reverse("wbportfolio:orderproposal-replay", args=[instance.id], request=request)
69
+ if instance.status == OrderProposal.Status.DRAFT:
70
+ res["reset"] = reverse("wbportfolio:orderproposal-reset", args=[instance.id], request=request)
71
+ res["normalize"] = reverse("wbportfolio:orderproposal-normalize", args=[instance.id], request=request)
72
+ if instance.status == OrderProposal.Status.DRAFT or instance.can_be_confirmed:
73
+ res["refresh_return"] = reverse(
74
+ "wbportfolio:orderproposal-refreshreturn", args=[instance.id], request=request
75
+ )
76
+ res["orders"] = reverse(
77
+ "wbportfolio:orderproposal-order-list",
78
+ args=[instance.id],
79
+ request=request,
80
+ )
81
+ return res
82
+
83
+ class Meta:
84
+ model = OrderProposal
85
+ only_fsm_transition_on_instance = True
86
+ percent_fields = ["total_cash_weight"]
87
+ fields = (
88
+ "id",
89
+ "trade_date",
90
+ "portfolio",
91
+ "_portfolio",
92
+ "total_cash_weight",
93
+ "comment",
94
+ "status",
95
+ "min_order_value",
96
+ "min_weighting",
97
+ "_rebalancing_model",
98
+ "rebalancing_model",
99
+ "target_portfolio",
100
+ "_target_portfolio",
101
+ "creator",
102
+ "approver",
103
+ "_creator",
104
+ "_approver",
105
+ "execution_status",
106
+ "execution_status_detail",
107
+ "execution_comment",
108
+ "execution_status_repr",
109
+ "_additional_resources",
110
+ )
111
+
112
+
113
+ class ReadOnlyOrderProposalModelSerializer(OrderProposalModelSerializer):
114
+ class Meta(OrderProposalModelSerializer.Meta):
115
+ read_only_fields = OrderProposalModelSerializer.Meta.fields
@@ -0,0 +1,283 @@
1
+ from decimal import Decimal
2
+
3
+ from rest_framework import serializers
4
+ from rest_framework.reverse import reverse
5
+ from rest_framework.validators import UniqueTogetherValidator
6
+ from wbcore import serializers as wb_serializers
7
+ from wbcore.metadata.configs.display.list_display import BaseTreeGroupLevelOption
8
+ from wbfdm.models import Instrument
9
+ from wbfdm.serializers import InvestableInstrumentRepresentationSerializer
10
+ from wbfdm.serializers.instruments.instruments import (
11
+ CompanyRepresentationSerializer,
12
+ SecurityRepresentationSerializer,
13
+ )
14
+
15
+ from wbportfolio.models import Order, OrderProposal
16
+
17
+
18
+ class GetSecurityDefault:
19
+ requires_context = True
20
+
21
+ def __call__(self, serializer_instance):
22
+ try:
23
+ instance = serializer_instance.view.get_object()
24
+ return instance.underlying_instrument.parent or instance.underlying_instrument
25
+ except Exception:
26
+ return None
27
+
28
+
29
+ class GetCompanyDefault:
30
+ requires_context = True
31
+
32
+ def __call__(self, serializer_instance):
33
+ try:
34
+ instance = serializer_instance.view.get_object()
35
+ security = instance.underlying_instrument.parent or instance.underlying_instrument
36
+ return security.parent or security
37
+ except Exception:
38
+ return None
39
+
40
+
41
+ class OrderOrderProposalListModelSerializer(wb_serializers.ModelSerializer):
42
+ underlying_instrument = wb_serializers.SlugRelatedField(read_only=True, slug_field="name")
43
+ underlying_instrument_isin = wb_serializers.CharField(read_only=True)
44
+ underlying_instrument_ticker = wb_serializers.CharField(read_only=True)
45
+ underlying_instrument_refinitiv_identifier_code = wb_serializers.CharField(read_only=True)
46
+ underlying_instrument_instrument_type = wb_serializers.CharField(read_only=True)
47
+ underlying_instrument_exchange = wb_serializers.CharField(read_only=True)
48
+
49
+ effective_weight = wb_serializers.DecimalField(
50
+ read_only=True,
51
+ max_digits=Order.ORDER_WEIGHTING_PRECISION + 1,
52
+ decimal_places=Order.ORDER_WEIGHTING_PRECISION,
53
+ default=0,
54
+ )
55
+ target_weight = wb_serializers.DecimalField(
56
+ max_digits=Order.ORDER_WEIGHTING_PRECISION + 1,
57
+ decimal_places=Order.ORDER_WEIGHTING_PRECISION,
58
+ required=False,
59
+ )
60
+
61
+ effective_shares = wb_serializers.DecimalField(read_only=True, max_digits=16, decimal_places=6, default=0)
62
+ target_shares = wb_serializers.DecimalField(required=False, max_digits=16, decimal_places=6)
63
+
64
+ effective_total_value_fx_portfolio = wb_serializers.DecimalField(
65
+ read_only=True, max_digits=16, decimal_places=2, default=0
66
+ )
67
+ target_total_value_fx_portfolio = wb_serializers.DecimalField(required=False, max_digits=16, decimal_places=2)
68
+ total_value_fx_portfolio = wb_serializers.DecimalField(required=False, max_digits=16, decimal_places=2)
69
+
70
+ portfolio_currency = wb_serializers.CharField(read_only=True)
71
+ underlying_instrument_currency = wb_serializers.CharField(read_only=True)
72
+ has_warnings = wb_serializers.BooleanField(read_only=True)
73
+ execution_instruction_parameters_repr = wb_serializers.CharField(read_only=True)
74
+ execution_date = wb_serializers.DateField(read_only=True)
75
+ execution_price = wb_serializers.FloatField(read_only=True)
76
+ execution_traded_shares = wb_serializers.FloatField(read_only=True)
77
+
78
+ @wb_serializers.register_resource()
79
+ def additional_resources(self, instance, request, user):
80
+ if (view := request.parser_context.get("view")) and view.order_proposal.status in [
81
+ OrderProposal.Status.DRAFT,
82
+ OrderProposal.Status.PENDING,
83
+ OrderProposal.Status.APPROVED,
84
+ ]:
85
+ return {
86
+ "execution_instruction": reverse(
87
+ "wbportfolio:orderproposal-order-changeexecutioninstruction",
88
+ args=[view.order_proposal.id, instance.id],
89
+ request=request,
90
+ )
91
+ }
92
+ return {}
93
+
94
+ def validate(self, data):
95
+ data.pop("company", None)
96
+ data.pop("security", None)
97
+ if self.instance and "underlying_instrument" in data:
98
+ raise serializers.ValidationError(
99
+ {
100
+ "underlying_instrument": "You cannot modify the underlying instrument other than creating a new entry"
101
+ }
102
+ )
103
+
104
+ effective_weight = self.instance._effective_weight if self.instance else Decimal(0.0)
105
+ effective_shares = self.instance._effective_shares if self.instance else Decimal(0.0)
106
+ portfolio_value = (
107
+ self.context["view"].order_proposal.portfolio_total_asset_value if "view" in self.context else Decimal(0.0)
108
+ )
109
+ if (total_value_fx_portfolio := data.pop("total_value_fx_portfolio", None)) is not None and portfolio_value:
110
+ data["weighting"] = total_value_fx_portfolio / portfolio_value
111
+ if (
112
+ target_total_value_fx_portfolio := data.pop("target_total_value_fx_portfolio", None)
113
+ ) is not None and portfolio_value:
114
+ data["target_weight"] = target_total_value_fx_portfolio / portfolio_value
115
+
116
+ if data.get("weighting") is not None or data.get("target_weight") is not None:
117
+ weighting = data.pop("weighting", None)
118
+ if (target_weight := data.pop("target_weight", None)) is not None:
119
+ weighting = target_weight - effective_weight
120
+ data["desired_target_weight"] = target_weight
121
+ if weighting is not None:
122
+ data["weighting"] = weighting
123
+ data.pop("shares", None)
124
+ data.pop("target_shares", None)
125
+
126
+ if data.get("shares") is not None or data.get("target_shares") is not None:
127
+ shares = data.pop("shares", None)
128
+ if (target_shares := data.pop("target_shares", None)) is not None:
129
+ shares = target_shares - effective_shares
130
+ if shares is not None:
131
+ data["shares"] = shares
132
+ return super().validate(data)
133
+
134
+ def update(self, instance, validated_data):
135
+ weighting = validated_data.pop("weighting", None)
136
+ shares = validated_data.pop("shares", None)
137
+ portfolio_total_asset_value = instance.order_proposal.portfolio_total_asset_value
138
+ if weighting is not None:
139
+ instance.set_weighting(weighting, portfolio_total_asset_value)
140
+ if shares is not None:
141
+ instance.set_shares(shares, portfolio_total_asset_value)
142
+ return super().update(instance, validated_data)
143
+
144
+ def create(self, validated_data):
145
+ weighting = validated_data.pop("weighting", None)
146
+ shares = validated_data.pop("shares", None)
147
+ instance = super().create(validated_data)
148
+ portfolio_total_asset_value = instance.order_proposal.portfolio_total_asset_value
149
+ if weighting is not None:
150
+ instance.set_weighting(weighting, portfolio_total_asset_value)
151
+ if shares is not None:
152
+ instance.set_shares(shares, portfolio_total_asset_value)
153
+ instance.save()
154
+ return instance
155
+
156
+ def get_unique_together_validators(self):
157
+ return [
158
+ UniqueTogetherValidator(
159
+ queryset=Order.objects.all(),
160
+ fields=("order_proposal", "underlying_instrument"),
161
+ message="This instrument is already in the orders list.",
162
+ )
163
+ ]
164
+
165
+ class Meta:
166
+ model = Order
167
+ percent_fields = ["effective_weight", "target_weight", "weighting", "desired_target_weight"]
168
+ decorators = {
169
+ "total_value_fx_portfolio": wb_serializers.decorator(
170
+ decorator_type="text", position="left", value="{{portfolio_currency}}"
171
+ ),
172
+ "effective_total_value_fx_portfolio": wb_serializers.decorator(
173
+ decorator_type="text", position="left", value="{{portfolio_currency}}"
174
+ ),
175
+ "target_total_value_fx_portfolio": wb_serializers.decorator(
176
+ decorator_type="text", position="left", value="{{portfolio_currency}}"
177
+ ),
178
+ "price": wb_serializers.decorator(position="left", value="{{underlying_instrument_currency}}"),
179
+ }
180
+ read_only_fields = (
181
+ "order_type",
182
+ "effective_shares",
183
+ "effective_total_value_fx_portfolio",
184
+ "has_warnings",
185
+ "desired_target_weight",
186
+ "daily_return",
187
+ "currency_fx_rate",
188
+ "price",
189
+ "execution_instruction",
190
+ "execution_instruction_parameters_repr",
191
+ "execution_date",
192
+ "execution_price",
193
+ "execution_traded_shares",
194
+ )
195
+ extra_kwargs = {
196
+ "price": {"required": False},
197
+ }
198
+ fields = (
199
+ "id",
200
+ "shares",
201
+ "underlying_instrument",
202
+ "underlying_instrument_isin",
203
+ "underlying_instrument_ticker",
204
+ "underlying_instrument_refinitiv_identifier_code",
205
+ "underlying_instrument_instrument_type",
206
+ "underlying_instrument_exchange",
207
+ "order_type",
208
+ "comment",
209
+ "effective_weight",
210
+ "target_weight",
211
+ "weighting",
212
+ "order_proposal",
213
+ "order",
214
+ "effective_shares",
215
+ "target_shares",
216
+ "total_value_fx_portfolio",
217
+ "effective_total_value_fx_portfolio",
218
+ "target_total_value_fx_portfolio",
219
+ "portfolio_currency",
220
+ "underlying_instrument_currency",
221
+ "has_warnings",
222
+ "desired_target_weight",
223
+ "daily_return",
224
+ "currency_fx_rate",
225
+ "price",
226
+ "execution_status",
227
+ "execution_instruction",
228
+ "execution_instruction_parameters",
229
+ "execution_comment",
230
+ "execution_instruction_parameters_repr",
231
+ "execution_date",
232
+ "execution_price",
233
+ "execution_traded_shares",
234
+ "_additional_resources",
235
+ )
236
+
237
+
238
+ class OrderOrderProposalModelSerializer(OrderOrderProposalListModelSerializer):
239
+ company = wb_serializers.PrimaryKeyRelatedField(
240
+ queryset=Instrument.objects.filter(level=0),
241
+ required=False,
242
+ read_only=lambda view: not view.new_mode,
243
+ default=GetCompanyDefault(),
244
+ )
245
+ _company = CompanyRepresentationSerializer(source="company", required=False)
246
+
247
+ security = wb_serializers.PrimaryKeyRelatedField(
248
+ queryset=Instrument.objects.filter(is_security=True),
249
+ required=False,
250
+ read_only=lambda view: not view.new_mode,
251
+ default=GetSecurityDefault(),
252
+ )
253
+ _security = SecurityRepresentationSerializer(
254
+ source="security",
255
+ optional_get_parameters={"company": "parent"},
256
+ depends_on=[{"field": "company", "options": {}}],
257
+ required=False,
258
+ select_first_choice=True,
259
+ )
260
+ underlying_instrument = wb_serializers.PrimaryKeyRelatedField(
261
+ queryset=Instrument.objects.all(), label="Quote", read_only=lambda view: not view.new_mode
262
+ )
263
+ _underlying_instrument = InvestableInstrumentRepresentationSerializer(
264
+ source="underlying_instrument",
265
+ optional_get_parameters={"security": "parent"},
266
+ depends_on=[{"field": "security", "options": {}}],
267
+ tree_config=BaseTreeGroupLevelOption(clear_filter=True, filter_key="parent"),
268
+ select_first_choice=True,
269
+ )
270
+
271
+ class Meta(OrderOrderProposalListModelSerializer.Meta):
272
+ fields = list(OrderOrderProposalListModelSerializer.Meta.fields) + [
273
+ "company",
274
+ "_company",
275
+ "security",
276
+ "_security",
277
+ "_underlying_instrument",
278
+ ]
279
+
280
+
281
+ class ReadOnlyOrderOrderProposalModelSerializer(OrderOrderProposalListModelSerializer):
282
+ class Meta(OrderOrderProposalListModelSerializer.Meta):
283
+ read_only_fields = OrderOrderProposalListModelSerializer.Meta.fields
@@ -34,10 +34,10 @@ class PortfolioModelSerializer(wb_serializers.ModelSerializer):
34
34
 
35
35
  last_asset_under_management_usd = wb_serializers.FloatField(read_only=True)
36
36
  last_positions = wb_serializers.FloatField(read_only=True)
37
- last_trade_proposal_date = wb_serializers.DateField(read_only=True)
38
- next_expected_trade_proposal_date = wb_serializers.SerializerMethodField(read_only=True)
37
+ last_order_proposal_date = wb_serializers.DateField(read_only=True)
38
+ next_expected_order_proposal_date = wb_serializers.SerializerMethodField(read_only=True)
39
39
 
40
- def get_next_expected_trade_proposal_date(self, obj):
40
+ def get_next_expected_order_proposal_date(self, obj):
41
41
  if (automatic_rebalancer := getattr(obj, "automatic_rebalancer", None)) and (
42
42
  _d := automatic_rebalancer.get_next_rebalancing_date(date.today())
43
43
  ):
@@ -107,8 +107,8 @@ class PortfolioModelSerializer(wb_serializers.ModelSerializer):
107
107
  args=[instance.id],
108
108
  request=request,
109
109
  )
110
- additional_resources["trade_proposals"] = reverse(
111
- "wbportfolio:portfolio-tradeproposal-list",
110
+ additional_resources["order_proposals"] = reverse(
111
+ "wbportfolio:portfolio-orderproposal-list",
112
112
  args=[instance.id],
113
113
  request=request,
114
114
  )
@@ -169,8 +169,8 @@ class PortfolioModelSerializer(wb_serializers.ModelSerializer):
169
169
  "last_position_date",
170
170
  "last_asset_under_management_usd",
171
171
  "last_positions",
172
- "last_trade_proposal_date",
173
- "next_expected_trade_proposal_date",
172
+ "last_order_proposal_date",
173
+ "next_expected_order_proposal_date",
174
174
  )
175
175
 
176
176
 
@@ -11,8 +11,8 @@ class AggregatedAssetPositionModelSerializer(wb_serializers.ModelSerializer):
11
11
  sum_total_value = wb_serializers.DecimalField(read_only=True, max_digits=16, decimal_places=4)
12
12
  weighting = wb_serializers.DecimalField(
13
13
  read_only=True,
14
- max_digits=6,
15
- decimal_places=4,
14
+ max_digits=9,
15
+ decimal_places=8,
16
16
  percent=True,
17
17
  decorators=[{"position": "right", "value": "%"}],
18
18
  )