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,7 +1,5 @@
1
1
  from wbcore.metadata.configs.titles import TitleViewConfig
2
2
 
3
- from wbportfolio.models import Portfolio
4
-
5
3
 
6
4
  class FeesTitleConfig(TitleViewConfig):
7
5
  def get_list_title(self):
@@ -14,13 +12,11 @@ class FeesTitleConfig(TitleViewConfig):
14
12
  return "New Fees"
15
13
 
16
14
 
17
- class FeesPortfolioTitleConfig(FeesTitleConfig):
15
+ class FeesProductTitleConfig(FeesTitleConfig):
18
16
  def get_list_title(self):
19
- portfolio = Portfolio.objects.get(id=self.view.kwargs["portfolio_id"])
20
- return f"Fees: {portfolio.name}"
17
+ return f"Fees: {self.view.product}"
21
18
 
22
19
 
23
- class FeesAggregatedPortfolioTitleConfig(TitleViewConfig):
20
+ class FeesAggregatedProductTitleConfig(TitleViewConfig):
24
21
  def get_list_title(self):
25
- portfolio = Portfolio.objects.get(id=self.view.kwargs["portfolio_id"])
26
- return f"Aggregated Fees for {portfolio.name}"
22
+ return f"Aggregated Fees for {self.view.product}"
@@ -58,12 +58,10 @@ class ESGMetricAggregationPortfolioPandasViewSet(UserPortfolioRequestPermissionM
58
58
  df = (
59
59
  pd.DataFrame(
60
60
  AssetPosition.objects.filter(portfolio=self.portfolio, date=self.val_date)
61
- .exclude(
62
- Q(underlying_instrument__is_cash=True) | Q(underlying_instrument__is_cash_equivalent=True)
63
- )
64
- .values("underlying_instrument", "weighting", "total_value_fx_usd")
61
+ .exclude(Q(underlying_quote__is_cash=True) | Q(underlying_quote__is_cash_equivalent=True))
62
+ .values("underlying_quote", "weighting", "total_value_fx_usd")
65
63
  )
66
- .groupby("underlying_instrument")
64
+ .groupby("underlying_quote")
67
65
  .sum()
68
66
  .astype(float)
69
67
  )
@@ -2,7 +2,7 @@ from django.shortcuts import get_object_or_404
2
2
  from django.utils.functional import cached_property
3
3
  from wbfdm.models import Instrument
4
4
 
5
- from wbportfolio.models import Portfolio, PortfolioRole
5
+ from wbportfolio.models import Portfolio, PortfolioRole, Product
6
6
 
7
7
 
8
8
  class UserPortfolioRequestPermissionMixin:
@@ -10,6 +10,10 @@ class UserPortfolioRequestPermissionMixin:
10
10
  def instrument(self) -> Instrument:
11
11
  return get_object_or_404(Instrument, pk=self.kwargs["instrument_id"])
12
12
 
13
+ @cached_property
14
+ def product(self) -> Instrument:
15
+ return get_object_or_404(Product, pk=self.kwargs["product_id"])
16
+
13
17
  @cached_property
14
18
  def portfolio(self) -> Portfolio:
15
19
  return get_object_or_404(Portfolio, id=self.kwargs["portfolio_id"])
@@ -0,0 +1,6 @@
1
+ from .order_proposals import (
2
+ OrderProposalModelViewSet,
3
+ OrderProposalPortfolioModelViewSet,
4
+ OrderProposalRepresentationViewSet,
5
+ )
6
+ from .orders import OrderOrderProposalModelViewSet
@@ -0,0 +1,4 @@
1
+ from .buttons import *
2
+ from .displays import *
3
+ from .endpoints import *
4
+ from .titles import *
@@ -0,0 +1,2 @@
1
+ from .order_proposals import OrderProposalButtonConfig
2
+ from .orders import OrderOrderProposalButtonConfig
@@ -0,0 +1,188 @@
1
+ from contextlib import suppress
2
+
3
+ from rest_framework.reverse import reverse
4
+ from wbcore import serializers as wb_serializers
5
+ from wbcore.contrib.icons import WBIcon
6
+ from wbcore.enums import RequestType
7
+ from wbcore.metadata.configs import buttons as bt
8
+ from wbcore.metadata.configs.buttons.view_config import ButtonViewConfig
9
+ from wbcore.metadata.configs.display import create_simple_display
10
+
11
+ from wbportfolio.models import OrderProposal, Portfolio
12
+ from wbportfolio.order_routing import ExecutionStatus
13
+ from wbportfolio.serializers import PortfolioRepresentationSerializer
14
+
15
+
16
+ class NormalizeSerializer(wb_serializers.Serializer):
17
+ total_cash_weight = wb_serializers.FloatField(default=0, precision=4, percent=True)
18
+
19
+
20
+ class ResetSerializer(wb_serializers.Serializer):
21
+ use_desired_target_weight = wb_serializers.BooleanField(
22
+ default=False,
23
+ label="Use initial target weight",
24
+ help_text="If True, the target weight used will be the value at the time the order proposal was submitted (as it may have changed due to previous modifications). If False, the delta weight will be set to 0 instead.",
25
+ )
26
+
27
+
28
+ class ExecuteSerializer(wb_serializers.Serializer):
29
+ prioritize_target_weight = wb_serializers.BooleanField(
30
+ default=False,
31
+ label="Prioritize Target Weight",
32
+ help_text="If True, we will communicate to the custodian to prioritize target weight in case both shares and target weight are communicated.",
33
+ )
34
+
35
+
36
+ class OrderProposalButtonConfig(ButtonViewConfig):
37
+ def get_custom_list_instance_buttons(self):
38
+ buttons = []
39
+ with suppress(AttributeError, AssertionError):
40
+ order_proposal = self.view.get_object()
41
+ if order_proposal.can_execute(self.request.user):
42
+ buttons.append(
43
+ bt.ActionButton(
44
+ method=RequestType.PATCH,
45
+ identifiers=("wbportfolio:order",),
46
+ icon=WBIcon.DEAL_MONEY.icon,
47
+ endpoint=reverse("wbportfolio:orderproposal-execute", args=[order_proposal.id]),
48
+ label="Execute",
49
+ action_label="Execute",
50
+ description_fields="<p>Execute the orders through the setup custodian.</p>",
51
+ serializer=ExecuteSerializer,
52
+ instance_display=create_simple_display([["prioritize_target_weight"]]),
53
+ )
54
+ )
55
+
56
+ elif order_proposal.status == OrderProposal.Status.EXECUTION and order_proposal.execution_status in [
57
+ ExecutionStatus.PENDING,
58
+ ExecutionStatus.IN_DRAFT,
59
+ ]:
60
+ buttons.append(
61
+ bt.ActionButton(
62
+ method=RequestType.PATCH,
63
+ identifiers=("wbportfolio:order",),
64
+ icon=WBIcon.PREVIOUS.icon,
65
+ endpoint=reverse("wbportfolio:orderproposal-cancelexecution", args=[order_proposal.id]),
66
+ label="Cancel Execution",
67
+ action_label="Cancel Execution",
68
+ description_fields="<p>Cancel the current requested execution. Time sensitive operation.</p>",
69
+ )
70
+ )
71
+ buttons.append(
72
+ bt.ActionButton(
73
+ method=RequestType.PATCH,
74
+ identifiers=("wbportfolio:order",),
75
+ icon=WBIcon.REFRESH.icon,
76
+ endpoint=reverse("wbportfolio:orderproposal-updateexecutionstatus", args=[order_proposal.id]),
77
+ label="Update Execution Status",
78
+ action_label="Update Execution Status",
79
+ description_fields="<p>Update Execution Status.<p>",
80
+ )
81
+ )
82
+ if order_proposal.can_be_confirmed and order_proposal.portfolio.is_model:
83
+
84
+ class PushModelChangeSerializer(wb_serializers.Serializer):
85
+ only_for_portfolio_ids = wb_serializers.PrimaryKeyRelatedField(
86
+ label="Only for Portfolios", queryset=Portfolio.objects.all()
87
+ )
88
+ _only_for_portfolio_ids = PortfolioRepresentationSerializer(
89
+ source="only_for_portfolio_ids",
90
+ many=True,
91
+ filter_params={"modeled_after": order_proposal.portfolio.id},
92
+ )
93
+ approve_automatically = wb_serializers.BooleanField(
94
+ default=False,
95
+ help_text="True if you want all created orders to be automatically move to the approve state.",
96
+ )
97
+
98
+ buttons.append(
99
+ bt.ActionButton(
100
+ method=RequestType.PATCH,
101
+ identifiers=("wbportfolio:order",),
102
+ endpoint=reverse("wbportfolio:orderproposal-pushmodelchange", args=[order_proposal.id]),
103
+ icon=WBIcon.BROADCAST.icon,
104
+ label="Push Model Changes",
105
+ description_fields=f"""
106
+ Push this rebalancing to all portfolios that are modeled after {order_proposal.portfolio}
107
+ """,
108
+ action_label="Push Model Changes",
109
+ title="Push Model Changes",
110
+ serializer=PushModelChangeSerializer,
111
+ instance_display=create_simple_display(
112
+ [["only_for_portfolio_ids"], ["approve_automatically"]]
113
+ ),
114
+ )
115
+ )
116
+ if order_proposal.status == OrderProposal.Status.DRAFT:
117
+ buttons.append(
118
+ bt.ActionButton(
119
+ method=RequestType.PATCH,
120
+ identifiers=("wbportfolio:order",),
121
+ endpoint=reverse("wbportfolio:orderproposal-refreshpretradechecks", args=[order_proposal.id]),
122
+ icon=WBIcon.RUNNING.icon,
123
+ label="Evaluate Pre-Trade Checks",
124
+ action_label="Checks are running.",
125
+ title="Evaluate Pre-Trade Checks",
126
+ )
127
+ )
128
+ buttons.append(
129
+ bt.DropDownButton(
130
+ label="Tools",
131
+ buttons=(
132
+ bt.ActionButton(
133
+ method=RequestType.PATCH,
134
+ identifiers=("wbportfolio:order",),
135
+ key="replay",
136
+ icon=WBIcon.SYNCHRONIZE.icon,
137
+ label="Replay Orders",
138
+ description_fields="""
139
+ <p>Replay Orders. It will recompute all assets positions until next order proposal day (or today otherwise) </p>
140
+ """,
141
+ action_label="Replay Order",
142
+ title="Replay Order",
143
+ ),
144
+ bt.ActionButton(
145
+ method=RequestType.PATCH,
146
+ identifiers=("wbportfolio:order",),
147
+ key="reset",
148
+ icon=WBIcon.REGENERATE.icon,
149
+ label="Reset Orders",
150
+ description_fields="""
151
+ <p><strong>Warning:</strong>This action will reset the order delta weight to either 0 or the difference between the previous weight and the locked target weight, depending on the user’s choice.</p>
152
+ <p><strong>Note:</strong>This operation will change the current delta weights and cannot be undone</p>
153
+ """,
154
+ action_label="Reset Orders",
155
+ title="Reset Orders",
156
+ serializer=ResetSerializer,
157
+ instance_display=create_simple_display([["use_desired_target_weight"]]),
158
+ ),
159
+ bt.ActionButton(
160
+ method=RequestType.PATCH,
161
+ identifiers=("wbportfolio:order",),
162
+ key="normalize",
163
+ icon=WBIcon.EDIT.icon,
164
+ label="Normalize Orders",
165
+ description_fields="""
166
+ <p>Make sure all orders normalize to a total target weight of (1 - {{total_cash_weight}})</p>
167
+ """,
168
+ action_label="Normalize Orders",
169
+ title="Normalize Orders",
170
+ serializer=NormalizeSerializer,
171
+ instance_display=create_simple_display([["total_cash_weight"]]),
172
+ ),
173
+ bt.ActionButton(
174
+ method=RequestType.PATCH,
175
+ identifiers=("wbportfolio:order",),
176
+ key="refresh_return",
177
+ icon=WBIcon.REFRESH.icon,
178
+ label="Refresh Returns & Price",
179
+ action_label="Refresh Returns & Price",
180
+ title="Refresh Returns & Price",
181
+ ),
182
+ ),
183
+ )
184
+ )
185
+ return set(buttons)
186
+
187
+ def get_custom_instance_buttons(self):
188
+ return self.get_custom_list_instance_buttons()
@@ -0,0 +1,113 @@
1
+ from wbcore import serializers as wb_serializers
2
+ from wbcore.contrib.icons import WBIcon
3
+ from wbcore.enums import RequestType
4
+ from wbcore.metadata.configs import buttons as bt
5
+ from wbcore.metadata.configs.buttons.enums import Button
6
+ from wbcore.metadata.configs.buttons.view_config import ButtonViewConfig
7
+ from wbcore.metadata.configs.display import create_simple_display
8
+
9
+ from wbportfolio.order_routing import ExecutionInstruction
10
+
11
+
12
+ class ExecutionInstructionSerializer(wb_serializers.Serializer):
13
+ execution_instruction = wb_serializers.ChoiceField(
14
+ choices=ExecutionInstruction.choices,
15
+ default=ExecutionInstruction.MARKET_ON_CLOSE.value,
16
+ clear_dependent_fields=False,
17
+ )
18
+ apply_execution_instruction_to_all_orders = wb_serializers.BooleanField(
19
+ default=False, label="Apply Execution instruction to all orders"
20
+ )
21
+
22
+ percentage = wb_serializers.FloatField(
23
+ label="Percentage [1% - 25%]",
24
+ percent=True,
25
+ required=False,
26
+ allow_null=True,
27
+ on_unsatisfied_deps="hide",
28
+ depends_on=[
29
+ {
30
+ "field": "execution_instruction",
31
+ "options": {"activates_on": [ExecutionInstruction.IN_LINE_WITH_VOLUME.value]},
32
+ }
33
+ ],
34
+ )
35
+ price = wb_serializers.FloatField(
36
+ label="Price (optional)",
37
+ required=False,
38
+ allow_null=True,
39
+ on_unsatisfied_deps="hide",
40
+ depends_on=[
41
+ {
42
+ "field": "execution_instruction",
43
+ "options": {"activates_on": [ExecutionInstruction.LIMIT_ORDER.value]},
44
+ }
45
+ ],
46
+ )
47
+ good_for_date = wb_serializers.DateField(
48
+ label="Good For Date (optional)",
49
+ required=False,
50
+ allow_null=True,
51
+ on_unsatisfied_deps="hide",
52
+ depends_on=[
53
+ {
54
+ "field": "execution_instruction",
55
+ "options": {"activates_on": [ExecutionInstruction.LIMIT_ORDER.value]},
56
+ }
57
+ ],
58
+ )
59
+ period = wb_serializers.IntegerField(
60
+ label="Period in minutes",
61
+ required=False,
62
+ allow_null=True,
63
+ on_unsatisfied_deps="hide",
64
+ depends_on=[
65
+ {
66
+ "field": "execution_instruction",
67
+ "options": {"activates_on": [ExecutionInstruction.VWAP.value, ExecutionInstruction.TWAP.value]},
68
+ }
69
+ ],
70
+ )
71
+ time = wb_serializers.TimeField(
72
+ label="Time (UTC)",
73
+ format="%H:%M",
74
+ required=False,
75
+ allow_null=True,
76
+ on_unsatisfied_deps="hide",
77
+ depends_on=[
78
+ {
79
+ "field": "execution_instruction",
80
+ "options": {"activates_on": [ExecutionInstruction.VWAP.value, ExecutionInstruction.TWAP.value]},
81
+ }
82
+ ],
83
+ )
84
+
85
+
86
+ class OrderOrderProposalButtonConfig(ButtonViewConfig):
87
+ def get_create_buttons(self):
88
+ return {
89
+ Button.SAVE_AND_CLOSE.value,
90
+ }
91
+
92
+ def get_custom_list_instance_buttons(self) -> set:
93
+ return {
94
+ bt.ActionButton(
95
+ method=RequestType.PUT,
96
+ identifiers=("wbportfolio:order",),
97
+ icon=WBIcon.DEAL_MONEY.icon,
98
+ key="execution_instruction",
99
+ label="Change Execution",
100
+ action_label="Execution changed",
101
+ description_fields="<p>Change Execution</p>",
102
+ serializer=ExecutionInstructionSerializer,
103
+ instance_display=create_simple_display(
104
+ [
105
+ ["execution_instruction", "execution_instruction"],
106
+ ["percentage", "percentage"],
107
+ ["price", "good_for_date"],
108
+ ["period", "time"],
109
+ ["apply_execution_instruction_to_all_orders", "."],
110
+ ]
111
+ ),
112
+ )
113
+ }
@@ -0,0 +1,2 @@
1
+ from .order_proposals import OrderProposalDisplayConfig
2
+ from .orders import OrderOrderProposalDisplayConfig
@@ -0,0 +1,157 @@
1
+ from contextlib import suppress
2
+ from typing import Optional
3
+
4
+ from wbcore.contrib.color.enums import WBColor
5
+ from wbcore.metadata.configs import display as dp
6
+ from wbcore.metadata.configs.display import Section
7
+ from wbcore.metadata.configs.display.instance_display import Inline, Layout, Page
8
+ from wbcore.metadata.configs.display.instance_display.operators import default
9
+ from wbcore.metadata.configs.display.instance_display.shortcuts import Display
10
+ from wbcore.metadata.configs.display.view_config import DisplayViewConfig
11
+
12
+ from wbportfolio.models import OrderProposal
13
+
14
+
15
+ class OrderProposalDisplayConfig(DisplayViewConfig):
16
+ def get_list_display(self) -> Optional[dp.ListDisplay]:
17
+ return dp.ListDisplay(
18
+ fields=[
19
+ dp.Field(key="portfolio", label="Portfolio") if "portfolio_id" not in self.view.kwargs else None,
20
+ dp.Field(key="trade_date", label="Order Date"),
21
+ dp.Field(key="rebalancing_model", label="Rebalancing Model"),
22
+ dp.Field(key="comment", label="Comment"),
23
+ dp.Field(key="creator", label="Creator"),
24
+ dp.Field(key="approver", label="Approver"),
25
+ ],
26
+ legends=[
27
+ dp.Legend(
28
+ key="status",
29
+ items=[
30
+ dp.LegendItem(
31
+ icon=WBColor.BLUE_LIGHT.value,
32
+ label=OrderProposal.Status.DRAFT.label,
33
+ value=OrderProposal.Status.DRAFT.value,
34
+ ),
35
+ dp.LegendItem(
36
+ icon=WBColor.YELLOW_LIGHT.value,
37
+ label=OrderProposal.Status.PENDING.label,
38
+ value=OrderProposal.Status.PENDING.value,
39
+ ),
40
+ dp.LegendItem(
41
+ icon=WBColor.GREEN_LIGHT.value,
42
+ label=OrderProposal.Status.APPROVED.label,
43
+ value=OrderProposal.Status.APPROVED.value,
44
+ ),
45
+ dp.LegendItem(
46
+ icon=WBColor.RED_LIGHT.value,
47
+ label=OrderProposal.Status.DENIED.label,
48
+ value=OrderProposal.Status.DENIED.value,
49
+ ),
50
+ dp.LegendItem(
51
+ icon=WBColor.GREEN.value,
52
+ label=OrderProposal.Status.CONFIRMED.label,
53
+ value=OrderProposal.Status.CONFIRMED.value,
54
+ ),
55
+ dp.LegendItem(
56
+ icon=WBColor.GREY.value,
57
+ label=OrderProposal.Status.EXECUTION.label,
58
+ value=OrderProposal.Status.EXECUTION.value,
59
+ ),
60
+ ],
61
+ ),
62
+ ],
63
+ formatting=[
64
+ dp.Formatting(
65
+ column="status",
66
+ formatting_rules=[
67
+ dp.FormattingRule(
68
+ style={"backgroundColor": WBColor.BLUE_LIGHT.value},
69
+ condition=("==", OrderProposal.Status.DRAFT.value),
70
+ ),
71
+ dp.FormattingRule(
72
+ style={"backgroundColor": WBColor.YELLOW_LIGHT.value},
73
+ condition=("==", OrderProposal.Status.PENDING.value),
74
+ ),
75
+ dp.FormattingRule(
76
+ style={"backgroundColor": WBColor.GREEN_LIGHT.value},
77
+ condition=("==", OrderProposal.Status.APPROVED.value),
78
+ ),
79
+ dp.FormattingRule(
80
+ style={"backgroundColor": WBColor.GREEN.value},
81
+ condition=("==", OrderProposal.Status.CONFIRMED.value),
82
+ ),
83
+ dp.FormattingRule(
84
+ style={"backgroundColor": WBColor.GREY.value},
85
+ condition=("==", OrderProposal.Status.EXECUTION.value),
86
+ ),
87
+ dp.FormattingRule(
88
+ style={"backgroundColor": WBColor.RED_LIGHT.value},
89
+ condition=("==", OrderProposal.Status.DENIED.value),
90
+ ),
91
+ dp.FormattingRule(
92
+ style={"backgroundColor": WBColor.RED_DARK.value},
93
+ condition=("==", OrderProposal.Status.FAILED.value),
94
+ ),
95
+ ],
96
+ )
97
+ ],
98
+ )
99
+
100
+ def get_instance_display(self) -> Display:
101
+ orders_grid_template_areas = [["orders"]]
102
+ orders_grid_template_rows = ["1fr"]
103
+ sections = []
104
+ with suppress(AttributeError, AssertionError):
105
+ op = self.view.get_object()
106
+ if op.execution_status:
107
+ orders_grid_template_areas = [["execution"], ["orders"]]
108
+ orders_grid_template_rows = ["100px", "1fr"]
109
+ sections.append(
110
+ Section(
111
+ key="execution",
112
+ title="Execution",
113
+ collapsed=False,
114
+ display=Display(
115
+ pages=[
116
+ Page(
117
+ layouts={
118
+ default(): Layout(
119
+ grid_template_areas=[["execution_status_repr", "execution_comment"]],
120
+ grid_template_columns=["0.3fr", "0.7fr"],
121
+ )
122
+ }
123
+ )
124
+ ]
125
+ ),
126
+ )
127
+ )
128
+
129
+ main_info_page = Page(
130
+ title="Main Information",
131
+ layouts={
132
+ default(): Layout(
133
+ grid_template_areas=[
134
+ ["status", "status", "status", "status"],
135
+ ["trade_date", "total_cash_weight", "min_order_value", "min_weighting"],
136
+ ["rebalancing_model", "rebalancing_model", "target_portfolio", "target_portfolio"]
137
+ if self.view.new_mode
138
+ else ["rebalancing_model", "rebalancing_model", "creator", "approver"],
139
+ ["comment", "comment", "comment", "comment"],
140
+ ],
141
+ ),
142
+ },
143
+ )
144
+ orders_page = Page(
145
+ title="Orders",
146
+ layouts={
147
+ default(): Layout(
148
+ grid_template_areas=orders_grid_template_areas,
149
+ grid_template_rows=orders_grid_template_rows,
150
+ inlines=[Inline(key="orders", endpoint="orders")],
151
+ sections=sections,
152
+ ),
153
+ },
154
+ )
155
+ return Display(
156
+ pages=[orders_page, main_info_page] if "pk" in self.view.kwargs else [main_info_page, orders_page]
157
+ )