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
@@ -1,410 +0,0 @@
1
- # Import necessary modules
2
- from datetime import date, timedelta
3
- from decimal import Decimal
4
- from unittest.mock import call, patch
5
-
6
- import pytest
7
- from faker import Faker
8
- from pandas._libs.tslibs.offsets import BDay, BusinessMonthEnd
9
-
10
- from wbportfolio.models import Portfolio, RebalancingModel, TradeProposal
11
- from wbportfolio.pms.typing import Portfolio as PortfolioDTO
12
- from wbportfolio.pms.typing import Position
13
-
14
- fake = Faker()
15
-
16
-
17
- # Mark tests to use Django's database
18
- @pytest.mark.django_db
19
- class TestTradeProposal:
20
- # Test that the checked object is correctly set to the portfolio
21
- def test_checked_object(self, trade_proposal):
22
- """
23
- Verify that the checked object is the portfolio associated with the trade proposal.
24
- """
25
- assert trade_proposal.checked_object == trade_proposal.portfolio
26
-
27
- # Test that the evaluation date matches the trade date
28
- def test_check_evaluation_date(self, trade_proposal):
29
- """
30
- Ensure the evaluation date is the same as the trade date.
31
- """
32
- assert trade_proposal.check_evaluation_date == trade_proposal.trade_date
33
-
34
- # Test the validated trading service functionality
35
- def test_validated_trading_service(self, trade_proposal, asset_position_factory, trade_factory):
36
- """
37
- Validate that the effective and target portfolios are correctly calculated.
38
- """
39
- effective_date = (trade_proposal.trade_date - BDay(1)).date()
40
-
41
- # Create asset positions for testing
42
- a1 = asset_position_factory.create(
43
- portfolio=trade_proposal.portfolio, date=effective_date, weighting=Decimal("0.3")
44
- )
45
- a2 = asset_position_factory.create(
46
- portfolio=trade_proposal.portfolio, date=effective_date, weighting=Decimal("0.7")
47
- )
48
-
49
- # Create trades for testing
50
- t1 = trade_factory.create(
51
- trade_proposal=trade_proposal,
52
- weighting=Decimal("0.05"),
53
- portfolio=trade_proposal.portfolio,
54
- transaction_date=trade_proposal.trade_date,
55
- underlying_instrument=a1.underlying_quote,
56
- )
57
- t2 = trade_factory.create(
58
- trade_proposal=trade_proposal,
59
- weighting=Decimal("-0.05"),
60
- portfolio=trade_proposal.portfolio,
61
- transaction_date=trade_proposal.trade_date,
62
- underlying_instrument=a2.underlying_quote,
63
- )
64
-
65
- # Get the validated trading service
66
- validated_trading_service = trade_proposal.validated_trading_service
67
-
68
- # Assert effective and target portfolios are as expected
69
- assert validated_trading_service.effective_portfolio.to_dict() == {
70
- a1.underlying_quote.id: a1.weighting,
71
- a2.underlying_quote.id: a2.weighting,
72
- }
73
- assert validated_trading_service.target_portfolio.to_dict() == {
74
- a1.underlying_quote.id: a1.weighting + t1.weighting,
75
- a2.underlying_quote.id: a2.weighting + t2.weighting,
76
- }
77
-
78
- # Test the calculation of the last effective date
79
- def test_last_effective_date(self, trade_proposal, asset_position_factory):
80
- """
81
- Verify the last effective date is correctly determined based on asset positions.
82
- """
83
- # Without any positions, it should be the day before the trade date
84
- assert (
85
- trade_proposal.last_effective_date == (trade_proposal.trade_date - BDay(1)).date()
86
- ), "Last effective date without position should be t-1"
87
-
88
- # Create an asset position before the trade date
89
- a1 = asset_position_factory.create(
90
- portfolio=trade_proposal.portfolio, date=(trade_proposal.trade_date - BDay(5)).date()
91
- )
92
- a_noise = asset_position_factory.create(portfolio=trade_proposal.portfolio, date=trade_proposal.trade_date) # noqa
93
-
94
- # The last effective date should still be the day before the trade date due to caching
95
- assert (
96
- trade_proposal.last_effective_date == (trade_proposal.trade_date - BDay(1)).date()
97
- ), "last effective date is cached, so it won't change as is"
98
-
99
- # Reset the cache property to recalculate
100
- del trade_proposal.last_effective_date
101
-
102
- # Now it should be the date of the latest position before the trade date
103
- assert (
104
- trade_proposal.last_effective_date == a1.date
105
- ), "last effective date is the latest position strictly lower than trade date"
106
-
107
- # Test finding the previous trade proposal
108
- def test_previous_trade_proposal(self, trade_proposal_factory):
109
- """
110
- Ensure the previous trade proposal is correctly identified as the last approved proposal before the current one.
111
- """
112
- tp = trade_proposal_factory.create()
113
- tp_previous_submit = trade_proposal_factory.create( # noqa
114
- portfolio=tp.portfolio, status=TradeProposal.Status.SUBMIT, trade_date=(tp.trade_date - BDay(1)).date()
115
- )
116
- tp_previous_approve = trade_proposal_factory.create(
117
- portfolio=tp.portfolio, status=TradeProposal.Status.APPROVED, trade_date=(tp.trade_date - BDay(2)).date()
118
- )
119
- tp_next_approve = trade_proposal_factory.create( # noqa
120
- portfolio=tp.portfolio, status=TradeProposal.Status.APPROVED, trade_date=(tp.trade_date + BDay(1)).date()
121
- )
122
-
123
- # The previous valid trade proposal should be the approved one strictly before the current proposal
124
- assert (
125
- tp.previous_trade_proposal == tp_previous_approve
126
- ), "the previous valid trade proposal is the strictly before and approved trade proposal"
127
-
128
- # Test finding the next trade proposal
129
- def test_next_trade_proposal(self, trade_proposal_factory):
130
- """
131
- Verify the next trade proposal is correctly identified as the first approved proposal after the current one.
132
- """
133
- tp = trade_proposal_factory.create()
134
- tp_next_submit = trade_proposal_factory.create( # noqa
135
- portfolio=tp.portfolio, status=TradeProposal.Status.SUBMIT, trade_date=(tp.trade_date + BDay(1)).date()
136
- )
137
- tp_next_approve = trade_proposal_factory.create(
138
- portfolio=tp.portfolio, status=TradeProposal.Status.APPROVED, trade_date=(tp.trade_date + BDay(2)).date()
139
- )
140
- tp_previous_approve = trade_proposal_factory.create( # noqa
141
- portfolio=tp.portfolio, status=TradeProposal.Status.APPROVED, trade_date=(tp.trade_date - BDay(1)).date()
142
- )
143
-
144
- # The next valid trade proposal should be the approved one strictly after the current proposal
145
- assert (
146
- tp.next_trade_proposal == tp_next_approve
147
- ), "the next valid trade proposal is the strictly after and approved trade proposal"
148
-
149
- # Test getting the default target portfolio
150
- def test__get_default_target_portfolio(self, trade_proposal, asset_position_factory):
151
- """
152
- Ensure the default target portfolio is set to the effective portfolio from the day before the trade date.
153
- """
154
- effective_date = (trade_proposal.trade_date - BDay(1)).date()
155
-
156
- # Create asset positions for testing
157
- a1 = asset_position_factory.create(
158
- portfolio=trade_proposal.portfolio, date=effective_date, weighting=Decimal("0.3")
159
- )
160
- a2 = asset_position_factory.create(
161
- portfolio=trade_proposal.portfolio, date=effective_date, weighting=Decimal("0.7")
162
- )
163
- asset_position_factory.create(portfolio=trade_proposal.portfolio, date=trade_proposal.trade_date) # noise
164
-
165
- # The default target portfolio should match the effective portfolio
166
- assert trade_proposal._get_default_target_portfolio().to_dict() == {
167
- a1.underlying_quote.id: a1.weighting,
168
- a2.underlying_quote.id: a2.weighting,
169
- }
170
-
171
- # Test getting the default target portfolio with a rebalancing model
172
- @patch.object(RebalancingModel, "get_target_portfolio")
173
- def test__get_default_target_portfolio_with_rebalancer_model(self, mock_fct, trade_proposal, rebalancer_factory):
174
- """
175
- Verify that the target portfolio is correctly obtained from a rebalancing model.
176
- """
177
- # Expected target portfolio from the rebalancing model
178
- expected_target_portfolio = PortfolioDTO(
179
- positions=(Position(underlying_instrument=1, weighting=Decimal(1), date=trade_proposal.trade_date),)
180
- )
181
- mock_fct.return_value = expected_target_portfolio
182
-
183
- # Create a rebalancer for testing
184
- rebalancer = rebalancer_factory.create(
185
- portfolio=trade_proposal.portfolio, parameters={"rebalancer_parameter": "A"}
186
- )
187
- trade_proposal.rebalancing_model = rebalancer.rebalancing_model
188
- trade_proposal.save()
189
-
190
- # Additional keyword arguments for the rebalancing model
191
- extra_kwargs = {"test": "test"}
192
-
193
- # Combine rebalancer parameters with extra keyword arguments
194
- expected_kwargs = rebalancer.parameters
195
- expected_kwargs.update(extra_kwargs)
196
-
197
- # Assert the target portfolio matches the expected output from the rebalancing model
198
- assert (
199
- trade_proposal._get_default_target_portfolio(**extra_kwargs) == expected_target_portfolio
200
- ), "We expect the target portfolio to be whatever is returned by the rebalancer model"
201
- mock_fct.assert_called_once_with(
202
- trade_proposal.portfolio, trade_proposal.trade_date, trade_proposal.last_effective_date, **expected_kwargs
203
- )
204
-
205
- # Test normalizing trades
206
- def test_normalize_trades(self, trade_proposal, trade_factory):
207
- """
208
- Ensure trades are normalized to sum up to 1, handling quantization errors.
209
- """
210
- # Create trades for testing
211
- t1 = trade_factory.create(
212
- trade_proposal=trade_proposal,
213
- transaction_date=trade_proposal.trade_date,
214
- portfolio=trade_proposal.portfolio,
215
- weighting=Decimal(0.2),
216
- )
217
- t2 = trade_factory.create(
218
- trade_proposal=trade_proposal,
219
- transaction_date=trade_proposal.trade_date,
220
- portfolio=trade_proposal.portfolio,
221
- weighting=Decimal(0.26),
222
- )
223
- t3 = trade_factory.create(
224
- trade_proposal=trade_proposal,
225
- transaction_date=trade_proposal.trade_date,
226
- portfolio=trade_proposal.portfolio,
227
- weighting=Decimal(0.14),
228
- )
229
-
230
- # Normalize trades
231
- trade_proposal.normalize_trades()
232
-
233
- # Refresh trades from the database
234
- t1.refresh_from_db()
235
- t2.refresh_from_db()
236
- t3.refresh_from_db()
237
-
238
- # Expected normalized weights
239
- normalized_t1_weight = Decimal("0.333333")
240
- normalized_t2_weight = Decimal("0.433333")
241
- normalized_t3_weight = Decimal("0.233333")
242
-
243
- # Calculate quantization error
244
- quantize_error = Decimal(1) - (normalized_t1_weight + normalized_t2_weight + normalized_t3_weight)
245
-
246
- # Assert quantization error exists and weights are normalized correctly
247
- assert quantize_error
248
- assert t1.weighting == normalized_t1_weight
249
- assert t2.weighting == normalized_t2_weight + quantize_error # Add quantize error to the largest position
250
- assert t3.weighting == normalized_t3_weight
251
-
252
- # Test resetting trades
253
- def test_reset_trades(self, trade_proposal, instrument_factory, asset_position_factory):
254
- """
255
- Verify trades are correctly reset based on effective and target portfolios.
256
- """
257
- effective_date = trade_proposal.last_effective_date
258
-
259
- # Create instruments for testing
260
- i1 = instrument_factory.create(currency=trade_proposal.portfolio.currency)
261
- i2 = instrument_factory.create(currency=trade_proposal.portfolio.currency)
262
- i3 = instrument_factory.create(currency=trade_proposal.portfolio.currency)
263
-
264
- # Build initial effective portfolio constituting only from two positions of i1 and i2
265
- asset_position_factory.create(
266
- portfolio=trade_proposal.portfolio, date=effective_date, underlying_instrument=i1, weighting=Decimal("0.7")
267
- )
268
- asset_position_factory.create(
269
- portfolio=trade_proposal.portfolio, date=effective_date, underlying_instrument=i2, weighting=Decimal("0.3")
270
- )
271
-
272
- # build the target portfolio
273
- target_portfolio = PortfolioDTO(
274
- positions=(
275
- Position(underlying_instrument=i2.id, date=trade_proposal.trade_date, weighting=Decimal("0.4")),
276
- Position(underlying_instrument=i3.id, date=trade_proposal.trade_date, weighting=Decimal("0.6")),
277
- )
278
- )
279
-
280
- # Reset trades
281
- trade_proposal.reset_trades(target_portfolio=target_portfolio)
282
-
283
- # Get trades for each instrument
284
- t1 = trade_proposal.trades.get(underlying_instrument=i1)
285
- t2 = trade_proposal.trades.get(underlying_instrument=i2)
286
- t3 = trade_proposal.trades.get(underlying_instrument=i3)
287
-
288
- # Assert trade weights are correctly reset
289
- assert t1.weighting == Decimal("-0.7")
290
- assert t2.weighting == Decimal("0.1")
291
- assert t3.weighting == Decimal("0.6")
292
-
293
- # build the target portfolio
294
- new_target_portfolio = PortfolioDTO(
295
- positions=(
296
- Position(underlying_instrument=i1.id, date=trade_proposal.trade_date, weighting=Decimal("0.2")),
297
- Position(underlying_instrument=i2.id, date=trade_proposal.trade_date, weighting=Decimal("0.3")),
298
- Position(underlying_instrument=i3.id, date=trade_proposal.trade_date, weighting=Decimal("0.5")),
299
- )
300
- )
301
-
302
- trade_proposal.reset_trades(target_portfolio=new_target_portfolio)
303
- # Refetch the trades for each instrument
304
- t1.refresh_from_db()
305
- t2.refresh_from_db()
306
- t3.refresh_from_db()
307
- # Assert existing trade weights are correctly updated
308
- assert t1.weighting == Decimal("-0.5")
309
- assert t2.weighting == Decimal("0")
310
- assert t3.weighting == Decimal("0.5")
311
-
312
- # Test replaying trade proposals
313
- @patch.object(Portfolio, "drift_weights")
314
- def test_replay(self, mock_fct, trade_proposal_factory):
315
- """
316
- Ensure replaying trade proposals correctly calls drift_weights for each period.
317
- """
318
- mock_fct.return_value = None, None
319
-
320
- # Create approved trade proposals for testing
321
- tp0 = trade_proposal_factory.create(status=TradeProposal.Status.APPROVED)
322
- tp1 = trade_proposal_factory.create(
323
- portfolio=tp0.portfolio,
324
- status=TradeProposal.Status.APPROVED,
325
- trade_date=(tp0.trade_date + BusinessMonthEnd(1)).date(),
326
- )
327
- tp2 = trade_proposal_factory.create(
328
- portfolio=tp0.portfolio,
329
- status=TradeProposal.Status.APPROVED,
330
- trade_date=(tp1.trade_date + BusinessMonthEnd(1)).date(),
331
- )
332
-
333
- # Replay trade proposals
334
- tp0.replay()
335
-
336
- # Expected calls to drift_weights
337
- expected_calls = [
338
- call(tp0.trade_date, tp1.trade_date - timedelta(days=1)),
339
- call(tp1.trade_date, tp2.trade_date - timedelta(days=1)),
340
- call(tp2.trade_date, date.today()),
341
- ]
342
-
343
- # Assert drift_weights was called as expected
344
- mock_fct.assert_has_calls(expected_calls)
345
-
346
- # Test stopping replay on a non-approved proposal
347
- tp1.status = TradeProposal.Status.FAILED
348
- tp1.save()
349
- expected_calls = [call(tp0.trade_date, tp1.trade_date - timedelta(days=1))]
350
- mock_fct.assert_has_calls(expected_calls)
351
-
352
- # Test estimating shares for a trade
353
- @patch.object(Portfolio, "get_total_asset_value")
354
- def test_get_estimated_shares(
355
- self, mock_fct, trade_proposal, trade_factory, instrument_price_factory, instrument_factory
356
- ):
357
- """
358
- Verify shares estimation based on trade weighting and instrument price.
359
- """
360
- portfolio = trade_proposal.portfolio
361
- instrument = instrument_factory.create(currency=portfolio.currency)
362
- trade = trade_factory.create(
363
- trade_proposal=trade_proposal,
364
- transaction_date=trade_proposal.trade_date,
365
- portfolio=portfolio,
366
- underlying_instrument=instrument,
367
- )
368
- trade.refresh_from_db()
369
- underlying_quote_price = instrument_price_factory.create(instrument=instrument, date=trade.transaction_date)
370
- mock_fct.return_value = Decimal(1_000_000) # 1 million cash
371
-
372
- # Assert estimated shares are correctly calculated
373
- assert (
374
- trade_proposal.get_estimated_shares(trade.weighting, trade.underlying_instrument)
375
- == Decimal(1_000_000) * trade.weighting / underlying_quote_price.net_value
376
- )
377
-
378
- @patch.object(Portfolio, "get_total_asset_value")
379
- def test_get_estimated_target_cash(self, mock_fct, trade_proposal, trade_factory, cash_factory):
380
- mock_fct.return_value = Decimal(1_000_000) # 1 million cash
381
- cash = cash_factory.create(currency=trade_proposal.portfolio.currency)
382
- trade_factory.create( # equity trade
383
- trade_proposal=trade_proposal,
384
- transaction_date=trade_proposal.trade_date,
385
- portfolio=trade_proposal.portfolio,
386
- weighting=Decimal("0.7"),
387
- )
388
- trade_factory.create( # cash trade
389
- trade_proposal=trade_proposal,
390
- transaction_date=trade_proposal.trade_date,
391
- portfolio=trade_proposal.portfolio,
392
- underlying_instrument=cash,
393
- weighting=Decimal("0.2"),
394
- )
395
-
396
- target_cash_position = trade_proposal.get_estimated_target_cash(trade_proposal.portfolio.currency)
397
- assert target_cash_position.weighting == Decimal("0.2") + Decimal("1.0") - (Decimal("0.7") + Decimal("0.2"))
398
- assert target_cash_position.initial_shares == Decimal(1_000_000) * Decimal("0.3")
399
-
400
- def test_trade_proposal_update_inception_date(self, trade_proposal_factory, portfolio, instrument_factory):
401
- # Check that if we create a prior trade proposal, the instrument inception date is updated accordingly
402
- instrument = instrument_factory.create(inception_date=None)
403
- instrument.portfolios.add(portfolio)
404
- tp = trade_proposal_factory.create(portfolio=portfolio)
405
- instrument.refresh_from_db()
406
- assert instrument.inception_date == (tp.trade_date + BDay(1)).date()
407
-
408
- tp2 = trade_proposal_factory.create(portfolio=portfolio, trade_date=tp.trade_date - BDay(1))
409
- instrument.refresh_from_db()
410
- assert instrument.inception_date == (tp2.trade_date + BDay(1)).date()
@@ -1,66 +0,0 @@
1
- from wbcore.contrib.icons import WBIcon
2
- from wbcore.enums import RequestType
3
- from wbcore.metadata.configs import buttons as bt
4
- from wbcore.metadata.configs.buttons.view_config import ButtonViewConfig
5
-
6
-
7
- class TradeProposalButtonConfig(ButtonViewConfig):
8
- def get_custom_list_instance_buttons(self):
9
- return {
10
- bt.DropDownButton(
11
- label="Tools",
12
- buttons=(
13
- bt.ActionButton(
14
- method=RequestType.PATCH,
15
- identifiers=("wbportfolio:tradeproposal",),
16
- key="replay",
17
- icon=WBIcon.SYNCHRONIZE.icon,
18
- label="Replay Trades",
19
- description_fields="""
20
- <p>Replay Trades. It will recompute all assets positions until next trade proposal day (or today otherwise) </p>
21
- """,
22
- action_label="Replay Trade",
23
- title="Replay Trade",
24
- ),
25
- bt.ActionButton(
26
- method=RequestType.PATCH,
27
- identifiers=("wbportfolio:tradeproposal",),
28
- key="reset",
29
- icon=WBIcon.REGENERATE.icon,
30
- label="Reset Trades",
31
- description_fields="""
32
- <p>Delete and recreate initial trades to from its associated model portfolio</p>
33
- """,
34
- action_label="Reset Trades",
35
- title="Reset Trades",
36
- ),
37
- bt.ActionButton(
38
- method=RequestType.PATCH,
39
- identifiers=("wbportfolio:tradeproposal",),
40
- key="normalize",
41
- icon=WBIcon.EDIT.icon,
42
- label="Normalize Trades",
43
- description_fields="""
44
- <p>Make sure all trades normalize to a total target weight of 100%</p>
45
- """,
46
- action_label="Normalize Trades",
47
- title="Normalize Trades",
48
- ),
49
- bt.ActionButton(
50
- method=RequestType.PATCH,
51
- identifiers=("wbportfolio:tradeproposal",),
52
- key="deleteall",
53
- icon=WBIcon.DELETE.icon,
54
- label="Delete All Trades",
55
- description_fields="""
56
- <p>Delete all trades from this trade proposal?</p>
57
- """,
58
- action_label="Delete All Trades",
59
- title="Delete All Trades",
60
- ),
61
- ),
62
- ),
63
- }
64
-
65
- def get_custom_instance_buttons(self):
66
- return self.get_custom_list_instance_buttons()
@@ -1,100 +0,0 @@
1
- from typing import Optional
2
-
3
- from wbcore.contrib.color.enums import WBColor
4
- from wbcore.metadata.configs import display as dp
5
- from wbcore.metadata.configs.display.instance_display import Inline, Layout, Page
6
- from wbcore.metadata.configs.display.instance_display.operators import default
7
- from wbcore.metadata.configs.display.instance_display.shortcuts import Display
8
- from wbcore.metadata.configs.display.view_config import DisplayViewConfig
9
-
10
- from wbportfolio.models import TradeProposal
11
-
12
-
13
- class TradeProposalDisplayConfig(DisplayViewConfig):
14
- def get_list_display(self) -> Optional[dp.ListDisplay]:
15
- return dp.ListDisplay(
16
- fields=[
17
- dp.Field(key="trade_date", label="Trade Date"),
18
- dp.Field(key="comment", label="Comment"),
19
- ],
20
- legends=[
21
- dp.Legend(
22
- key="status",
23
- items=[
24
- dp.LegendItem(
25
- icon=WBColor.BLUE_LIGHT.value,
26
- label=TradeProposal.Status.DRAFT.label,
27
- value=TradeProposal.Status.DRAFT.value,
28
- ),
29
- dp.LegendItem(
30
- icon=WBColor.YELLOW_LIGHT.value,
31
- label=TradeProposal.Status.SUBMIT.label,
32
- value=TradeProposal.Status.SUBMIT.value,
33
- ),
34
- dp.LegendItem(
35
- icon=WBColor.GREEN_LIGHT.value,
36
- label=TradeProposal.Status.APPROVED.label,
37
- value=TradeProposal.Status.APPROVED.value,
38
- ),
39
- dp.LegendItem(
40
- icon=WBColor.RED_LIGHT.value,
41
- label=TradeProposal.Status.DENIED.label,
42
- value=TradeProposal.Status.DENIED.value,
43
- ),
44
- ],
45
- ),
46
- ],
47
- formatting=[
48
- dp.Formatting(
49
- column="status",
50
- formatting_rules=[
51
- dp.FormattingRule(
52
- style={"backgroundColor": WBColor.BLUE_LIGHT.value},
53
- condition=("==", TradeProposal.Status.DRAFT.value),
54
- ),
55
- dp.FormattingRule(
56
- style={"backgroundColor": WBColor.YELLOW_LIGHT.value},
57
- condition=("==", TradeProposal.Status.SUBMIT.value),
58
- ),
59
- dp.FormattingRule(
60
- style={"backgroundColor": WBColor.GREEN_LIGHT.value},
61
- condition=("==", TradeProposal.Status.APPROVED.value),
62
- ),
63
- dp.FormattingRule(
64
- style={"backgroundColor": WBColor.RED_LIGHT.value},
65
- condition=("==", TradeProposal.Status.DENIED.value),
66
- ),
67
- ],
68
- )
69
- ],
70
- )
71
-
72
- def get_instance_display(self) -> Display:
73
- return Display(
74
- pages=[
75
- Page(
76
- title="Main Information",
77
- layouts={
78
- default(): Layout(
79
- grid_template_areas=[
80
- ["status", "status", "status"],
81
- ["trade_date", "rebalancing_model", "target_portfolio"]
82
- if self.view.new_mode
83
- else ["trade_date", "rebalancing_model", "rebalancing_model"],
84
- ["comment", "comment", "comment"],
85
- ],
86
- ),
87
- },
88
- ),
89
- Page(
90
- title="Trades",
91
- layouts={
92
- default(): Layout(
93
- grid_template_areas=[["trades"]],
94
- grid_template_rows=["1fr"],
95
- inlines=[Inline(key="trades", endpoint="trades")],
96
- ),
97
- },
98
- ),
99
- ]
100
- )
@@ -1,55 +0,0 @@
1
- from typing import Optional
2
-
3
- from wbcore.metadata.configs import display as dp
4
- from wbcore.metadata.configs.display.instance_display.shortcuts import (
5
- Display,
6
- create_simple_display,
7
- )
8
- from wbcore.metadata.configs.display.instance_display.utils import repeat_field
9
- from wbcore.metadata.configs.display.view_config import DisplayViewConfig
10
-
11
-
12
- class TransactionDisplayConfig(DisplayViewConfig):
13
- def get_list_display(self) -> Optional[dp.ListDisplay]:
14
- return dp.ListDisplay(
15
- fields=[
16
- dp.Field(key="transaction_type", label="Type"),
17
- dp.Field(key="transaction_underlying_type", label="Underlying Type"),
18
- dp.Field(key="transaction_date", label="Transaction Date"),
19
- dp.Field(key="portfolio", label="Portfolio"),
20
- dp.Field(key="underlying_instrument", label="Instrument"),
21
- dp.Field(key="currency_fx_rate", label="FX Rate"),
22
- dp.Field(key="currency", label="Currency"),
23
- dp.Field(key="total_value", label="Total Value"),
24
- dp.Field(key="total_value_fx_portfolio", label="Total Value (Portfolio)"),
25
- dp.Field(key="total_value_usd", label="Total Value ($)"),
26
- ]
27
- )
28
-
29
- def get_instance_display(self) -> Display:
30
- return create_simple_display(
31
- [
32
- ["transaction_type", "underlying_instrument", "external_id"],
33
- ["transaction_date", "book_date", "value_date"],
34
- ["currency", "total_value", "total_value_fx_portfolio"],
35
- [".", "total_value_gross", "total_value_gross_fx_portfolio"],
36
- [repeat_field(3, "comment")],
37
- ]
38
- )
39
-
40
-
41
- class TransactionPortfolioDisplayConfig(TransactionDisplayConfig):
42
- def get_list_display(self) -> Optional[dp.ListDisplay]:
43
- return dp.ListDisplay(
44
- fields=[
45
- dp.Field(key="transaction_type", label="Type"),
46
- dp.Field(key="transaction_underlying_type", label="Underlying Type"),
47
- dp.Field(key="transaction_date", label="Transaction Date"),
48
- dp.Field(key="underlying_instrument", label="Instrument"),
49
- dp.Field(key="currency_fx_rate", label="FX Rate"),
50
- dp.Field(key="currency", label="Currency"),
51
- dp.Field(key="total_value", label="Total Value"),
52
- dp.Field(key="total_value_fx_portfolio", label="Total Value (Portfolio)"),
53
- dp.Field(key="total_value_usd", label="Total Value ($)"),
54
- ]
55
- )
@@ -1,18 +0,0 @@
1
- from rest_framework.reverse import reverse
2
- from wbcore.metadata.configs.endpoints import EndpointViewConfig
3
-
4
- from wbportfolio.models import TradeProposal
5
-
6
-
7
- class TradeProposalPortfolioEndpointConfig(EndpointViewConfig):
8
- def get_endpoint(self, **kwargs):
9
- return reverse(
10
- "wbportfolio:portfolio-tradeproposal-list", args=[self.view.kwargs["portfolio_id"]], request=self.request
11
- )
12
-
13
- def get_delete_endpoint(self, **kwargs):
14
- if trade_proposal_id := self.view.kwargs.get("pk", None):
15
- trade_proposal = TradeProposal.objects.get(id=trade_proposal_id)
16
- if trade_proposal.status == TradeProposal.Status.DRAFT:
17
- return reverse("wbportfolio:tradeproposal-list", args=[], request=self.request)
18
- return None
@@ -1,14 +0,0 @@
1
- from wbcore.metadata.configs.endpoints import EndpointViewConfig
2
-
3
-
4
- class TransactionEndpointConfig(EndpointViewConfig):
5
- def get_endpoint(self, **kwargs):
6
- return None
7
-
8
- def get_instance_endpoint(self, **kwargs):
9
- model = "{{transaction_url_type}}"
10
- return f"{self.request.scheme}://{self.request.get_host()}/api/portfolio/{model}/"
11
-
12
-
13
- class TransactionPortfolioEndpointConfig(TransactionEndpointConfig):
14
- pass
@@ -1,9 +0,0 @@
1
- from wbcore.menus import ItemPermission, MenuItem
2
-
3
- from wbportfolio.permissions import is_manager
4
-
5
- TRANSACTION_MENUITEM = MenuItem(
6
- label="Transactions",
7
- endpoint="wbportfolio:transaction-list",
8
- permission=ItemPermission(method=is_manager, permissions=["wbportfolio.view_transaction"]),
9
- )