wbportfolio 1.44.5__py2.py3-none-any.whl → 1.45.0__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 (315) hide show
  1. wbportfolio/admin/__init__.py +1 -1
  2. wbportfolio/admin/asset.py +2 -1
  3. wbportfolio/admin/custodians.py +1 -0
  4. wbportfolio/admin/indexes.py +15 -0
  5. wbportfolio/admin/portfolio.py +12 -7
  6. wbportfolio/admin/portfolio_relationships.py +1 -0
  7. wbportfolio/admin/product_groups.py +2 -0
  8. wbportfolio/admin/products.py +2 -1
  9. wbportfolio/admin/reconciliations.py +1 -0
  10. wbportfolio/admin/registers.py +1 -0
  11. wbportfolio/admin/roles.py +1 -0
  12. wbportfolio/admin/transactions/__init__.py +1 -0
  13. wbportfolio/admin/transactions/claim.py +1 -0
  14. wbportfolio/admin/transactions/dividends.py +1 -0
  15. wbportfolio/admin/transactions/fees.py +1 -0
  16. wbportfolio/admin/transactions/rebalancing.py +26 -0
  17. wbportfolio/admin/transactions/trades.py +4 -3
  18. wbportfolio/admin/transactions/transactions.py +1 -0
  19. wbportfolio/analysis/claims.py +2 -1
  20. wbportfolio/contrib/company_portfolio/models.py +3 -6
  21. wbportfolio/contrib/company_portfolio/tests/conftest.py +0 -12
  22. wbportfolio/contrib/company_portfolio/tests/test_models.py +1 -0
  23. wbportfolio/defaults/fees/default.py +1 -0
  24. wbportfolio/factories/__init__.py +1 -7
  25. wbportfolio/factories/adjustments.py +1 -0
  26. wbportfolio/factories/assets.py +13 -7
  27. wbportfolio/factories/claim.py +1 -0
  28. wbportfolio/factories/custodians.py +1 -0
  29. wbportfolio/factories/dividends.py +1 -0
  30. wbportfolio/factories/fees.py +1 -0
  31. wbportfolio/factories/indexes.py +1 -0
  32. wbportfolio/factories/portfolio_cash_flow.py +1 -0
  33. wbportfolio/factories/portfolio_cash_targets.py +1 -0
  34. wbportfolio/factories/portfolio_swing_pricings.py +1 -0
  35. wbportfolio/factories/portfolios.py +3 -0
  36. wbportfolio/factories/product_groups.py +1 -0
  37. wbportfolio/factories/products.py +1 -0
  38. wbportfolio/factories/rebalancing.py +23 -0
  39. wbportfolio/factories/reconciliations.py +1 -0
  40. wbportfolio/factories/roles.py +1 -0
  41. wbportfolio/factories/trades.py +1 -0
  42. wbportfolio/factories/transactions.py +1 -0
  43. wbportfolio/fdm/tasks.py +1 -0
  44. wbportfolio/filters/__init__.py +1 -1
  45. wbportfolio/filters/assets.py +8 -9
  46. wbportfolio/filters/assets_and_net_new_money_progression.py +1 -0
  47. wbportfolio/filters/custodians.py +1 -0
  48. wbportfolio/filters/esg.py +1 -0
  49. wbportfolio/filters/performances.py +7 -6
  50. wbportfolio/filters/portfolios.py +21 -1
  51. wbportfolio/filters/positions.py +1 -0
  52. wbportfolio/filters/products.py +1 -0
  53. wbportfolio/filters/roles.py +1 -0
  54. wbportfolio/filters/signals.py +1 -0
  55. wbportfolio/filters/transactions/claim.py +1 -0
  56. wbportfolio/filters/transactions/fees.py +1 -0
  57. wbportfolio/filters/transactions/trades.py +2 -1
  58. wbportfolio/filters/transactions/transactions.py +1 -0
  59. wbportfolio/import_export/backends/ubs/mixin.py +1 -0
  60. wbportfolio/import_export/backends/wbfdm/adjustment.py +1 -0
  61. wbportfolio/import_export/handlers/asset_position.py +11 -13
  62. wbportfolio/import_export/handlers/fees.py +1 -0
  63. wbportfolio/import_export/handlers/portfolio_cash_flow.py +1 -0
  64. wbportfolio/import_export/handlers/trade.py +1 -0
  65. wbportfolio/import_export/parsers/jpmorgan/customer_trade.py +1 -0
  66. wbportfolio/import_export/parsers/jpmorgan/fees.py +1 -0
  67. wbportfolio/import_export/parsers/jpmorgan/strategy.py +5 -4
  68. wbportfolio/import_export/parsers/jpmorgan/valuation.py +1 -0
  69. wbportfolio/import_export/parsers/leonteq/customer_trade.py +1 -0
  70. wbportfolio/import_export/parsers/leonteq/equity.py +13 -12
  71. wbportfolio/import_export/parsers/leonteq/fees.py +1 -0
  72. wbportfolio/import_export/parsers/leonteq/trade.py +1 -0
  73. wbportfolio/import_export/parsers/leonteq/valuation.py +1 -0
  74. wbportfolio/import_export/parsers/natixis/customer_trade.py +1 -0
  75. wbportfolio/import_export/parsers/natixis/d1_customer_trade.py +1 -0
  76. wbportfolio/import_export/parsers/natixis/d1_equity.py +3 -2
  77. wbportfolio/import_export/parsers/natixis/d1_fees.py +1 -0
  78. wbportfolio/import_export/parsers/natixis/d1_trade.py +1 -0
  79. wbportfolio/import_export/parsers/natixis/d1_valuation.py +1 -0
  80. wbportfolio/import_export/parsers/natixis/equity.py +5 -5
  81. wbportfolio/import_export/parsers/natixis/trade.py +1 -0
  82. wbportfolio/import_export/parsers/natixis/utils.py +8 -7
  83. wbportfolio/import_export/parsers/sg_lux/custodian_positions.py +1 -0
  84. wbportfolio/import_export/parsers/sg_lux/customer_trade.py +1 -0
  85. wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py +2 -1
  86. wbportfolio/import_export/parsers/sg_lux/customer_trade_slk.py +2 -1
  87. wbportfolio/import_export/parsers/sg_lux/customer_trade_without_pw.py +1 -0
  88. wbportfolio/import_export/parsers/sg_lux/equity.py +7 -8
  89. wbportfolio/import_export/parsers/sg_lux/portfolio_cash_flow.py +1 -0
  90. wbportfolio/import_export/parsers/sg_lux/portfolio_future_cash_flow.py +1 -0
  91. wbportfolio/import_export/parsers/sg_lux/registers.py +2 -1
  92. wbportfolio/import_export/parsers/societe_generale/customer_trade.py +1 -0
  93. wbportfolio/import_export/parsers/societe_generale/strategy.py +8 -9
  94. wbportfolio/import_export/parsers/societe_generale/valuation.py +1 -0
  95. wbportfolio/import_export/parsers/tellco/equity.py +5 -4
  96. wbportfolio/import_export/parsers/ubs/api/asset_position.py +15 -14
  97. wbportfolio/import_export/parsers/ubs/api/fees.py +1 -0
  98. wbportfolio/import_export/parsers/ubs/customer_trade.py +1 -0
  99. wbportfolio/import_export/parsers/ubs/equity.py +3 -2
  100. wbportfolio/import_export/parsers/ubs/historical_customer_trade.py +1 -0
  101. wbportfolio/import_export/parsers/ubs/valuation.py +1 -0
  102. wbportfolio/import_export/parsers/vontobel/asset_position.py +19 -19
  103. wbportfolio/import_export/parsers/vontobel/customer_trade.py +1 -0
  104. wbportfolio/import_export/parsers/vontobel/historical_customer_trade.py +1 -0
  105. wbportfolio/import_export/parsers/vontobel/management_fees.py +1 -0
  106. wbportfolio/import_export/parsers/vontobel/performance_fees.py +1 -0
  107. wbportfolio/import_export/parsers/vontobel/trade.py +1 -0
  108. wbportfolio/import_export/parsers/vontobel/valuation_api.py +20 -0
  109. wbportfolio/import_export/resources/assets.py +4 -3
  110. wbportfolio/import_export/resources/trades.py +1 -0
  111. wbportfolio/metric/backends/base.py +1 -0
  112. wbportfolio/metric/backends/portfolio_base.py +1 -0
  113. wbportfolio/metric/backends/portfolio_esg.py +1 -0
  114. wbportfolio/metric/tests/test_portfolio_base.py +1 -0
  115. wbportfolio/migrations/0052_remove_cash_instrument_ptr_and_more.py +1 -131
  116. wbportfolio/migrations/0067_assetposition_unique_asset_position.py +1 -1
  117. wbportfolio/migrations/0070_remove_assetposition_unique_asset_position_and_more.py +1 -1
  118. wbportfolio/migrations/0073_remove_product_price_computation_and_more.py +407 -0
  119. wbportfolio/models/__init__.py +0 -5
  120. wbportfolio/models/adjustments.py +8 -2
  121. wbportfolio/models/asset.py +117 -98
  122. wbportfolio/models/graphs/portfolio.py +144 -0
  123. wbportfolio/models/graphs/utils.py +83 -0
  124. wbportfolio/models/indexes.py +2 -13
  125. wbportfolio/models/mixins/instruments.py +28 -8
  126. wbportfolio/models/portfolio.py +538 -332
  127. wbportfolio/models/portfolio_cash_flow.py +1 -0
  128. wbportfolio/models/portfolio_relationship.py +6 -2
  129. wbportfolio/models/product_groups.py +3 -2
  130. wbportfolio/models/products.py +3 -17
  131. wbportfolio/models/reconciliations/account_reconciliation_lines.py +1 -0
  132. wbportfolio/models/reconciliations/account_reconciliations.py +1 -0
  133. wbportfolio/models/registers.py +1 -0
  134. wbportfolio/models/transactions/__init__.py +1 -0
  135. wbportfolio/models/transactions/claim.py +8 -8
  136. wbportfolio/models/transactions/dividends.py +1 -0
  137. wbportfolio/models/transactions/fees.py +1 -0
  138. wbportfolio/models/transactions/rebalancing.py +153 -0
  139. wbportfolio/models/transactions/trade_proposals.py +153 -155
  140. wbportfolio/models/transactions/trades.py +48 -40
  141. wbportfolio/models/transactions/transactions.py +6 -12
  142. wbportfolio/models/utils.py +1 -0
  143. wbportfolio/pms/analytics/__init__.py +0 -0
  144. wbportfolio/pms/analytics/portfolio.py +28 -0
  145. wbportfolio/pms/trading/handler.py +13 -16
  146. wbportfolio/pms/typing.py +13 -29
  147. wbportfolio/rebalancing/__init__.py +0 -0
  148. wbportfolio/rebalancing/base.py +16 -0
  149. wbportfolio/rebalancing/decorators.py +17 -0
  150. wbportfolio/rebalancing/models/__init__.py +3 -0
  151. wbportfolio/rebalancing/models/composite.py +31 -0
  152. wbportfolio/rebalancing/models/equally_weighted.py +21 -0
  153. wbportfolio/rebalancing/models/model_portfolio.py +35 -0
  154. wbportfolio/reports/monthly_position_report.py +1 -1
  155. wbportfolio/risk_management/backends/accounts.py +7 -6
  156. wbportfolio/risk_management/backends/controversy_portfolio.py +1 -0
  157. wbportfolio/risk_management/backends/exposure_portfolio.py +1 -0
  158. wbportfolio/risk_management/backends/instrument_list_portfolio.py +1 -0
  159. wbportfolio/risk_management/backends/liquidity_risk.py +1 -0
  160. wbportfolio/risk_management/backends/liquidity_stress_instrument.py +1 -0
  161. wbportfolio/risk_management/backends/mixins.py +1 -0
  162. wbportfolio/risk_management/backends/product_integrity.py +6 -1
  163. wbportfolio/risk_management/backends/stop_loss_instrument.py +1 -0
  164. wbportfolio/risk_management/backends/stop_loss_portfolio.py +1 -0
  165. wbportfolio/risk_management/backends/ucits_portfolio.py +1 -0
  166. wbportfolio/risk_management/tests/test_accounts.py +1 -0
  167. wbportfolio/risk_management/tests/test_controversy_portfolio.py +1 -0
  168. wbportfolio/risk_management/tests/test_exposure_portfolio.py +1 -0
  169. wbportfolio/risk_management/tests/test_instrument_list_portfolio.py +1 -0
  170. wbportfolio/risk_management/tests/test_liquidity_risk.py +1 -0
  171. wbportfolio/risk_management/tests/test_product_integrity.py +1 -0
  172. wbportfolio/risk_management/tests/test_stop_loss_instrument.py +1 -0
  173. wbportfolio/risk_management/tests/test_stop_loss_portfolio.py +1 -0
  174. wbportfolio/risk_management/tests/test_ucits_portfolio.py +1 -0
  175. wbportfolio/serializers/__init__.py +5 -5
  176. wbportfolio/serializers/adjustments.py +1 -0
  177. wbportfolio/serializers/assets.py +18 -19
  178. wbportfolio/serializers/custodians.py +1 -0
  179. wbportfolio/serializers/portfolio_cash_flow.py +1 -0
  180. wbportfolio/serializers/portfolio_cash_targets.py +1 -0
  181. wbportfolio/serializers/portfolio_relationship.py +1 -0
  182. wbportfolio/serializers/portfolio_swing_pricing.py +1 -0
  183. wbportfolio/serializers/portfolios.py +61 -40
  184. wbportfolio/serializers/positions.py +1 -0
  185. wbportfolio/serializers/product_group.py +1 -0
  186. wbportfolio/serializers/products.py +4 -7
  187. wbportfolio/serializers/rebalancing.py +57 -0
  188. wbportfolio/serializers/reconciliations.py +2 -1
  189. wbportfolio/serializers/registers.py +1 -0
  190. wbportfolio/serializers/roles.py +1 -0
  191. wbportfolio/serializers/signals.py +10 -15
  192. wbportfolio/serializers/transactions/__init__.py +1 -1
  193. wbportfolio/serializers/transactions/claim.py +1 -0
  194. wbportfolio/serializers/transactions/fees.py +1 -0
  195. wbportfolio/serializers/transactions/trade_proposals.py +85 -0
  196. wbportfolio/serializers/transactions/trades.py +9 -51
  197. wbportfolio/serializers/transactions/transactions.py +4 -3
  198. wbportfolio/tasks.py +1 -78
  199. wbportfolio/tests/conftest.py +6 -13
  200. wbportfolio/tests/models/test_account_reconciliation.py +2 -0
  201. wbportfolio/tests/models/test_assets.py +27 -19
  202. wbportfolio/tests/models/test_customer_trades.py +1 -0
  203. wbportfolio/tests/models/test_imports.py +5 -1
  204. wbportfolio/tests/models/test_merge.py +5 -4
  205. wbportfolio/tests/models/test_portfolio_cash_flow.py +8 -6
  206. wbportfolio/tests/models/test_portfolios.py +594 -154
  207. wbportfolio/tests/models/test_product_groups.py +1 -0
  208. wbportfolio/tests/models/test_products.py +6 -3
  209. wbportfolio/tests/models/test_roles.py +1 -0
  210. wbportfolio/tests/models/test_splits.py +1 -0
  211. wbportfolio/tests/models/transactions/test_claim.py +1 -0
  212. wbportfolio/tests/models/transactions/test_fees.py +1 -0
  213. wbportfolio/tests/models/transactions/test_rebalancing.py +81 -0
  214. wbportfolio/tests/models/transactions/test_trades.py +1 -0
  215. wbportfolio/tests/models/utils.py +1 -0
  216. wbportfolio/tests/pms/__init__.py +0 -0
  217. wbportfolio/tests/pms/test_analytics.py +35 -0
  218. wbportfolio/tests/rebalancing/__init__.py +0 -0
  219. wbportfolio/tests/rebalancing/test_models.py +127 -0
  220. wbportfolio/tests/serializers/test_claims.py +1 -0
  221. wbportfolio/tests/signals.py +1 -7
  222. wbportfolio/tests/tests.py +2 -0
  223. wbportfolio/tests/viewsets/test_assets.py +1 -0
  224. wbportfolio/tests/viewsets/test_performances.py +1 -0
  225. wbportfolio/tests/viewsets/test_products.py +1 -0
  226. wbportfolio/tests/viewsets/transactions/test_claims.py +1 -0
  227. wbportfolio/urls.py +26 -12
  228. wbportfolio/viewsets/__init__.py +2 -5
  229. wbportfolio/viewsets/adjustments.py +1 -0
  230. wbportfolio/viewsets/assets.py +62 -51
  231. wbportfolio/viewsets/assets_and_net_new_money_progression.py +1 -0
  232. wbportfolio/viewsets/charts/assets.py +3 -1
  233. wbportfolio/viewsets/configs/buttons/__init__.py +1 -1
  234. wbportfolio/viewsets/configs/buttons/assets.py +1 -0
  235. wbportfolio/viewsets/configs/buttons/custodians.py +1 -0
  236. wbportfolio/viewsets/configs/buttons/mixins.py +1 -20
  237. wbportfolio/viewsets/configs/buttons/portfolios.py +90 -76
  238. wbportfolio/viewsets/configs/buttons/signals.py +1 -0
  239. wbportfolio/viewsets/configs/buttons/trades.py +1 -0
  240. wbportfolio/viewsets/configs/display/__init__.py +2 -1
  241. wbportfolio/viewsets/configs/display/adjustments.py +1 -0
  242. wbportfolio/viewsets/configs/display/assets.py +7 -6
  243. wbportfolio/viewsets/configs/display/claim.py +1 -0
  244. wbportfolio/viewsets/configs/display/portfolios.py +127 -79
  245. wbportfolio/viewsets/configs/display/product_performance.py +1 -0
  246. wbportfolio/viewsets/configs/display/rebalancing.py +27 -0
  247. wbportfolio/viewsets/configs/display/trade_proposals.py +7 -4
  248. wbportfolio/viewsets/configs/display/trades.py +75 -42
  249. wbportfolio/viewsets/configs/endpoints/__init__.py +3 -1
  250. wbportfolio/viewsets/configs/endpoints/claim.py +1 -0
  251. wbportfolio/viewsets/configs/endpoints/portfolios.py +23 -7
  252. wbportfolio/viewsets/configs/endpoints/rebalancing.py +6 -0
  253. wbportfolio/viewsets/configs/endpoints/reconciliations.py +1 -0
  254. wbportfolio/viewsets/configs/endpoints/trade_proposals.py +1 -0
  255. wbportfolio/viewsets/configs/endpoints/trades.py +1 -0
  256. wbportfolio/viewsets/configs/menu/adjustments.py +1 -0
  257. wbportfolio/viewsets/configs/menu/assets.py +1 -0
  258. wbportfolio/viewsets/configs/menu/fees.py +1 -0
  259. wbportfolio/viewsets/configs/menu/portfolio_cash_flow.py +1 -0
  260. wbportfolio/viewsets/configs/menu/portfolios.py +4 -2
  261. wbportfolio/viewsets/configs/menu/positions.py +1 -0
  262. wbportfolio/viewsets/configs/menu/roles.py +1 -0
  263. wbportfolio/viewsets/configs/menu/transactions.py +1 -0
  264. wbportfolio/viewsets/configs/previews/portfolios.py +1 -6
  265. wbportfolio/viewsets/configs/titles/__init__.py +1 -1
  266. wbportfolio/viewsets/configs/titles/assets.py +1 -0
  267. wbportfolio/viewsets/configs/titles/fees.py +1 -0
  268. wbportfolio/viewsets/configs/titles/instrument_prices.py +1 -0
  269. wbportfolio/viewsets/configs/titles/portfolios.py +13 -11
  270. wbportfolio/viewsets/configs/titles/roles.py +1 -0
  271. wbportfolio/viewsets/configs/titles/trades.py +1 -0
  272. wbportfolio/viewsets/configs/titles/transactions.py +1 -0
  273. wbportfolio/viewsets/custodians.py +1 -0
  274. wbportfolio/viewsets/esg.py +1 -0
  275. wbportfolio/viewsets/mixins.py +1 -0
  276. wbportfolio/viewsets/portfolio_cash_flow.py +1 -0
  277. wbportfolio/viewsets/portfolio_cash_targets.py +1 -0
  278. wbportfolio/viewsets/portfolio_relationship.py +1 -0
  279. wbportfolio/viewsets/portfolio_swing_pricing.py +1 -0
  280. wbportfolio/viewsets/portfolios.py +228 -61
  281. wbportfolio/viewsets/positions.py +3 -2
  282. wbportfolio/viewsets/product_groups.py +1 -0
  283. wbportfolio/viewsets/product_performance.py +1 -0
  284. wbportfolio/viewsets/products.py +1 -0
  285. wbportfolio/viewsets/reconciliations.py +1 -0
  286. wbportfolio/viewsets/registers.py +1 -0
  287. wbportfolio/viewsets/roles.py +1 -0
  288. wbportfolio/viewsets/signals.py +1 -0
  289. wbportfolio/viewsets/transactions/__init__.py +1 -0
  290. wbportfolio/viewsets/transactions/claim.py +2 -1
  291. wbportfolio/viewsets/transactions/fees.py +1 -0
  292. wbportfolio/viewsets/transactions/mixins.py +1 -0
  293. wbportfolio/viewsets/transactions/rebalancing.py +31 -0
  294. wbportfolio/viewsets/transactions/trade_proposals.py +25 -5
  295. wbportfolio/viewsets/transactions/trades.py +16 -9
  296. wbportfolio/viewsets/transactions/transactions.py +1 -0
  297. {wbportfolio-1.44.5.dist-info → wbportfolio-1.45.0.dist-info}/METADATA +4 -1
  298. {wbportfolio-1.44.5.dist-info → wbportfolio-1.45.0.dist-info}/RECORD +301 -288
  299. wbportfolio/admin/synchronization/__init__.py +0 -2
  300. wbportfolio/admin/synchronization/admin.py +0 -114
  301. wbportfolio/admin/synchronization/portfolio_synchronization.py +0 -18
  302. wbportfolio/admin/synchronization/price_computation.py +0 -21
  303. wbportfolio/defaults/portfolio/default_rebalancing.py +0 -45
  304. wbportfolio/factories/pytest_utils.py +0 -121
  305. wbportfolio/factories/synchronization.py +0 -40
  306. wbportfolio/models/synchronization/__init__.py +0 -3
  307. wbportfolio/models/synchronization/portfolio_synchronization.py +0 -292
  308. wbportfolio/models/synchronization/price_computation.py +0 -200
  309. wbportfolio/models/synchronization/synchronization.py +0 -188
  310. wbportfolio/serializers/synchronization.py +0 -18
  311. wbportfolio/tests/models/test_synchronization.py +0 -617
  312. wbportfolio/viewsets/synchronization.py +0 -25
  313. /wbportfolio/{defaults/portfolio → models/graphs}/__init__.py +0 -0
  314. {wbportfolio-1.44.5.dist-info → wbportfolio-1.45.0.dist-info}/WHEEL +0 -0
  315. {wbportfolio-1.44.5.dist-info → wbportfolio-1.45.0.dist-info}/licenses/LICENSE +0 -0
@@ -2,14 +2,15 @@ from typing import TYPE_CHECKING, Any
2
2
 
3
3
  from rest_framework.reverse import reverse
4
4
  from wbcore import serializers
5
+ from wbcore.contrib.authentication.models import User
5
6
  from wbcore.contrib.authentication.serializers import UserRepresentationSerializer
6
7
  from wbcore.utils.urls import new_mode
7
8
  from wbcrm.models import Account
8
9
  from wbcrm.serializers.accounts import AccountRepresentationSerializer
10
+
9
11
  from wbportfolio.models import AccountReconciliation
10
12
  from wbportfolio.models.reconciliations import AccountReconciliationLine
11
13
  from wbportfolio.serializers.products import ProductRepresentationSerializer
12
- from wbcore.contrib.authentication.models import User
13
14
 
14
15
  if TYPE_CHECKING:
15
16
  from rest_framework.request import Request
@@ -1,6 +1,7 @@
1
1
  from rest_framework.reverse import reverse
2
2
  from wbcore import serializers
3
3
  from wbcore.contrib.geography.serializers import GeographyRepresentationSerializer
4
+
4
5
  from wbportfolio.models import Register
5
6
 
6
7
 
@@ -2,6 +2,7 @@ from rest_framework import serializers
2
2
  from wbcore import serializers as wb_serializers
3
3
  from wbcore.contrib.directory.serializers import PersonRepresentationSerializer
4
4
  from wbfdm.serializers import InvestableInstrumentRepresentationSerializer
5
+
5
6
  from wbportfolio.models import PortfolioRole
6
7
 
7
8
 
@@ -11,6 +11,7 @@ from wbcore.filters.defaults import current_quarter_date_start
11
11
  from wbcore.signals import add_instance_additional_resource
12
12
  from wbcrm.serializers.accounts import AccountModelSerializer
13
13
  from wbfdm.serializers import InstrumentModelSerializer
14
+
14
15
  from wbportfolio.models import PortfolioRole, Trade
15
16
  from wbportfolio.serializers.products import ProductModelSerializer
16
17
 
@@ -47,9 +48,9 @@ def add_instrument_serializer_resources(sender, serializer, instance, request, u
47
48
  .exclude(transaction_subtype__in=[Trade.Type.REDEMPTION, Trade.Type.SUBSCRIPTION])
48
49
  .exists()
49
50
  ):
50
- additional_resources[
51
- "instrument_trades"
52
- ] = f'{reverse("wbportfolio:instrument-trade-list", args=[instance.id], request=request)}?is_customer_trade=False'
51
+ additional_resources["instrument_trades"] = (
52
+ f'{reverse("wbportfolio:instrument-trade-list", args=[instance.id], request=request)}?is_customer_trade=False'
53
+ )
53
54
 
54
55
  if PortfolioRole.is_manager(user.profile):
55
56
  additional_resources["portfoliorole"] = reverse(
@@ -74,12 +75,6 @@ def asset_portfolio_resources(sender, serializer, instance, request, user, **kwa
74
75
  args=[portfolio.id],
75
76
  request=request,
76
77
  )
77
- if request.user.has_perm("wbportfolio.administrate_instrument") and (
78
- portfolio.portfolio_synchronization or hasattr(instance, "price_computation")
79
- ):
80
- additional_resources[
81
- "resynchronize"
82
- ] = f'{reverse("wbportfolio:portfolio-resynchronize", args=[portfolio.id], request=request)}?instrument={instance.id}'
83
78
  return additional_resources
84
79
 
85
80
 
@@ -90,12 +85,12 @@ def transactions_resources(sender, serializer, instance, request, user, **kwargs
90
85
  portfolio = instance.primary_portfolio
91
86
  if portfolio:
92
87
  if portfolio.transactions.exists() and user.profile.is_internal:
93
- additional_resources[
94
- "portfolio_subscriptionsredemptions"
95
- ] = f'{reverse("wbportfolio:portfolio-trade-list", args=[portfolio.id], request=request)}?is_customer_trade=True'
96
- additional_resources[
97
- "portfolio_trades"
98
- ] = f'{reverse("wbportfolio:portfolio-trade-list", args=[portfolio.id], request=request)}?is_customer_trade=False'
88
+ additional_resources["portfolio_subscriptionsredemptions"] = (
89
+ f'{reverse("wbportfolio:portfolio-trade-list", args=[portfolio.id], request=request)}?is_customer_trade=True'
90
+ )
91
+ additional_resources["portfolio_trades"] = (
92
+ f'{reverse("wbportfolio:portfolio-trade-list", args=[portfolio.id], request=request)}?is_customer_trade=False'
93
+ )
99
94
 
100
95
  additional_resources["portfolio_transactions"] = reverse(
101
96
  "wbportfolio:portfolio-transaction-list", args=[portfolio.id], request=request
@@ -1,3 +1,4 @@
1
+ from .trade_proposals import TradeProposalModelSerializer, ReadOnlyTradeProposalModelSerializer
1
2
  from .claim import (
2
3
  ClaimAccountSerializer,
3
4
  ClaimAPIModelSerializer,
@@ -12,7 +13,6 @@ from .expiry import ExpiryModelSerializer, ExpiryRepresentationSerializer
12
13
  from .fees import FeesModelSerializer, FeesRepresentationSerializer
13
14
  from .trades import (
14
15
  TradeModelSerializer,
15
- TradeProposalModelSerializer,
16
16
  TradeProposalRepresentationSerializer,
17
17
  TradeRepresentationSerializer,
18
18
  TradeTradeProposalModelSerializer,
@@ -6,6 +6,7 @@ from wbcore.contrib.directory.models import Person
6
6
  from wbcore.contrib.directory.serializers import EntryRepresentationSerializer
7
7
  from wbcrm.models import Account
8
8
  from wbcrm.serializers.accounts import TerminalAccountRepresentationSerializer
9
+
9
10
  from wbportfolio.models import Product, Trade
10
11
  from wbportfolio.models.transactions.claim import Claim
11
12
  from wbportfolio.serializers.products import (
@@ -1,4 +1,5 @@
1
1
  from wbcore import serializers as wb_serializers
2
+
2
3
  from wbportfolio.models import Fees
3
4
 
4
5
  from .transactions import (
@@ -0,0 +1,85 @@
1
+ from contextlib import suppress
2
+
3
+ from django.core.exceptions import ValidationError
4
+ from rest_framework.reverse import reverse
5
+ from wbcore import serializers as wb_serializers
6
+ from wbcore.serializers import DefaultFromView
7
+
8
+ from wbportfolio.models import Portfolio, RebalancingModel, TradeProposal
9
+
10
+ from .. import PortfolioRepresentationSerializer, RebalancingModelRepresentationSerializer
11
+
12
+
13
+ class TradeProposalModelSerializer(wb_serializers.ModelSerializer):
14
+ rebalancing_model = wb_serializers.PrimaryKeyRelatedField(
15
+ queryset=RebalancingModel.objects.all(),
16
+ default=DefaultFromView("portfolio.automatic_rebalancer.rebalancing_model"),
17
+ )
18
+ _rebalancing_model = RebalancingModelRepresentationSerializer(source="rebalancing_model")
19
+ target_portfolio = wb_serializers.PrimaryKeyRelatedField(
20
+ queryset=Portfolio.objects.all(), write_only=True, required=False, default=DefaultFromView("portfolio")
21
+ )
22
+ _target_portfolio = PortfolioRepresentationSerializer(source="target_portfolio")
23
+
24
+ trade_date = wb_serializers.DateField(
25
+ read_only=lambda view: not view.new_mode, default=DefaultFromView("default_trade_date")
26
+ )
27
+
28
+ def create(self, validated_data):
29
+ target_portfolio = validated_data.pop("target_portfolio", None)
30
+ rebalancing_model = validated_data.get("rebalancing_model", None)
31
+ if request := self.context.get("request"):
32
+ validated_data["creator"] = request.user.profile
33
+ obj = super().create(validated_data)
34
+
35
+ target_portfolio_dto = None
36
+ if (
37
+ target_portfolio
38
+ and not rebalancing_model
39
+ and (
40
+ last_effective_date := target_portfolio.get_latest_asset_position_date(
41
+ obj.trade_date, with_estimated=True
42
+ )
43
+ )
44
+ ):
45
+ target_portfolio_dto = target_portfolio._build_dto(last_effective_date)
46
+ with suppress(
47
+ ValidationError
48
+ ): # we ignore validation error at this point as the trade are automatically create for convenience
49
+ obj.reset_trades(target_portfolio=target_portfolio_dto)
50
+ return obj
51
+
52
+ @wb_serializers.register_only_instance_resource()
53
+ def additional_resources(self, instance, request, user, **kwargs):
54
+ res = {}
55
+ if instance.status == TradeProposal.Status.APPROVED:
56
+ res["replay"] = reverse("wbportfolio:tradeproposal-replay", args=[instance.id], request=request)
57
+ if instance.status == TradeProposal.Status.DRAFT:
58
+ res["reset"] = reverse("wbportfolio:tradeproposal-reset", args=[instance.id], request=request)
59
+ res["trades"] = reverse(
60
+ "wbportfolio:tradeproposal-trade-list",
61
+ args=[instance.id],
62
+ request=request,
63
+ )
64
+ return res
65
+
66
+ class Meta:
67
+ model = TradeProposal
68
+ only_fsm_transition_on_instance = True
69
+ fields = (
70
+ "id",
71
+ "trade_date",
72
+ "comment",
73
+ "status",
74
+ "portfolio",
75
+ "_rebalancing_model",
76
+ "rebalancing_model",
77
+ "target_portfolio",
78
+ "_target_portfolio",
79
+ "_additional_resources",
80
+ )
81
+
82
+
83
+ class ReadOnlyTradeProposalModelSerializer(TradeProposalModelSerializer):
84
+ class Meta(TradeProposalModelSerializer.Meta):
85
+ read_only_fields = TradeProposalModelSerializer.Meta.fields
@@ -4,10 +4,10 @@ from decimal import Decimal
4
4
  from rest_framework import serializers
5
5
  from rest_framework.reverse import reverse
6
6
  from wbcore import serializers as wb_serializers
7
- from wbportfolio.models import Portfolio, PortfolioRole, Trade, TradeProposal
7
+
8
+ from wbportfolio.models import PortfolioRole, Trade, TradeProposal
8
9
  from wbportfolio.models.transactions.claim import Claim
9
10
  from wbportfolio.serializers.custodians import CustodianRepresentationSerializer
10
- from wbportfolio.serializers.portfolios import PortfolioRepresentationSerializer
11
11
  from wbportfolio.serializers.registers import RegisterRepresentationSerializer
12
12
 
13
13
  from .transactions import (
@@ -215,6 +215,8 @@ class TradeTradeProposalModelSerializer(TradeModelSerializer):
215
215
  status = wb_serializers.ChoiceField(default=Trade.Status.DRAFT, choices=Trade.Status.choices)
216
216
  target_weight = wb_serializers.DecimalField(max_digits=16, decimal_places=6, required=False, default=0)
217
217
  effective_weight = wb_serializers.DecimalField(read_only=True, max_digits=16, decimal_places=6, default=0)
218
+ effective_shares = wb_serializers.DecimalField(read_only=True, max_digits=16, decimal_places=6, default=0)
219
+ target_shares = wb_serializers.DecimalField(read_only=True, max_digits=16, decimal_places=6, default=0)
218
220
 
219
221
  def validate(self, data):
220
222
  if self.instance and "underlying_instrument" in data:
@@ -240,6 +242,9 @@ class TradeTradeProposalModelSerializer(TradeModelSerializer):
240
242
  "shares",
241
243
  # "underlying_instrument",
242
244
  # "_underlying_instrument",
245
+ "shares",
246
+ "effective_shares",
247
+ "target_shares",
243
248
  )
244
249
  fields = (
245
250
  "id",
@@ -248,64 +253,17 @@ class TradeTradeProposalModelSerializer(TradeModelSerializer):
248
253
  "_underlying_instrument",
249
254
  "transaction_subtype",
250
255
  "status",
251
- # "total_value",
252
- # "total_value_fx_portfolio",
253
- # "total_value_gross",
254
- # "total_value_gross_fx_portfolio",
255
256
  "comment",
256
- # "total_value_usd",
257
- # "total_value_gross_usd",
258
257
  "effective_weight",
259
258
  "target_weight",
260
259
  "weighting",
261
260
  "trade_proposal",
262
261
  "order",
262
+ "effective_shares",
263
+ "target_shares",
263
264
  )
264
265
 
265
266
 
266
267
  class ReadOnlyTradeTradeProposalModelSerializer(TradeTradeProposalModelSerializer):
267
268
  class Meta(TradeTradeProposalModelSerializer.Meta):
268
269
  read_only_fields = TradeTradeProposalModelSerializer.Meta.fields
269
-
270
-
271
- class TradeProposalModelSerializer(wb_serializers.ModelSerializer):
272
- _model_portfolio = PortfolioRepresentationSerializer(source="model_portfolio")
273
- model_portfolio = wb_serializers.PrimaryKeyRelatedField(
274
- queryset=Portfolio.objects.all(),
275
- default=wb_serializers.DefaultFromKwargs("portfolio_id"),
276
- read_only=lambda view: not view.new_mode,
277
- )
278
- trade_date = wb_serializers.DateField(read_only=lambda view: not view.new_mode)
279
-
280
- def create(self, validated_data):
281
- if request := self.context.get("request"):
282
- validated_data["creator"] = request.user.profile
283
- return super().create(validated_data)
284
-
285
- @wb_serializers.register_only_instance_resource()
286
- def additional_resources(self, instance, request, user, **kwargs):
287
- res = {}
288
- if instance.status == TradeProposal.Status.APPROVED:
289
- res["replay"] = reverse("wbportfolio:tradeproposal-replay", args=[instance.id], request=request)
290
- if instance.model_portfolio:
291
- res["reset"] = reverse("wbportfolio:tradeproposal-reset", args=[instance.id], request=request)
292
- res["trades"] = reverse(
293
- "wbportfolio:tradeproposal-trade-list",
294
- args=[instance.id],
295
- request=request,
296
- )
297
- return res
298
-
299
- class Meta:
300
- model = TradeProposal
301
- only_fsm_transition_on_instance = True
302
- fields = (
303
- "id",
304
- "trade_date",
305
- "comment",
306
- "status",
307
- "portfolio",
308
- "_model_portfolio",
309
- "model_portfolio",
310
- "_additional_resources",
311
- )
@@ -1,6 +1,7 @@
1
1
  from wbcore import serializers as wb_serializers
2
2
  from wbcore.contrib.currency.serializers import CurrencyRepresentationSerializer
3
- from wbfdm.serializers.instruments import InvestableInstrumentRepresentationSerializer
3
+ from wbfdm.serializers import InvestableUniverseRepresentationSerializer
4
+
4
5
  from wbportfolio.models import Transaction
5
6
  from wbportfolio.serializers.portfolios import PortfolioRepresentationSerializer
6
7
 
@@ -21,7 +22,7 @@ class TransactionModelSerializer(wb_serializers.ModelSerializer):
21
22
  transaction_underlying_type = wb_serializers.CharField(read_only=True)
22
23
  transaction_url_type = wb_serializers.SerializerMethodField()
23
24
  _portfolio = PortfolioRepresentationSerializer(source="portfolio")
24
- _underlying_instrument = InvestableInstrumentRepresentationSerializer(source="underlying_instrument")
25
+ _underlying_instrument = InvestableUniverseRepresentationSerializer(source="underlying_instrument")
25
26
  _currency = CurrencyRepresentationSerializer(source="currency")
26
27
 
27
28
  total_value = wb_serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
@@ -54,7 +55,7 @@ class TransactionModelSerializer(wb_serializers.ModelSerializer):
54
55
  ),
55
56
  "total_value_gross_fx_portfolio": wb_serializers.decorator(
56
57
  position="left", value="{{_portfolio.currency_symbol}}"
57
- )
58
+ ),
58
59
  # "total_value_fx_portfolio": wb_serializers.decorator(decorator_type="text", position="left", value="{{_portfolio.currency_symbol}}}"),
59
60
  # "total_value_gross_fx_portfolio": wb_serializers.decorator(decorator_type="text", position="left", value="{{_portfolio.currency_symbol}}"),
60
61
  }
wbportfolio/tasks.py CHANGED
@@ -1,23 +1,14 @@
1
1
  from contextlib import suppress
2
2
  from datetime import date, timedelta
3
3
 
4
- from celery import chain, chord
5
4
  from django.db.models import ProtectedError, Q
6
- from pandas.tseries.offsets import BDay
7
- from tqdm import tqdm
8
- from wbfdm.models import InstrumentPrice
5
+
9
6
  from wbportfolio.models import Portfolio, Trade
10
- from wbportfolio.models.portfolio import propagate_or_update_portfolio_assets_as_task
11
7
  from wbportfolio.models.products import Product, update_outstanding_shares_as_task
12
8
 
13
9
  from .fdm.tasks import * # noqa
14
10
 
15
11
 
16
- @shared_task()
17
- def dummy_task():
18
- return True
19
-
20
-
21
12
  @shared_task(queue="portfolio")
22
13
  def periodically_update_outstanding_shares_for_active_products():
23
14
  qs = Product.active_objects.all()
@@ -42,29 +33,6 @@ def periodically_clean_marked_for_deletion_trades(max_allowed_iterations: int =
42
33
  i += 1
43
34
 
44
35
 
45
- # Daily synchronization tasks.
46
- # This tasks needs to be ran at maximum once a day in order to guarantee data consitency in
47
- # case of change in change (e.g. reimport).
48
- @shared_task(queue="portfolio")
49
- def daily_instrument_price_statistics_synchronization(today: date = None, day_periods: int = 7):
50
- if not today:
51
- today = date.today()
52
-
53
- # We query for the last 7 days unsynch instrument prices.
54
- prices = (
55
- InstrumentPrice.objects.filter(
56
- date__gte=today - timedelta(days=day_periods), instrument__related_instruments__isnull=False
57
- )
58
- .filter(Q(sharpe_ratio__isnull=True) | Q(correlation__isnull=True) | Q(beta__isnull=True))
59
- .distinct()
60
- )
61
- objs = []
62
- for p in prices.iterator():
63
- p.compute_and_update_statistics()
64
- objs.append(p)
65
- InstrumentPrice.objects.bulk_update(objs, fields=["sharpe_ratio", "correlation", "beta"])
66
-
67
-
68
36
  # A Task to run every day to update automatically the preferred classification
69
37
  # per instrument of each wbportfolio containing assets.
70
38
  @shared_task(queue="portfolio")
@@ -78,48 +46,3 @@ def update_preferred_classification_per_instrument_and_portfolio_as_task():
78
46
  # - propagate (or update) t-2 asset positions into t-1
79
47
  # - Synchronize wbportfolio at t-1
80
48
  # - Compute Instrument Price estimate at t-1
81
-
82
-
83
- @shared_task(queue="portfolio")
84
- def general_portfolio_synchronization_and_update(task_date=None):
85
- if not task_date:
86
- task_date = date.today()
87
- t_1 = (task_date - BDay(1)).date()
88
- t_2 = (t_1 - BDay(1)).date()
89
-
90
- # We propagate or update assets position from t-2 to t-1 (Needs stainly t-1 prices)
91
- subroutines_propagate_or_update_portfolio_assets = list()
92
- for portfolio in Portfolio.tracked_objects.all():
93
- if (
94
- portfolio.assets.filter(date=t_2).exists()
95
- and not portfolio.assets.filter(date=t_1, is_estimated=False).exists()
96
- ):
97
- subroutines_propagate_or_update_portfolio_assets.append(
98
- propagate_or_update_portfolio_assets_as_task.si(portfolio.id, t_2, t_1)
99
- )
100
-
101
- # # Synchronize wbportfolio at t-1 (needs propagated data)
102
- # subroutines_synchronize_computed_positions = list()
103
- # for method in PortfolioSynchronization.objects.all():
104
- # for portfolio in method.portfolios.all():
105
- # subroutines_synchronize_computed_positions.append(
106
- # synchronize_portfolio_as_task.si(portfolio.id, t_1, synchronization_method_id=method.id)
107
- # )
108
- #
109
- # # Compute price estimate at t-1 (needs synchronized wbportfolio)
110
- # subroutines_compute_price = list()
111
- # for method in PriceComputation.objects.all():
112
- # for instrument in method.instruments.all():
113
- # subroutines_compute_price.append(
114
- # compute_price_as_task.si(instrument.id, t_1, price_computation_method_id=method.id)
115
- # )
116
-
117
- subroutines_propagate_or_update_portfolio_assets_group = chord(
118
- subroutines_propagate_or_update_portfolio_assets, dummy_task.si()
119
- )
120
-
121
- chain(
122
- subroutines_propagate_or_update_portfolio_assets_group,
123
- # subroutines_synchronize_computed_positions_group,
124
- # subroutines_compute_price_group
125
- ).apply_async()
@@ -21,6 +21,7 @@ from wbcore.contrib.directory.factories.entries import (
21
21
  )
22
22
  from wbcore.contrib.geography.factories import CityFactory, CountryFactory, StateFactory
23
23
  from wbcore.contrib.io.factories import (
24
+ CrontabScheduleFactory,
24
25
  DataBackendFactory,
25
26
  ImportSourceFactory,
26
27
  ParserHandlerFactory,
@@ -67,13 +68,8 @@ from wbportfolio.factories import (
67
68
  TradeFactory,
68
69
  TradeProposalFactory,
69
70
  WhiteLabelProductFactory,
70
- )
71
- from wbportfolio.factories.synchronization import (
72
- CrontabScheduleFactory,
73
- PeriodicTaskFactory,
74
- PortfolioSynchronizationFactory,
75
- PriceComputationFactory,
76
- SynchronizationTaskFactory,
71
+ RebalancerFactory,
72
+ RebalancingModelFactory
77
73
  )
78
74
 
79
75
  from wbcore.tests.conftest import * # isort:skip
@@ -90,7 +86,7 @@ register(DataBackendFactory)
90
86
  register(ProviderFactory)
91
87
  register(SourceFactory)
92
88
  register(ParserHandlerFactory)
93
-
89
+ register(CrontabScheduleFactory)
94
90
  register(AssetPositionFactory)
95
91
  register(ProductFactory)
96
92
  register(ProductGroupFactory)
@@ -110,6 +106,8 @@ register(IndexProductFactory, "index_product")
110
106
  register(CurrencyFXRatesFactory)
111
107
  register(ProductPortfolioRoleFactory, "product_portfolio_role")
112
108
  register(ManagerPortfolioRoleFactory, "manager_portfolio_role")
109
+ register(RebalancerFactory)
110
+ register(RebalancingModelFactory)
113
111
 
114
112
  register(CurrencyFactory)
115
113
  register(CityFactory)
@@ -128,11 +126,6 @@ register(UserFactory)
128
126
  register(SuperUserFactory, "superuser")
129
127
  register(CustodianFactory)
130
128
 
131
- register(CrontabScheduleFactory)
132
- register(PeriodicTaskFactory)
133
- register(SynchronizationTaskFactory)
134
- register(PortfolioSynchronizationFactory)
135
- register(PriceComputationFactory)
136
129
  register(AdjustmentFactory)
137
130
  register(IndexFactory)
138
131
 
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING
4
4
  import pytest
5
5
  from django.db import IntegrityError
6
6
  from wbcrm.models import Account
7
+
7
8
  from wbportfolio.models import AccountReconciliation, Claim
8
9
  from wbportfolio.models.products import Product
9
10
  from wbportfolio.models.reconciliations.account_reconciliation_lines import (
@@ -12,6 +13,7 @@ from wbportfolio.models.reconciliations.account_reconciliation_lines import (
12
13
 
13
14
  if TYPE_CHECKING:
14
15
  from wbcore.contrib.authentication.models import User
16
+
15
17
  from wbportfolio.factories import AccountReconciliationLineFactory, ClaimFactory
16
18
 
17
19
 
@@ -2,6 +2,7 @@ from decimal import Decimal
2
2
 
3
3
  import pytest
4
4
  from pandas.tseries.offsets import BDay
5
+
5
6
  from wbportfolio.models import AssetPosition, PortfolioRole
6
7
 
7
8
 
@@ -52,13 +53,13 @@ class TestAssetPositionModel:
52
53
  i2 = instrument_price_factory.create(instrument=equity_factory.create(), market_capitalization=30000000000)
53
54
  asset_position_factory.create(
54
55
  portfolio=portfolio,
55
- underlying_instrument_price=i1,
56
+ underlying_quote_price=i1,
56
57
  currency_fx_rate_instrument_to_usd=fx_rate,
57
58
  currency_fx_rate_portfolio_to_usd=fx_rate,
58
59
  ) # small
59
60
  asset_position_factory.create(
60
61
  portfolio=portfolio,
61
- underlying_instrument_price=i2,
62
+ underlying_quote_price=i2,
62
63
  currency_fx_rate_instrument_to_usd=fx_rate,
63
64
  currency_fx_rate_portfolio_to_usd=fx_rate,
64
65
  ) # larg
@@ -130,24 +131,24 @@ class TestAssetPositionModel:
130
131
  assert ap4.performance is None
131
132
  assert ep4._net_value_usd / ep3._net_value_usd is not None
132
133
 
133
- def test_get_underlying_instrument_price(self, asset_position_factory, instrument_price_factory):
134
+ def test_get_underlying_quote_price(self, asset_position_factory, instrument_price_factory):
134
135
  """
135
- In this test, we confirm that an asset position with no matching real price for the same date and underlying instrument will result in an empty underlying_instrument_price
136
+ In this test, we confirm that an asset position with no matching real price for the same date and underlying instrument will result in an empty underlying_quote_price
136
137
  """
137
- a1 = asset_position_factory.create(underlying_instrument_price=None)
138
+ a1 = asset_position_factory.create(underlying_quote_price=None)
138
139
  a1.save()
139
- assert a1.underlying_instrument_price is None
140
+ assert a1.underlying_quote_price is None
140
141
  p2_calculated = instrument_price_factory.create(calculated=True)
141
142
  a2 = asset_position_factory.create(
142
- underlying_instrument_price=None, underlying_instrument=p2_calculated.instrument, date=p2_calculated.date
143
+ underlying_quote_price=None, underlying_instrument=p2_calculated.instrument, date=p2_calculated.date
143
144
  )
144
145
  a2.save()
145
- assert a2.underlying_instrument_price is None
146
+ assert a2.underlying_quote_price is None
146
147
  p2_real = instrument_price_factory.create(
147
148
  calculated=False, date=p2_calculated.date, instrument=p2_calculated.instrument
148
149
  )
149
150
  a2.save()
150
- assert a2.underlying_instrument_price == p2_real
151
+ assert a2.underlying_quote_price == p2_real
151
152
 
152
153
  def test_change_currency_recompute_currency_fx_rate_for_price_and_assets(
153
154
  self, weekday, instrument, currency_fx_rates_factory, instrument_price_factory, asset_position_factory
@@ -173,21 +174,28 @@ class TestAssetPositionModel:
173
174
  def test_create_price_set_assetposition(self, asset_position_factory, instrument_price_factory):
174
175
  p0 = instrument_price_factory.create()
175
176
  d1 = (p0.date + BDay(1)).date()
176
- a1 = asset_position_factory.create(
177
- date=d1, underlying_instrument=p0.instrument, underlying_instrument_price=None
178
- )
177
+ a1 = asset_position_factory.create(date=d1, underlying_instrument=p0.instrument, underlying_quote_price=None)
179
178
 
180
179
  # check it is linked to the only price
181
- assert a1.underlying_instrument_price == p0
180
+ assert a1.underlying_quote_price == p0
182
181
  p1 = instrument_price_factory.create(instrument=p0.instrument, date=d1)
183
182
  a1.refresh_from_db()
184
- assert a1.underlying_instrument_price == p1
183
+ assert a1.underlying_quote_price == p1
185
184
 
186
185
  d_1 = (p0.date - BDay(1)).date()
187
- a_1 = asset_position_factory.create(
188
- date=d_1, underlying_instrument=p1.instrument, underlying_instrument_price=None
189
- )
190
- assert a_1.underlying_instrument_price is None
186
+ a_1 = asset_position_factory.create(date=d_1, underlying_instrument=p1.instrument, underlying_quote_price=None)
187
+ assert a_1.underlying_quote_price is None
191
188
  p_1 = instrument_price_factory.create(instrument=p1.instrument, date=d_1)
192
189
  a_1.refresh_from_db()
193
- assert a_1.underlying_instrument_price == p_1
190
+ assert a_1.underlying_quote_price == p_1
191
+
192
+ def test_save_asset_without_instrument(self, asset_position_factory, instrument_factory):
193
+ parent = instrument_factory.create()
194
+ instrument_factory.create(parent=parent) # noise
195
+ primary_quote = instrument_factory.create(parent=parent, is_primary=True)
196
+
197
+ a = asset_position_factory.create(underlying_quote=primary_quote, underlying_instrument=None)
198
+ assert a.underlying_instrument == parent
199
+
200
+ a = asset_position_factory.create(underlying_quote=None, underlying_instrument=parent)
201
+ assert a.underlying_quote == primary_quote
@@ -4,6 +4,7 @@ from datetime import timedelta
4
4
  import pytest
5
5
  from django.db.models import ProtectedError
6
6
  from faker import Faker
7
+
7
8
  from wbportfolio.models import Trade
8
9
  from wbportfolio.models.transactions.claim import Claim
9
10
 
@@ -6,6 +6,7 @@ from faker import Faker
6
6
  from pandas.tseries.offsets import BDay
7
7
  from wbfdm.import_export.handlers.instrument_price import InstrumentPriceImportHandler
8
8
  from wbfdm.models import InstrumentPrice
9
+
9
10
  from wbportfolio.import_export.handlers.asset_position import AssetPositionImportHandler
10
11
  from wbportfolio.import_export.handlers.fees import FeesImportHandler
11
12
  from wbportfolio.import_export.handlers.trade import TradeImportHandler
@@ -181,8 +182,11 @@ class TestImportMixinModel:
181
182
  handler.process(data)
182
183
  assert AssetPosition.objects.count() == 1
183
184
 
184
- def test_import_assetposition_index(self, import_source, index, currency, equity, asset_position_factory):
185
+ def test_import_assetposition_index(
186
+ self, import_source, index, portfolio, currency, equity, asset_position_factory
187
+ ):
185
188
  positions = asset_position_factory.build(underlying_instrument=equity)
189
+ index.portfolios.add(portfolio)
186
190
  data = {"data": [self._serialize_position(positions, index, equity)]}
187
191
 
188
192
  # Import non existing data
@@ -3,6 +3,7 @@ from faker import Faker
3
3
  from pandas.tseries.offsets import BDay
4
4
  from wbfdm.factories import InstrumentFactory
5
5
  from wbfdm.models import InstrumentPrice
6
+
6
7
  from wbportfolio.models import (
7
8
  Adjustment,
8
9
  PortfolioInstrumentPreferredClassificationThroughModel,
@@ -33,16 +34,16 @@ class TestMergeInstrument:
33
34
  a1 = asset_position_factory.create(underlying_instrument=main_instrument, date=weekday)
34
35
  a2 = asset_position_factory.create(underlying_instrument=merged_instrument, date=weekday)
35
36
 
36
- assert a1.underlying_instrument_price == p1
37
- assert a2.underlying_instrument_price == p2
37
+ assert a1.underlying_quote_price == p1
38
+ assert a2.underlying_quote_price == p2
38
39
  main_instrument.merge(merged_instrument)
39
40
  a2.refresh_from_db()
40
41
  a1.refresh_from_db()
41
42
 
42
43
  assert a1.underlying_instrument == main_instrument # Make sure this doesn't change
43
- assert a1.underlying_instrument_price == p1 # Make sure this doesn't change
44
+ assert a1.underlying_quote_price == p1 # Make sure this doesn't change
44
45
  assert a2.underlying_instrument == main_instrument
45
- assert a2.underlying_instrument_price == p1
46
+ assert a2.underlying_quote_price == p1
46
47
 
47
48
  def test_roles(self, main_instrument, merged_instrument, product_portfolio_role_factory):
48
49
  """