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
@@ -6,7 +6,7 @@ import pytest
6
6
  from django.core.exceptions import ValidationError
7
7
  from pandas._libs.tslibs.offsets import BDay
8
8
 
9
- from wbportfolio.models import AssetPosition, TradeProposal
9
+ from wbportfolio.models import AssetPosition, OrderProposal
10
10
 
11
11
 
12
12
  @pytest.mark.django_db
@@ -47,7 +47,7 @@ class TestRebalancer:
47
47
  def test_evaluate_rebalancing(
48
48
  self, weekday, rebalancer_factory, asset_position_factory, instrument_factory, instrument_price_factory
49
49
  ):
50
- rebalancer = rebalancer_factory.create(approve_trade_proposal_automatically=True)
50
+ rebalancer = rebalancer_factory.create(apply_order_proposal_automatically=True)
51
51
  trade_date = (weekday + BDay(1)).date()
52
52
 
53
53
  i1 = instrument_factory.create()
@@ -63,9 +63,9 @@ class TestRebalancer:
63
63
  a2 = asset_position_factory.create(
64
64
  portfolio=rebalancer.portfolio, date=weekday, weighting=0.3, underlying_instrument=i2
65
65
  )
66
- trade_proposal = rebalancer.evaluate_rebalancing(trade_date)
67
- assert trade_proposal.trades.count() == 2
68
- assert trade_proposal.status == TradeProposal.Status.APPROVED
66
+ order_proposal = rebalancer.evaluate_rebalancing(trade_date)
67
+ assert order_proposal.orders.count() == 2
68
+ assert order_proposal.status == OrderProposal.Status.CONFIRMED
69
69
  assert AssetPosition.objects.get(
70
70
  portfolio=rebalancer.portfolio, date=trade_date, underlying_quote=a1.underlying_instrument
71
71
  ).weighting == Decimal(0.5)
@@ -3,7 +3,6 @@ from decimal import Decimal
3
3
 
4
4
  import pytest
5
5
  from faker import Faker
6
- from pandas._libs.tslibs.offsets import BDay
7
6
 
8
7
  from wbportfolio.models import Product, Trade
9
8
 
@@ -103,12 +102,6 @@ class TestTradeModel:
103
102
  assert customer_trade.claimed_shares == c2.shares
104
103
 
105
104
 
106
- @pytest.mark.django_db
107
- class TestTradeProposalModel:
108
- def test_init(self, trade_proposal):
109
- assert trade_proposal.id is not None
110
-
111
-
112
105
  @pytest.mark.django_db
113
106
  class TestTradeInstrumentPrice:
114
107
  def test_shares(self, portfolio, product_factory, trade_factory, instrument_price_factory):
@@ -204,16 +197,3 @@ class TestTradeInstrumentPrice:
204
197
  subscription_trade1.refresh_from_db()
205
198
  assert subscription_trade1.internal_trade == internal_trade
206
199
  assert subscription_trade1.marked_as_internal is True
207
-
208
- def test_last_underlying_quote_price(self, weekday, trade_factory, instrument_price_factory):
209
- trade = trade_factory.create(transaction_date=weekday, value_date=(weekday - BDay(1)).date())
210
- assert trade.last_underlying_quote_price is None
211
- del trade.last_underlying_quote_price
212
-
213
- # test that underlying quote price returns any price found at transaction_date, or then at value_date (in that order)
214
- p0 = instrument_price_factory.create(instrument=trade.underlying_instrument, date=trade.value_date)
215
- assert trade.last_underlying_quote_price == p0
216
- del trade.last_underlying_quote_price
217
-
218
- p1 = instrument_price_factory.create(instrument=trade.underlying_instrument, date=trade.transaction_date)
219
- assert trade.last_underlying_quote_price == p1
@@ -1,4 +1,5 @@
1
1
  import pandas as pd
2
+ import pytest
2
3
 
3
4
  from wbportfolio.pms.analytics.portfolio import Portfolio
4
5
 
@@ -15,9 +16,27 @@ def test_get_next_weights():
15
16
  portfolio = Portfolio(X=pd.DataFrame([returns]), weights=pd.Series(weights))
16
17
  next_weights = portfolio.get_next_weights()
17
18
 
18
- assert next_weights[0] == w0 * (r0 + 1) / (w0 * (r0 + 1) + w1 * (r1 + 1) + w2 * (r2 + 1))
19
- assert next_weights[1] == w1 * (r1 + 1) / (w0 * (r0 + 1) + w1 * (r1 + 1) + w2 * (r2 + 1))
20
- assert next_weights[2] == w2 * (r2 + 1) / (w0 * (r0 + 1) + w1 * (r1 + 1) + w2 * (r2 + 1))
19
+ assert next_weights[0] == pytest.approx(w0 * (r0 + 1) / (w0 * (r0 + 1) + w1 * (r1 + 1) + w2 * (r2 + 1)), abs=10e-8)
20
+ assert next_weights[1] == pytest.approx(w1 * (r1 + 1) / (w0 * (r0 + 1) + w1 * (r1 + 1) + w2 * (r2 + 1)), abs=10e-8)
21
+ assert next_weights[2] == pytest.approx(w2 * (r2 + 1) / (w0 * (r0 + 1) + w1 * (r1 + 1) + w2 * (r2 + 1)), abs=10e-8)
22
+
23
+
24
+ def test_get_next_weights_solve_quantization_error():
25
+ w0 = 0.33333334
26
+ w1 = 0.33333333
27
+ w2 = 0.33333333
28
+ weights = [w0, w1, w2]
29
+ returns = [1.0, 1.0, 1.0] # no returns
30
+ portfolio = Portfolio(X=pd.DataFrame([returns]), weights=pd.Series(weights))
31
+ next_weights = portfolio.get_next_weights(round_precision=8) # no rounding as number are all 8 decimals
32
+ assert sum(next_weights.values()) == 1.0
33
+ next_weights = portfolio.get_next_weights(
34
+ round_precision=7
35
+ ) # we expect the weight to be rounded to 6 decimals, which would lead to a total sum of 0.999999
36
+
37
+ assert next_weights[0] == 0.3333334
38
+ assert next_weights[1] == 0.3333333
39
+ assert next_weights[2] == 0.3333333
21
40
 
22
41
 
23
42
  def test_get_estimate_net_value():
@@ -4,28 +4,34 @@ import pytest
4
4
  from pandas._libs.tslibs.offsets import BDay
5
5
  from wbfdm.models import InstrumentPrice
6
6
 
7
- from wbportfolio.factories import PortfolioFactory, TradeFactory, TradeProposalFactory
8
- from wbportfolio.models import PortfolioPortfolioThroughModel, Trade, TradeProposal
7
+ from wbportfolio.factories import OrderFactory, OrderProposalFactory, PortfolioFactory
8
+ from wbportfolio.models import Order, OrderProposal, PortfolioPortfolioThroughModel
9
9
 
10
10
 
11
11
  @pytest.mark.django_db
12
12
  class TestEquallyWeightedRebalancing:
13
- @pytest.fixture()
14
- def model(self, portfolio, weekday):
13
+ def test_is_valid(self, portfolio, weekday, asset_position_factory, instrument_price_factory):
15
14
  from wbportfolio.rebalancing.models import EquallyWeightedRebalancing
16
15
 
17
- return EquallyWeightedRebalancing(portfolio, (weekday + BDay(1)).date(), weekday)
18
-
19
- def test_is_valid(self, portfolio, weekday, model, asset_position_factory, instrument_price_factory):
16
+ trade_date = (weekday + BDay(1)).date()
17
+ model = EquallyWeightedRebalancing(portfolio, trade_date, weekday)
20
18
  assert not model.is_valid()
21
- a = asset_position_factory.create(portfolio=model.portfolio, date=model.last_effective_date)
19
+
20
+ a = asset_position_factory.create(portfolio=portfolio, date=weekday)
21
+ model = EquallyWeightedRebalancing(portfolio, trade_date, weekday)
22
22
  assert not model.is_valid()
23
- instrument_price_factory.create(instrument=a.underlying_quote, date=model.trade_date)
23
+
24
+ instrument_price_factory.create(instrument=a.underlying_quote, date=trade_date)
25
+ model = EquallyWeightedRebalancing(portfolio, trade_date, weekday)
24
26
  assert model.is_valid()
25
27
 
26
- def test_get_target_portfolio(self, portfolio, weekday, model, asset_position_factory):
27
- a1 = asset_position_factory(weighting=0.7, portfolio=portfolio, date=model.last_effective_date)
28
- a2 = asset_position_factory(weighting=0.3, portfolio=portfolio, date=model.last_effective_date)
28
+ def test_get_target_portfolio(self, portfolio, weekday, asset_position_factory):
29
+ from wbportfolio.rebalancing.models import EquallyWeightedRebalancing
30
+
31
+ a1 = asset_position_factory(weighting=0.7, portfolio=portfolio, date=weekday)
32
+ a2 = asset_position_factory(weighting=0.3, portfolio=portfolio, date=weekday)
33
+ model = EquallyWeightedRebalancing(portfolio, (weekday + BDay(1)).date(), weekday)
34
+
29
35
  target_portfolio = model.get_target_portfolio()
30
36
  target_positions = target_portfolio.positions_map
31
37
  assert target_positions[a1.underlying_instrument.id].weighting == Decimal(0.5)
@@ -38,30 +44,22 @@ class TestModelPortfolioRebalancing:
38
44
  def model(self, portfolio, weekday):
39
45
  from wbportfolio.rebalancing.models import ModelPortfolioRebalancing
40
46
 
47
+ PortfolioPortfolioThroughModel.objects.create(
48
+ portfolio=portfolio,
49
+ dependency_portfolio=PortfolioFactory.create(),
50
+ type=PortfolioPortfolioThroughModel.Type.MODEL,
51
+ )
41
52
  return ModelPortfolioRebalancing(portfolio, (weekday + BDay(1)).date(), weekday)
42
53
 
43
54
  def test_is_valid(self, portfolio, weekday, model, asset_position_factory, instrument_price_factory):
44
55
  assert not model.is_valid()
45
56
  asset_position_factory.create(portfolio=model.portfolio, date=model.last_effective_date)
46
57
  assert not model.is_valid()
47
- model_portfolio = PortfolioFactory.create()
48
- PortfolioPortfolioThroughModel.objects.create(
49
- portfolio=model.portfolio,
50
- dependency_portfolio=model_portfolio,
51
- type=PortfolioPortfolioThroughModel.Type.MODEL,
52
- )
53
- a = asset_position_factory.create(portfolio=model.model_portfolio, date=model.last_effective_date)
54
- assert not model.is_valid()
55
- instrument_price_factory.create(instrument=a.underlying_quote, date=model.trade_date)
58
+
59
+ asset_position_factory.create(portfolio=model.model_portfolio, date=model.last_effective_date)
56
60
  assert model.is_valid()
57
61
 
58
62
  def test_get_target_portfolio(self, portfolio, weekday, model, asset_position_factory):
59
- model_portfolio = PortfolioFactory.create()
60
- PortfolioPortfolioThroughModel.objects.create(
61
- portfolio=model.portfolio,
62
- dependency_portfolio=model_portfolio,
63
- type=PortfolioPortfolioThroughModel.Type.MODEL,
64
- )
65
63
  asset_position_factory(portfolio=portfolio, date=model.last_effective_date) # noise
66
64
  asset_position_factory(portfolio=portfolio, date=model.last_effective_date) # noise
67
65
  a1 = asset_position_factory(weighting=0.8, portfolio=portfolio.model_portfolio, date=model.last_effective_date)
@@ -83,50 +81,46 @@ class TestCompositeRebalancing:
83
81
  def test_is_valid(self, portfolio, weekday, model, asset_position_factory, instrument_price_factory):
84
82
  assert not model.is_valid()
85
83
 
86
- trade_proposal = TradeProposalFactory.create(
87
- portfolio=model.portfolio, trade_date=model.last_effective_date, status=TradeProposal.Status.APPROVED
84
+ order_proposal = OrderProposalFactory.create(
85
+ portfolio=model.portfolio, trade_date=model.last_effective_date, status=OrderProposal.Status.CONFIRMED
88
86
  )
89
- t1 = TradeFactory.create(
87
+ t1 = OrderFactory.create(
90
88
  portfolio=model.portfolio,
91
- transaction_date=model.last_effective_date,
92
- transaction_subtype=Trade.Type.BUY,
93
- trade_proposal=trade_proposal,
94
- weighting=0.7,
95
- status=Trade.Status.EXECUTED,
89
+ value_date=model.last_effective_date,
90
+ order_type=Order.Type.BUY,
91
+ order_proposal=order_proposal,
92
+ weighting=Decimal(0.7),
96
93
  )
97
- TradeFactory.create(
94
+ OrderFactory.create(
98
95
  portfolio=model.portfolio,
99
- transaction_date=model.last_effective_date,
100
- transaction_subtype=Trade.Type.BUY,
101
- trade_proposal=trade_proposal,
102
- weighting=0.3,
103
- status=Trade.Status.EXECUTED,
96
+ value_date=model.last_effective_date,
97
+ order_type=Order.Type.BUY,
98
+ order_proposal=order_proposal,
99
+ weighting=Decimal(0.3),
104
100
  )
105
101
  assert not model.is_valid()
106
102
  instrument_price_factory.create(instrument=t1.underlying_instrument, date=model.trade_date)
107
103
  assert model.is_valid()
108
104
 
109
105
  def test_get_target_portfolio(self, portfolio, weekday, model, asset_position_factory):
110
- trade_proposal = TradeProposalFactory.create(
111
- portfolio=model.portfolio, trade_date=model.last_effective_date, status=TradeProposal.Status.APPROVED
106
+ order_proposal = OrderProposalFactory.create(
107
+ portfolio=model.portfolio, trade_date=model.last_effective_date, status=OrderProposal.Status.CONFIRMED
112
108
  )
113
109
  asset_position_factory(portfolio=portfolio, date=model.last_effective_date) # noise
114
110
  asset_position_factory(portfolio=portfolio, date=model.last_effective_date) # noise
115
- t1 = TradeFactory.create(
111
+ t1 = OrderFactory.create(
116
112
  portfolio=model.portfolio,
117
- transaction_date=model.last_effective_date,
118
- transaction_subtype=Trade.Type.BUY,
119
- trade_proposal=trade_proposal,
120
- weighting=0.8,
121
- status=Trade.Status.EXECUTED,
113
+ value_date=model.last_effective_date,
114
+ order_type=Order.Type.BUY,
115
+ order_proposal=order_proposal,
116
+ weighting=Decimal(0.8),
122
117
  )
123
- t2 = TradeFactory.create(
118
+ t2 = OrderFactory.create(
124
119
  portfolio=model.portfolio,
125
- transaction_date=model.last_effective_date,
126
- transaction_subtype=Trade.Type.BUY,
127
- trade_proposal=trade_proposal,
128
- weighting=0.2,
129
- status=Trade.Status.EXECUTED,
120
+ value_date=model.last_effective_date,
121
+ order_type=Order.Type.BUY,
122
+ order_proposal=order_proposal,
123
+ weighting=Decimal(0.2),
130
124
  )
131
125
  target_portfolio = model.get_target_portfolio()
132
126
  target_positions = target_portfolio.positions_map
@@ -162,5 +156,5 @@ class TestMarketCapitalizationRebalancing:
162
156
 
163
157
  target_portfolio = model.get_target_portfolio()
164
158
  target_positions = target_portfolio.positions_map
165
- assert target_positions[i1].weighting == mkt12 / (mkt12 + mkt21)
166
- assert target_positions[i2].weighting == mkt21 / (mkt12 + mkt21)
159
+ assert target_positions[i1].weighting == pytest.approx(Decimal(mkt12 / (mkt12 + mkt21)), abs=Decimal(1e-8))
160
+ assert target_positions[i2].weighting == pytest.approx(Decimal(mkt21 / (mkt12 + mkt21)), abs=Decimal(1e-8))
@@ -6,9 +6,9 @@ from wbfdm.factories import InstrumentPriceFactory
6
6
 
7
7
  from wbportfolio.factories import (
8
8
  CustomerTradeFactory,
9
+ OrderProposalFactory,
9
10
  ProductFactory,
10
11
  ProductPortfolioRoleFactory,
11
- TradeProposalFactory,
12
12
  )
13
13
  from wbportfolio.viewsets import (
14
14
  AssetPositionInstrumentModelViewSet,
@@ -17,17 +17,15 @@ from wbportfolio.viewsets import (
17
17
  ClaimEntryModelViewSet,
18
18
  CustodianDistributionInstrumentChartViewSet,
19
19
  CustomerDistributionInstrumentChartViewSet,
20
- DistributionChartViewSet,
21
- DistributionTableViewSet,
22
20
  NominalProductChartView,
21
+ OrderOrderProposalModelViewSet,
22
+ OrderProposalPortfolioModelViewSet,
23
23
  PortfolioRoleInstrumentModelViewSet,
24
24
  ProductCustomerModelViewSet,
25
25
  ProductPerformanceFeesModelViewSet,
26
26
  SubscriptionRedemptionInstrumentModelViewSet,
27
27
  SubscriptionRedemptionModelViewSet,
28
28
  TradeInstrumentModelViewSet,
29
- TradeProposalPortfolioModelViewSet,
30
- TradeTradeProposalModelViewSet,
31
29
  )
32
30
 
33
31
  # =================================================================================================================
@@ -54,18 +52,18 @@ def receive_factory_product_portfolio_role(sender, *args, **kwargs):
54
52
  # =================================================================================================================
55
53
  # UPDATE KWARGS
56
54
  # =================================================================================================================
57
- @receiver(custom_update_kwargs, sender=TradeTradeProposalModelViewSet)
58
- def receive_kwargs_trade_trade_proposal_product(sender, *args, **kwargs):
55
+ @receiver(custom_update_kwargs, sender=OrderOrderProposalModelViewSet)
56
+ def receive_kwargs_trade_order_proposal_product(sender, *args, **kwargs):
59
57
  if obj := kwargs.get("obj_factory"):
60
- trade_proposal = TradeProposalFactory.create()
61
- obj.trade_proposal = trade_proposal
58
+ order_proposal = OrderProposalFactory.create()
59
+ obj.order_proposal = order_proposal
62
60
  obj.save()
63
- return {"trade_proposal_id": trade_proposal.id}
61
+ return {"order_proposal_id": order_proposal.id}
64
62
  return {}
65
63
 
66
64
 
67
- @receiver(custom_update_kwargs, sender=TradeProposalPortfolioModelViewSet)
68
- def receive_kwargs_trade_trade_proposal_portfolio(sender, *args, **kwargs):
65
+ @receiver(custom_update_kwargs, sender=OrderProposalPortfolioModelViewSet)
66
+ def receive_kwargs_trade_order_proposal_portfolio(sender, *args, **kwargs):
69
67
  if obj := kwargs.get("obj_factory"):
70
68
  return {"portfolio_id": obj.portfolio.id}
71
69
  return {}
@@ -96,14 +94,6 @@ def receive_kwargs_portfolio_role_instrument(sender, *args, **kwargs):
96
94
  return {}
97
95
 
98
96
 
99
- @receiver(custom_update_kwargs, sender=DistributionChartViewSet)
100
- @receiver(custom_update_kwargs, sender=DistributionTableViewSet)
101
- def receive_kwargs_distribution_chart_instrument_id(sender, *args, **kwargs):
102
- if instrument_id := kwargs.get("underlying_instrument_id"):
103
- return {"instrument_id": instrument_id}
104
- return {}
105
-
106
-
107
97
  @receiver(custom_update_kwargs, sender=ProductPerformanceFeesModelViewSet)
108
98
  def receive_kwargs_product_performance_fees(sender, *args, **kwargs):
109
99
  CurrencyFXRatesFactory()
@@ -9,7 +9,7 @@ for key, value in default_config.items():
9
9
  and x.__name__
10
10
  not in [
11
11
  "AggregatedAssetPositionLiquidityPandasView",
12
- "TradeTradeProposalModelViewSet",
12
+ "OrderOrderProposalModelViewSet",
13
13
  "PortfolioSwingPricing",
14
14
  "PortfolioCashTarget",
15
15
  "PortfolioSwingPricingModelViewSet",
@@ -19,6 +19,8 @@ for key, value in default_config.items():
19
19
  "AccountReconciliationLine",
20
20
  "TopDownPortfolioCompositionPandasAPIView",
21
21
  "CompositionModelPortfolioPandasView",
22
+ "DistributionChartViewSet",
23
+ "DistributionTableViewSet",
22
24
  # "ClaimModelViewSet",
23
25
  # "ClaimModelSerializer",
24
26
  ],
@@ -23,6 +23,7 @@ class TestProductModelViewSet:
23
23
  portfolio_factory.create_batch(4, invested_timespan=DateRange(date.min, date.max))
24
24
  + portfolio_factory.create_batch(2, invested_timespan=DateRange(date.min, date.max)),
25
25
  product_factory.create_batch(6),
26
+ strict=False,
26
27
  ):
27
28
  InstrumentPortfolioThroughModel.objects.update_or_create(
28
29
  instrument=product, defaults={"portfolio": portfolio}
wbportfolio/urls.py CHANGED
@@ -30,7 +30,6 @@ router.register(
30
30
  router.register(r"portfoliorole", viewsets.PortfolioRoleModelViewSet, basename="portfoliorole")
31
31
 
32
32
 
33
- router.register(r"transaction", viewsets.TransactionModelViewSet, basename="transaction")
34
33
  router.register(r"fees", viewsets.FeesModelViewSet, basename="fees")
35
34
  router.register(r"trade", viewsets.TradeModelViewSet, basename="trade")
36
35
  router.register(
@@ -39,7 +38,7 @@ router.register(
39
38
 
40
39
  router.register(r"traderepresentation", viewsets.TradeRepresentationViewSet, basename="traderepresentation")
41
40
  router.register(
42
- r"tradeproposalrepresentation", viewsets.TradeProposalRepresentationViewSet, basename="tradeproposalrepresentation"
41
+ r"orderproposalrepresentation", viewsets.OrderProposalRepresentationViewSet, basename="orderproposalrepresentation"
43
42
  )
44
43
  router.register(
45
44
  r"rebalancingmodelrepresentation",
@@ -63,7 +62,7 @@ router.register(r"aumchart", viewsets.InstrumentPriceAUMDataChartView, basename=
63
62
 
64
63
  router.register(r"custodian", viewsets.CustodianModelViewSet, basename="custodian")
65
64
 
66
- router.register(r"tradeproposal", viewsets.TradeProposalModelViewSet, basename="tradeproposal")
65
+ router.register(r"orderproposal", viewsets.OrderProposalModelViewSet, basename="orderproposal")
67
66
 
68
67
  router.register(
69
68
  r"assetandnetnewmoneyprogression",
@@ -79,8 +78,6 @@ portfolio_router.register(
79
78
  )
80
79
  portfolio_router.register(r"asset", viewsets.AssetPositionPortfolioModelViewSet, basename="portfolio-asset")
81
80
  portfolio_router.register(r"contributor", viewsets.ContributorPortfolioChartView, basename="portfolio-contributor")
82
- portfolio_router.register(r"fees", viewsets.FeesPortfolioModelViewSet, basename="portfolio-fees")
83
- portfolio_router.register(r"transaction", viewsets.TransactionPortfolioModelViewSet, basename="portfolio-transaction")
84
81
  portfolio_router.register(r"trade", viewsets.TradePortfolioModelViewSet, basename="portfolio-trade")
85
82
  portfolio_router.register(
86
83
  r"instrument", viewsets.InstrumentPortfolioThroughPortfolioModelViewSet, basename="portfolio-instrument"
@@ -91,7 +88,7 @@ portfolio_router.register(
91
88
  basename="portfolio-preferredclassification",
92
89
  )
93
90
  portfolio_router.register(
94
- r"tradeproposal", viewsets.TradeProposalPortfolioModelViewSet, basename="portfolio-tradeproposal"
91
+ r"orderproposal", viewsets.OrderProposalPortfolioModelViewSet, basename="portfolio-orderproposal"
95
92
  )
96
93
  portfolio_router.register(
97
94
  r"dependencyportfolio", viewsets.PortfolioPortfolioThroughModelViewSet, basename="portfolio-dependencyportfolio"
@@ -101,9 +98,7 @@ portfolio_router.register(
101
98
  viewsets.CompositionModelPortfolioPandasView,
102
99
  basename="portfolio-modelcompositionpandas",
103
100
  )
104
- portfolio_router.register(
105
- r"feesaggregated", viewsets.FeesAggregatedPortfolioPandasView, basename="portfolio-feesaggregated"
106
- )
101
+
107
102
  portfolio_router.register(
108
103
  r"distributionchart",
109
104
  viewsets.DistributionChartViewSet,
@@ -139,10 +134,12 @@ product_router = WBCoreRouter()
139
134
  product_router.register(r"nominalchart", viewsets.NominalProductChartView, basename="product-nominalchart")
140
135
  product_router.register(r"aumchart", viewsets.AUMProductChartView, basename="product-aumchart")
141
136
  product_router.register(r"claim", viewsets.ClaimProductModelViewSet, basename="product-claim")
137
+ product_router.register(r"feesaggregated", viewsets.FeesAggregatedProductPandasView, basename="product-feesaggregated")
138
+ product_router.register(r"fees", viewsets.FeesProductModelViewSet, basename="product-fees")
142
139
 
143
- # Subrouter for Trade Proposal
144
- trade_proposal_router = WBCoreRouter()
145
- trade_proposal_router.register(r"trade", viewsets.TradeTradeProposalModelViewSet, basename="tradeproposal-trade")
140
+ # Subrouter for Order Proposal
141
+ order_proposal_router = WBCoreRouter()
142
+ order_proposal_router.register(r"order", viewsets.OrderOrderProposalModelViewSet, basename="orderproposal-order")
146
143
 
147
144
  trade_router = WBCoreRouter()
148
145
  trade_router.register(r"claim", viewsets.ClaimTradeModelViewSet, basename="trade-claim")
@@ -253,7 +250,7 @@ urlpatterns = [
253
250
  path("product/<int:product_id>/", include(product_router.urls)),
254
251
  path("trade/<int:trade_id>/", include(trade_router.urls)),
255
252
  path("portfolio/<int:portfolio_id>/", include(portfolio_router.urls)),
256
- path("tradeproposal/<int:trade_proposal_id>/", include(trade_proposal_router.urls)),
253
+ path("orderproposal/<int:order_proposal_id>/", include(order_proposal_router.urls)),
257
254
  path("instrument/<int:instrument_id>/", include(instrument_router.urls)),
258
255
  path("account/<int:account_id>/", include(account_router.urls)),
259
256
  path("entry/<int:entry_id>/", include(entry_router.urls)),
@@ -2,12 +2,16 @@ from .assets import (
2
2
  AssetPositionInstrumentModelViewSet,
3
3
  AssetPositionModelViewSet,
4
4
  AssetPositionPortfolioModelViewSet,
5
- AssetPositionUnderlyingInstrumentChartViewSet,
6
5
  CashPositionPortfolioPandasAPIView,
7
6
  CompositionModelPortfolioPandasView,
8
- ContributorPortfolioChartView,
9
7
  )
10
- from .charts import *
8
+ from .charts import (
9
+ DistributionChartViewSet,
10
+ DistributionTableViewSet,
11
+ AssetPositionUnderlyingInstrumentChartViewSet,
12
+ ContributorPortfolioChartView
13
+
14
+ )
11
15
  from .custodians import CustodianModelViewSet, CustodianRepresentationViewSet
12
16
  from .portfolio_relationship import InstrumentPreferedClassificationThroughProductModelViewSet
13
17
  from .portfolios import (
@@ -23,8 +27,9 @@ from .positions import (
23
27
  )
24
28
  from .registers import RegisterModelViewSet, RegisterRepresentationViewSet
25
29
  from .roles import PortfolioRoleInstrumentModelViewSet, PortfolioRoleModelViewSet
26
- from .signals import *
30
+ from .rebalancing import RebalancingModelRepresentationViewSet, RebalancerRepresentationViewSet, RebalancerModelViewSet
27
31
  from .transactions import *
32
+ from .orders import *
28
33
  from .adjustments import AdjustmentEquityModelViewSet, AdjustmentModelViewSet
29
34
  from .product_groups import ProductGroupModelViewSet, ProductGroupRepresentationViewSet
30
35
  from .product_performance import (