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,277 @@
1
+ from decimal import Decimal
2
+
3
+ from django.contrib.messages import error, info
4
+ from django.db.models import (
5
+ Case,
6
+ CharField,
7
+ F,
8
+ Func,
9
+ Sum,
10
+ Value,
11
+ When,
12
+ )
13
+ from django.shortcuts import get_object_or_404
14
+ from django.utils.functional import cached_property
15
+ from rest_framework.decorators import action
16
+ from rest_framework.response import Response
17
+ from wbcore import viewsets
18
+ from wbcore.permissions.permissions import InternalUserPermissionMixin
19
+ from wbcore.utils.strings import format_number
20
+ from wbcore.viewsets.mixins import OrderableMixin
21
+
22
+ from wbportfolio.import_export.resources.trades import OrderProposalTradeResource
23
+ from wbportfolio.models import Order, OrderProposal
24
+ from wbportfolio.serializers import (
25
+ OrderOrderProposalListModelSerializer,
26
+ OrderOrderProposalModelSerializer,
27
+ ReadOnlyOrderOrderProposalModelSerializer,
28
+ )
29
+
30
+ from ...filters.orders import OrderFilterSet
31
+ from ...permissions import IsPortfolioManager
32
+ from ..mixins import UserPortfolioRequestPermissionMixin
33
+ from .configs import (
34
+ OrderOrderProposalButtonConfig,
35
+ OrderOrderProposalDisplayConfig,
36
+ OrderOrderProposalEndpointConfig,
37
+ )
38
+ from .configs.buttons.orders import ExecutionInstructionSerializer
39
+
40
+
41
+ class OrderOrderProposalModelViewSet(
42
+ UserPortfolioRequestPermissionMixin, InternalUserPermissionMixin, OrderableMixin, viewsets.ModelViewSet
43
+ ):
44
+ IDENTIFIER = "wbportfolio:order"
45
+ IMPORT_ALLOWED = True
46
+ ordering = (
47
+ "order_proposal",
48
+ "order",
49
+ )
50
+ ordering_fields = (
51
+ "underlying_instrument__name",
52
+ "underlying_instrument_isin",
53
+ "underlying_instrument_ticker",
54
+ "underlying_instrument_refinitiv_identifier_code",
55
+ "underlying_instrument_instrument_type",
56
+ "target_weight",
57
+ "effective_weight",
58
+ "effective_shares",
59
+ "target_shares",
60
+ "shares",
61
+ "weighting",
62
+ )
63
+ IDENTIFIER = "wbportfolio:order"
64
+ search_fields = ("underlying_instrument__name",)
65
+ queryset = Order.objects.none()
66
+ filterset_class = OrderFilterSet
67
+
68
+ display_config_class = OrderOrderProposalDisplayConfig
69
+ endpoint_config_class = OrderOrderProposalEndpointConfig
70
+ serializer_class = OrderOrderProposalModelSerializer
71
+ button_config_class = OrderOrderProposalButtonConfig
72
+
73
+ @cached_property
74
+ def order_proposal(self):
75
+ return get_object_or_404(OrderProposal, pk=self.kwargs["order_proposal_id"])
76
+
77
+ @cached_property
78
+ def portfolio_total_asset_value(self):
79
+ return self.order_proposal.portfolio_total_asset_value
80
+
81
+ def has_import_permission(self, request) -> bool: # allow import only on draft order proposal
82
+ return super().has_import_permission(request) and self.order_proposal.status == OrderProposal.Status.DRAFT
83
+
84
+ def get_import_resource_kwargs(self):
85
+ resource_kwargs = super().get_import_resource_kwargs()
86
+ resource_kwargs["columns_mapping"] = {"underlying_instrument": "underlying_instrument__isin"}
87
+ return resource_kwargs
88
+
89
+ def get_resource_class(self):
90
+ return OrderProposalTradeResource
91
+
92
+ def get_aggregates(self, queryset, *args, **kwargs):
93
+ agg = {}
94
+ if queryset.exists():
95
+ noncash_aggregates = queryset.filter(underlying_instrument__is_cash=False).aggregate(
96
+ sum_target_weight=Sum(F("target_weight")),
97
+ sum_effective_weight=Sum(F("effective_weight")),
98
+ sum_target_total_value_fx_portfolio=Sum(F("target_total_value_fx_portfolio")),
99
+ sum_effective_total_value_fx_portfolio=Sum(F("effective_total_value_fx_portfolio")),
100
+ )
101
+ # weights aggregates
102
+ cash_sum_effective_weight = self.order_proposal.total_effective_portfolio_weight - (
103
+ noncash_aggregates["sum_effective_weight"] or Decimal(0)
104
+ )
105
+ cash_sum_target_cash_weight = Decimal("1.0") - (noncash_aggregates["sum_target_weight"] or Decimal(0))
106
+ noncash_sum_effective_weight = noncash_aggregates["sum_effective_weight"] or Decimal(0)
107
+ noncash_sum_target_weight = noncash_aggregates["sum_target_weight"] or Decimal(0)
108
+ sum_buy_weight = queryset.filter(weighting__gte=0).aggregate(s=Sum(F("weighting")))["s"] or Decimal(0)
109
+ sum_sell_weight = queryset.filter(weighting__lt=0).aggregate(s=Sum(F("weighting")))["s"] or Decimal(0)
110
+
111
+ # shares aggregates
112
+ cash_sum_effective_total_value_fx_portfolio = cash_sum_effective_weight * self.portfolio_total_asset_value
113
+ cash_sum_target_total_value_fx_portfolio = cash_sum_target_cash_weight * self.portfolio_total_asset_value
114
+ noncash_sum_effective_total_value_fx_portfolio = noncash_aggregates[
115
+ "sum_effective_total_value_fx_portfolio"
116
+ ] or Decimal(0)
117
+ noncash_sum_target_total_value_fx_portfolio = noncash_aggregates[
118
+ "sum_target_total_value_fx_portfolio"
119
+ ] or Decimal(0)
120
+ sum_buy_total_value_fx_portfolio = queryset.filter(total_value_fx_portfolio__gte=0).aggregate(
121
+ s=Sum(F("total_value_fx_portfolio"))
122
+ )["s"] or Decimal(0)
123
+ sum_sell_total_value_fx_portfolio = queryset.filter(total_value_fx_portfolio__lt=0).aggregate(
124
+ s=Sum(F("total_value_fx_portfolio"))
125
+ )["s"] or Decimal(0)
126
+
127
+ agg = {
128
+ "effective_weight": {
129
+ "Cash": format_number(cash_sum_effective_weight, decimal=Order.ORDER_WEIGHTING_PRECISION),
130
+ "Non-Cash": format_number(noncash_sum_effective_weight, decimal=Order.ORDER_WEIGHTING_PRECISION),
131
+ "Total": format_number(
132
+ noncash_sum_effective_weight + cash_sum_effective_weight,
133
+ decimal=Order.ORDER_WEIGHTING_PRECISION,
134
+ ),
135
+ },
136
+ "target_weight": {
137
+ "Cash": format_number(cash_sum_target_cash_weight, decimal=Order.ORDER_WEIGHTING_PRECISION),
138
+ "Non-Cash": format_number(noncash_sum_target_weight, decimal=Order.ORDER_WEIGHTING_PRECISION),
139
+ "Total": format_number(
140
+ cash_sum_target_cash_weight + noncash_sum_target_weight,
141
+ decimal=Order.ORDER_WEIGHTING_PRECISION,
142
+ ),
143
+ },
144
+ "effective_total_value_fx_portfolio": {
145
+ "Cash": format_number(cash_sum_effective_total_value_fx_portfolio, decimal=6),
146
+ "Non-Cash": format_number(noncash_sum_effective_total_value_fx_portfolio, decimal=6),
147
+ "Total": format_number(
148
+ cash_sum_effective_total_value_fx_portfolio + noncash_sum_effective_total_value_fx_portfolio,
149
+ decimal=6,
150
+ ),
151
+ },
152
+ "target_total_value_fx_portfolio": {
153
+ "Cash": format_number(cash_sum_target_total_value_fx_portfolio, decimal=6),
154
+ "Non-Cash": format_number(noncash_sum_target_total_value_fx_portfolio, decimal=6),
155
+ "Total": format_number(
156
+ cash_sum_target_total_value_fx_portfolio + noncash_sum_target_total_value_fx_portfolio,
157
+ decimal=6,
158
+ ),
159
+ },
160
+ "weighting": {
161
+ "Cash Flow": format_number(
162
+ sum_sell_weight + sum_buy_weight,
163
+ decimal=Order.ORDER_WEIGHTING_PRECISION,
164
+ ),
165
+ "Buy": format_number(sum_buy_weight, decimal=Order.ORDER_WEIGHTING_PRECISION),
166
+ "Sell": format_number(sum_sell_weight, decimal=Order.ORDER_WEIGHTING_PRECISION),
167
+ },
168
+ "total_value_fx_portfolio": {
169
+ "Cash Flow": format_number(
170
+ cash_sum_target_total_value_fx_portfolio - cash_sum_effective_total_value_fx_portfolio,
171
+ decimal=6,
172
+ ),
173
+ "Buy": format_number(sum_buy_total_value_fx_portfolio, decimal=6),
174
+ "Sell": format_number(sum_sell_total_value_fx_portfolio, decimal=6),
175
+ },
176
+ }
177
+
178
+ return agg
179
+
180
+ def get_serializer_class(self):
181
+ if self.order_proposal.status != OrderProposal.Status.DRAFT and not self.order_proposal.can_be_confirmed:
182
+ return ReadOnlyOrderOrderProposalModelSerializer
183
+ if not self.new_mode and "pk" not in self.kwargs:
184
+ serializer_base_class = OrderOrderProposalListModelSerializer
185
+ else:
186
+ serializer_base_class = OrderOrderProposalModelSerializer
187
+ if not self.order_proposal.portfolio_total_asset_value:
188
+
189
+ class OnlyWeightSerializerClass(serializer_base_class):
190
+ class Meta(serializer_base_class.Meta):
191
+ read_only_fields = list(serializer_base_class.Meta.read_only_fields) + [
192
+ "shares",
193
+ "target_shares",
194
+ "total_value_fx_portfolio",
195
+ "target_total_value_fx_portfolio",
196
+ ]
197
+
198
+ return OnlyWeightSerializerClass
199
+ return serializer_base_class
200
+
201
+ def add_messages(self, request, queryset=None, paginated_queryset=None, instance=None, initial=False):
202
+ if self.orders.exists() and self.order_proposal.status in [
203
+ OrderProposal.Status.PENDING,
204
+ OrderProposal.Status.DRAFT,
205
+ ]:
206
+ total_target_weight = self.orders.aggregate(c=Sum(F("target_weight")))["c"] or Decimal(0)
207
+ if round(total_target_weight, 8) != 1:
208
+ info(
209
+ request,
210
+ "The total target weight does not equal 1. To avoid automatic cash allocation, please adjust the order weights to sum up to 1. Otherwise, a cash component will be added when this order proposal is submitted.",
211
+ )
212
+ if self.orders.filter(has_warnings=True).exists():
213
+ error(
214
+ request,
215
+ "Some orders failed preparation. To resolve this, please revert the order proposal to draft, review and correct the orders, and then resubmit.",
216
+ )
217
+
218
+ @cached_property
219
+ def orders(self):
220
+ qs = self.order_proposal.get_orders()
221
+ if not self.is_portfolio_manager:
222
+ return qs.none()
223
+ return qs
224
+
225
+ def get_queryset(self):
226
+ return (
227
+ self.orders.filter(underlying_instrument__is_cash=False)
228
+ .annotate( # .exclude(underlying_instrument__is_cash=True)
229
+ underlying_instrument_isin=F("underlying_instrument__isin"),
230
+ underlying_instrument_ticker=F("underlying_instrument__ticker"),
231
+ underlying_instrument_refinitiv_identifier_code=F("underlying_instrument__refinitiv_identifier_code"),
232
+ underlying_instrument_instrument_type=Case(
233
+ When(
234
+ underlying_instrument__parent__is_security=True,
235
+ then=F("underlying_instrument__parent__instrument_type__short_name"),
236
+ ),
237
+ default=F("underlying_instrument__instrument_type__short_name"),
238
+ ),
239
+ underlying_instrument_exchange=F("underlying_instrument__exchange__name"),
240
+ effective_total_value_fx_portfolio=F("effective_weight") * Value(self.portfolio_total_asset_value),
241
+ target_total_value_fx_portfolio=F("target_weight") * Value(self.portfolio_total_asset_value),
242
+ portfolio_currency=F("portfolio__currency__symbol"),
243
+ underlying_instrument_currency=F("underlying_instrument__currency__symbol"),
244
+ security=F("underlying_instrument__parent"),
245
+ company=F("underlying_instrument__parent__parent"),
246
+ execution_instruction_parameters_repr=Func(
247
+ "execution_instruction_parameters",
248
+ function="string_agg",
249
+ template="(SELECT string_agg(key || '=' || value, ',') FROM jsonb_each_text(%(expressions)s))",
250
+ output_field=CharField(),
251
+ ),
252
+ execution_date=F("execution_trade__transaction_date"),
253
+ execution_price=F("execution_trade__price"),
254
+ execution_traded_shares=F("execution_trade__shares"),
255
+ )
256
+ .select_related(
257
+ "underlying_instrument", "underlying_instrument__parent", "underlying_instrument__parent__parent"
258
+ )
259
+ )
260
+
261
+ @action(detail=True, methods=["PUT"], permission_classes=[IsPortfolioManager])
262
+ def changeexecutioninstruction(self, request, pk=None, order_proposal_id=None, **kwargs):
263
+ serializer = ExecutionInstructionSerializer(data=request.data)
264
+ order_proposal = get_object_or_404(OrderProposal, pk=order_proposal_id)
265
+ if serializer.is_valid(raise_exception=True):
266
+ parameters = dict(serializer.data)
267
+ orders_to_update = order_proposal.orders.all()
268
+ execution_instruction = parameters.pop("execution_instruction")
269
+ apply_execution_instruction_to_all_orders = parameters.pop("apply_execution_instruction_to_all_orders")
270
+ execution_parameters = {k: v for k, v in parameters.items() if v}
271
+ if not apply_execution_instruction_to_all_orders:
272
+ orders_to_update = orders_to_update.filter(id=pk)
273
+ orders_to_update.update(
274
+ execution_instruction=execution_instruction, execution_instruction_parameters=execution_parameters
275
+ )
276
+
277
+ return Response({"send": True})
@@ -2,6 +2,7 @@ from contextlib import suppress
2
2
  from datetime import date, datetime
3
3
 
4
4
  import pandas as pd
5
+ from celery import chain
5
6
  from django.db.models import OuterRef, Q, Subquery, Sum
6
7
  from django.http import HttpResponse
7
8
  from django.shortcuts import get_object_or_404
@@ -14,6 +15,7 @@ from rest_framework.reverse import reverse
14
15
  from wbcore import viewsets
15
16
  from wbcore.contrib.currency.models import Currency
16
17
  from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
18
+ from wbcore.contrib.notifications.dispatch import send_notification_as_task
17
19
  from wbcore.pandas import fields as pf
18
20
  from wbcore.permissions.permissions import InternalUserPermissionMixin
19
21
  from wbfdm.models import Instrument
@@ -21,11 +23,11 @@ from wbfdm.models import Instrument
21
23
  from wbportfolio.filters import PortfolioFilterSet, PortfolioTreeGraphChartFilterSet
22
24
  from wbportfolio.models import (
23
25
  AssetPosition,
26
+ OrderProposal,
24
27
  Portfolio,
25
28
  PortfolioPortfolioThroughModel,
26
29
  Rebalancer,
27
30
  RebalancingModel,
28
- TradeProposal,
29
31
  )
30
32
  from wbportfolio.models.portfolio import compute_lookthrough_as_task
31
33
  from wbportfolio.serializers import (
@@ -35,6 +37,8 @@ from wbportfolio.serializers import (
35
37
  )
36
38
 
37
39
  from ..models.graphs.portfolio import PortfolioGraph
40
+ from ..models.utils import adjust_quote_as_task
41
+ from ..permissions import IsPortfolioManager
38
42
  from .configs import (
39
43
  PortfolioButtonConfig,
40
44
  PortfolioDisplayConfig,
@@ -77,7 +81,7 @@ class PortfolioModelViewSet(UserPortfolioRequestPermissionMixin, InternalUserPer
77
81
  "last_asset_under_management_usd",
78
82
  "last_positions",
79
83
  "automatic_rebalancer",
80
- "last_trade_proposal_date",
84
+ "last_order_proposal_date",
81
85
  "is_manageable",
82
86
  "is_tracked",
83
87
  "only_weighting",
@@ -111,8 +115,8 @@ class PortfolioModelViewSet(UserPortfolioRequestPermissionMixin, InternalUserPer
111
115
  .annotate(s=Sum("shares"))
112
116
  .values("s")[:1]
113
117
  ),
114
- last_trade_proposal_date=Subquery(
115
- TradeProposal.objects.filter(portfolio=OuterRef("pk"))
118
+ last_order_proposal_date=Subquery(
119
+ OrderProposal.objects.filter(portfolio=OuterRef("pk"))
116
120
  .order_by("-trade_date")
117
121
  .values("trade_date")[:1]
118
122
  ),
@@ -123,10 +127,10 @@ class PortfolioModelViewSet(UserPortfolioRequestPermissionMixin, InternalUserPer
123
127
  def rebalance(self, request, pk=None):
124
128
  if date_str := request.POST.get("trade_date", None):
125
129
  trade_date = datetime.strptime(date_str, "%Y-%m-%d")
126
- trade_proposal, _ = TradeProposal.objects.get_or_create(portfolio_id=pk, trade_date=trade_date)
127
- trade_proposal.reset_trades()
130
+ order_proposal, _ = OrderProposal.objects.get_or_create(portfolio_id=pk, trade_date=trade_date)
131
+ order_proposal.reset_orders()
128
132
  return Response(
129
- {"endpoint": reverse("wbportfolio:tradeproposal-detail", args=[trade_proposal.id], request=request)}
133
+ {"endpoint": reverse("wbportfolio:orderproposal-detail", args=[order_proposal.id], request=request)}
130
134
  )
131
135
  raise HttpResponse("Bad Request", status=400)
132
136
 
@@ -147,8 +151,8 @@ class PortfolioModelViewSet(UserPortfolioRequestPermissionMixin, InternalUserPer
147
151
  activation_date = datetime.strptime(request.POST["activation_date"], "%Y-%m-%d")
148
152
  rebalancing_model = get_object_or_404(RebalancingModel, pk=request.POST["rebalancing_model"])
149
153
  frequency = request.POST["frequency"]
150
- approve_trade_proposal_automatically = (
151
- request.POST.get("approve_trade_proposal_automatically", "false") == "true"
154
+ apply_order_proposal_automatically = (
155
+ request.POST.get("apply_order_proposal_automatically", "false") == "true"
152
156
  )
153
157
 
154
158
  rebalancer, _ = Rebalancer.objects.update_or_create(
@@ -157,7 +161,7 @@ class PortfolioModelViewSet(UserPortfolioRequestPermissionMixin, InternalUserPer
157
161
  "rebalancing_model": rebalancing_model,
158
162
  "frequency": frequency,
159
163
  "activation_date": activation_date,
160
- "approve_trade_proposal_automatically": approve_trade_proposal_automatically,
164
+ "apply_order_proposal_automatically": apply_order_proposal_automatically,
161
165
  },
162
166
  )
163
167
  return Response(
@@ -178,6 +182,26 @@ class PortfolioModelViewSet(UserPortfolioRequestPermissionMixin, InternalUserPer
178
182
 
179
183
  return HttpResponse("Bad arguments", status=400)
180
184
 
185
+ @action(detail=False, methods=["POST"], permission_classes=[IsPortfolioManager])
186
+ def adjustquote(self, request, pk=None):
187
+ old_quote = get_object_or_404(Instrument, pk=request.POST["old_quote"])
188
+ new_quote = get_object_or_404(Instrument, pk=request.POST["new_quote"])
189
+ adjust_after = parse_date(request.data["adjust_after"]) if "adjust_after" in request.data else None
190
+ only_portfolio_ids = request.data["only_portfolios"].split(",") if "only_portfolios" in request.data else []
191
+
192
+ chain(
193
+ adjust_quote_as_task.si(
194
+ old_quote.id, new_quote.id, adjust_after=adjust_after, only_portfolio_ids=only_portfolio_ids
195
+ ),
196
+ send_notification_as_task.si(
197
+ "wbportfolio.portfolio.action_done",
198
+ f"Quote adjustment from {old_quote} to {new_quote} is done",
199
+ "The associated positions and orders were successfully adjusted",
200
+ request.user.id,
201
+ ),
202
+ ).apply_async()
203
+ return HttpResponse("Ok", status=200)
204
+
181
205
 
182
206
  class PortfolioPortfolioThroughModelViewSet(InternalUserPermissionMixin, viewsets.ModelViewSet):
183
207
  serializer_class = PortfolioPortfolioThroughModelSerializer
@@ -242,9 +266,9 @@ class TopDownPortfolioCompositionPandasAPIView(UserPortfolioRequestPermissionMix
242
266
  @cached_property
243
267
  def last_rebalancing_date(self) -> date | None:
244
268
  if self.composition_portfolio and self.last_effective_date:
245
- with suppress(TradeProposal.DoesNotExist):
269
+ with suppress(OrderProposal.DoesNotExist):
246
270
  return (
247
- self.composition_portfolio.trade_proposals.filter(trade_date__lte=self.last_effective_date)
271
+ self.composition_portfolio.order_proposals.filter(trade_date__lte=self.last_effective_date)
248
272
  .latest("trade_date")
249
273
  .trade_date
250
274
  )
@@ -10,7 +10,7 @@ from wbcore.pandas import fields as pf
10
10
  from wbcore.permissions.permissions import InternalUserPermissionMixin
11
11
  from wbcore.serializers import decorator
12
12
  from wbcore.utils.strings import format_number
13
- from wbfdm.models import Classification, ClassificationGroup, Instrument, InstrumentType
13
+ from wbfdm.models import Classification, ClassificationGroup, Instrument
14
14
 
15
15
  from wbportfolio.filters import (
16
16
  AggregatedAssetPositionLiquidityFilter,
@@ -19,6 +19,7 @@ from wbportfolio.filters import (
19
19
  from wbportfolio.filters.positions import GroupbyChoice
20
20
  from wbportfolio.models import AssetPosition, Portfolio
21
21
 
22
+ from ..constants import EQUITY_TYPE_KEYS
22
23
  from .configs import (
23
24
  AggregatedAssetPositionLiquidityDisplayConfig,
24
25
  AggregatedAssetPositionLiquidityEndpointConfig,
@@ -212,7 +213,7 @@ class AggregatedAssetPositionLiquidityPandasView(InternalUserPermissionMixin, Ex
212
213
 
213
214
  # Take the liquidity query for the two dates.
214
215
  qs_assets = queryset.filter(
215
- underlying_instrument__instrument_type=InstrumentType.EQUITY,
216
+ underlying_instrument__instrument_type__key__in=EQUITY_TYPE_KEYS,
216
217
  date__in=[historic_date, compared_date],
217
218
  ).values(
218
219
  "date",
@@ -317,16 +317,16 @@ class ProductPerformanceFeesModelViewSet(
317
317
  if date_lte is None and date_gte is None:
318
318
  date_gte, date_lte = get_start_and_end_date_from_date(date.today())
319
319
 
320
- fees = Fees.valid_objects.filter(linked_product=OuterRef("pk"))
320
+ fees = Fees.valid_objects.filter(product=OuterRef("pk"))
321
321
  if date_gte:
322
- fees = fees.filter(transaction_date__gte=date_gte)
322
+ fees = fees.filter(fee_date__gte=date_gte)
323
323
  if date_lte:
324
- fees = fees.filter(transaction_date__lte=date_lte)
324
+ fees = fees.filter(fee_date__lte=date_lte)
325
325
 
326
326
  management_fees = Coalesce(
327
327
  Subquery(
328
328
  fees.filter(transaction_subtype=Fees.Type.MANAGEMENT)
329
- .values("linked_product")
329
+ .values("product")
330
330
  .annotate(sum_management_fees=Sum("total_value"))
331
331
  .values("sum_management_fees")[:1],
332
332
  output_field=FloatField(),
@@ -340,7 +340,7 @@ class ProductPerformanceFeesModelViewSet(
340
340
  Q(transaction_subtype=Fees.Type.PERFORMANCE)
341
341
  | Q(transaction_subtype=Fees.Type.PERFORMANCE_CRYSTALIZED)
342
342
  )
343
- .values("linked_product")
343
+ .values("product")
344
344
  .annotate(sum_performance_fees_net=Sum("total_value"))
345
345
  .values("sum_performance_fees_net")[:1],
346
346
  output_field=FloatField(),
@@ -367,7 +367,7 @@ class ProductPerformanceFeesModelViewSet(
367
367
  sum_total_usd=F("sum_management_fees_usd") + F("sum_performance_fees_net_usd"),
368
368
  )
369
369
  .select_related("currency")
370
- .prefetch_related("transactions", "prices")
370
+ .prefetch_related("fees", "prices")
371
371
  )
372
372
  return qs
373
373
 
@@ -8,8 +8,8 @@ from wbportfolio.serializers import (
8
8
  RebalancingModelRepresentationSerializer,
9
9
  )
10
10
 
11
- from ..configs.display import RebalancerDisplayConfig
12
- from ..configs.endpoints import RebalancerEndpointConfig
11
+ from .configs.display import RebalancerDisplayConfig
12
+ from .configs.endpoints import RebalancerEndpointConfig
13
13
 
14
14
 
15
15
  class RebalancingModelRepresentationViewSet(InternalUserPermissionMixin, viewsets.RepresentationViewSet):
@@ -13,16 +13,11 @@ from .claim import (
13
13
  ProfitAndLossPandasView,
14
14
  )
15
15
  from .fees import (
16
- FeesAggregatedPortfolioPandasView,
16
+ FeesAggregatedProductPandasView,
17
17
  FeesModelViewSet,
18
- FeesPortfolioModelViewSet,
19
- )
20
- from .rebalancing import RebalancingModelRepresentationViewSet, RebalancerRepresentationViewSet, RebalancerModelViewSet
21
- from .trade_proposals import (
22
- TradeProposalModelViewSet,
23
- TradeProposalPortfolioModelViewSet,
24
- TradeProposalRepresentationViewSet,
18
+ FeesProductModelViewSet,
25
19
  )
20
+
26
21
  from .trades import (
27
22
  CustodianDistributionInstrumentChartViewSet,
28
23
  CustomerDistributionInstrumentChartViewSet,
@@ -32,10 +27,4 @@ from .trades import (
32
27
  TradeModelViewSet,
33
28
  TradePortfolioModelViewSet,
34
29
  TradeRepresentationViewSet,
35
- TradeTradeProposalModelViewSet,
36
- )
37
- from .transactions import (
38
- TransactionModelViewSet,
39
- TransactionPortfolioModelViewSet,
40
- TransactionRepresentationViewSet,
41
30
  )
@@ -8,28 +8,28 @@ from wbcore.pandas import fields as pf
8
8
  from wbcore.permissions.permissions import InternalUserPermissionMixin
9
9
  from wbcore.serializers import decorator
10
10
  from wbcore.utils.strings import format_number
11
+ from wbcore.viewsets import ModelViewSet
11
12
 
12
- from wbportfolio.filters import FeesAggregatedFilter, FeesFilter, FeesPortfolioFilterSet
13
+ from wbportfolio.filters import FeesAggregatedFilter, FeesFilter, FeesProductFilterSet
13
14
  from wbportfolio.models import Fees
14
15
  from wbportfolio.serializers import FeesModelSerializer
15
16
 
16
17
  from ..configs import (
17
18
  FeeEndpointConfig,
18
- FeesAggregatedPortfolioPandasDisplayConfig,
19
- FeesAggregatedPortfolioPandasEndpointConfig,
20
- FeesAggregatedPortfolioTitleConfig,
19
+ FeesAggregatedProductPandasDisplayConfig,
20
+ FeesAggregatedProductPandasEndpointConfig,
21
+ FeesAggregatedProductTitleConfig,
21
22
  FeesButtonConfig,
22
23
  FeesDisplayConfig,
23
- FeesPortfolioDisplayConfig,
24
- FeesPortfolioEndpointConfig,
25
- FeesPortfolioTitleConfig,
24
+ FeesProductDisplayConfig,
25
+ FeesProductEndpointConfig,
26
+ FeesProductTitleConfig,
26
27
  FeesTitleConfig,
27
28
  )
28
29
  from ..mixins import UserPortfolioRequestPermissionMixin
29
- from .transactions import TransactionModelViewSet
30
30
 
31
31
 
32
- class FeesModelViewSet(TransactionModelViewSet):
32
+ class FeesModelViewSet(UserPortfolioRequestPermissionMixin, InternalUserPermissionMixin, ModelViewSet):
33
33
  filter_backends = (
34
34
  DjangoFilterBackend,
35
35
  filters.OrderingFilter,
@@ -56,27 +56,27 @@ class FeesModelViewSet(TransactionModelViewSet):
56
56
 
57
57
  def get_queryset(self):
58
58
  if self.is_manager:
59
- return super().get_queryset().select_related("import_source", "linked_product")
59
+ return super().get_queryset().select_related("import_source", "product")
60
60
  return Fees.objects.none()
61
61
 
62
62
 
63
- class FeesPortfolioModelViewSet(FeesModelViewSet):
64
- IDENTIFIER = "wbportfolio:portfolio-fees"
63
+ class FeesProductModelViewSet(FeesModelViewSet):
64
+ IDENTIFIER = "wbportfolio:product-fees"
65
65
 
66
- filterset_class = FeesPortfolioFilterSet
66
+ filterset_class = FeesProductFilterSet
67
67
 
68
- display_config_class = FeesPortfolioDisplayConfig
69
- title_config_class = FeesPortfolioTitleConfig
70
- endpoint_config_class = FeesPortfolioEndpointConfig
68
+ display_config_class = FeesProductDisplayConfig
69
+ title_config_class = FeesProductTitleConfig
70
+ endpoint_config_class = FeesProductEndpointConfig
71
71
 
72
72
  def get_queryset(self):
73
73
  if self.is_portfolio_manager:
74
- return super().get_queryset().filter(portfolio=self.portfolio)
74
+ return super().get_queryset().filter(product=self.product)
75
75
 
76
76
  return Fees.objects.none()
77
77
 
78
78
 
79
- class FeesAggregatedPortfolioPandasView(
79
+ class FeesAggregatedProductPandasView(
80
80
  UserPortfolioRequestPermissionMixin, InternalUserPermissionMixin, ExportPandasAPIViewSet
81
81
  ):
82
82
  IDENTIFIER = "wbportfolio:aggregetedfees"
@@ -85,9 +85,9 @@ class FeesAggregatedPortfolioPandasView(
85
85
 
86
86
  queryset = Fees.valid_objects.all()
87
87
 
88
- display_config_class = FeesAggregatedPortfolioPandasDisplayConfig
89
- title_config_class = FeesAggregatedPortfolioTitleConfig
90
- endpoint_config_class = FeesAggregatedPortfolioPandasEndpointConfig
88
+ display_config_class = FeesAggregatedProductPandasDisplayConfig
89
+ title_config_class = FeesAggregatedProductTitleConfig
90
+ endpoint_config_class = FeesAggregatedProductPandasEndpointConfig
91
91
 
92
92
  pandas_fields = pf.PandasFields(
93
93
  fields=(
@@ -153,7 +153,7 @@ class FeesAggregatedPortfolioPandasView(
153
153
 
154
154
  def get_queryset(self):
155
155
  if self.is_portfolio_manager:
156
- qs = super().get_queryset().filter(portfolio=self.portfolio)
156
+ qs = super().get_queryset().filter(product=self.product)
157
157
  return qs.annotate(
158
158
  currency_fx_rate_usd=CurrencyFXRates.get_fx_rates_subquery(
159
159
  "fee_date", currency="currency", lookup_expr="exact"