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
@@ -1,617 +0,0 @@
1
- from datetime import date, timedelta
2
- from decimal import Decimal
3
- from unittest.mock import PropertyMock, patch
4
-
5
- import pytest
6
- from celery import shared_task
7
- from django.contrib.contenttypes.models import ContentType
8
- from django.db.models import Sum
9
- from faker import Faker
10
- from pandas.tseries.offsets import BDay
11
- from wbcore.contrib.currency.models import Currency
12
- from wbportfolio.models import Trade
13
- from wbportfolio.models.synchronization.portfolio_synchronization import (
14
- PortfolioSynchronization,
15
- synchronize_portfolio_as_task,
16
- )
17
- from wbportfolio.models.synchronization.price_computation import (
18
- PriceComputation,
19
- compute_price_as_task,
20
- )
21
- from wbportfolio.pms.typing import Position as PositionDTO
22
-
23
- from .utils import PortfolioTestMixin
24
-
25
- fake = Faker()
26
-
27
-
28
- @shared_task()
29
- def callback(*args, **kwargs):
30
- return True
31
-
32
-
33
- @pytest.mark.django_db
34
- class TestSynchronizationTaskModel(PortfolioTestMixin):
35
- def test_init(self, synchronization_task):
36
- assert synchronization_task.id is not None
37
-
38
- def test_args(self, synchronization_task):
39
- content_type = ContentType.objects.get_for_model(synchronization_task)
40
- assert (
41
- synchronization_task.args
42
- == f'["{synchronization_task.id}", "{content_type.app_label}", "{content_type.model}"]'
43
- )
44
-
45
- def test_import_method(self, synchronization_task):
46
- with pytest.raises(NotImplementedError):
47
- method = synchronization_task._import_method
48
- method()
49
-
50
- def test_import_method_with_custom_callback(self, synchronization_task_factory):
51
- synchronization_task = synchronization_task_factory.create(
52
- import_path="wbportfolio.tests.models.test_synchronization"
53
- )
54
- callback_si_task = callback.si().task.replace("modules.wbportfolio.", "")
55
- test_callback_si_task = synchronization_task._import_method.si().task.replace("modules.wbportfolio.", "")
56
- assert callback_si_task == test_callback_si_task
57
-
58
- def test_tasks_signature(self, synchronization_task):
59
- with pytest.raises(NotImplementedError):
60
- synchronization_task._tasks_signature()
61
-
62
- def test_default_callback(self, synchronization_task):
63
- with pytest.raises(NotImplementedError):
64
- synchronization_task._default_callback()
65
-
66
- def test_end_task_signature(self, synchronization_task):
67
- assert synchronization_task._end_task_signature() is None
68
-
69
- def test_end_task_signature_with_signature(self, synchronization_task_factory, periodic_task_factory):
70
- dependent_task = synchronization_task_factory.create(
71
- task="wbportfolio.tests.models.test_synchronization.callback"
72
- )
73
-
74
- synchronization_task = synchronization_task_factory.create()
75
- synchronization_task.dependent_task = dependent_task
76
- synchronization_task.save()
77
-
78
- assert (
79
- synchronization_task._end_task_signature().task
80
- == "wbportfolio.models.synchronization.synchronization.task"
81
- )
82
-
83
-
84
- @pytest.mark.django_db
85
- class TestPortfolioSynchronization(PortfolioTestMixin):
86
- def test_init(self, portfolio_synchronization):
87
- assert portfolio_synchronization.id is not None
88
-
89
- @patch.object(PortfolioSynchronization, "_import_method", new_callable=PropertyMock)
90
- @patch.object(PortfolioSynchronization, "is_valid_date")
91
- def test_synchronize_automatic_approval(
92
- self,
93
- mock_is_valid_date,
94
- mock_import_method,
95
- portfolio_synchronization,
96
- portfolio_factory,
97
- asset_position_factory,
98
- equity_factory,
99
- currency_factory,
100
- trade_factory,
101
- instrument_portfolio_through_model_factory,
102
- active_product,
103
- weekday,
104
- ):
105
- portfolio = portfolio_factory.create(portfolio_synchronization=portfolio_synchronization)
106
- instrument_portfolio_through_model_factory.create(instrument=active_product, portfolio=portfolio)
107
- trade_factory.create(
108
- underlying_instrument=active_product,
109
- transaction_date=weekday,
110
- transaction_subtype=Trade.Type.SUBSCRIPTION,
111
- shares=100,
112
- )
113
-
114
- portfolio.save()
115
-
116
- def callback(*args, **kwargs):
117
- for i in range(10):
118
- position = asset_position_factory.build(
119
- underlying_instrument=equity_factory.create(), currency=currency_factory.create()
120
- )
121
- yield None, PositionDTO(
122
- underlying_instrument=position.underlying_instrument.id,
123
- instrument_type=position.underlying_instrument.instrument_type,
124
- date=weekday,
125
- asset_valuation_date=weekday,
126
- currency=position.currency.id,
127
- price=position.initial_price,
128
- shares=position.initial_shares,
129
- currency_fx_rate=position.initial_currency_fx_rate,
130
- weighting=position.weighting,
131
- is_estimated=position.is_estimated,
132
- )
133
-
134
- mock_import_method.return_value = callback
135
- mock_is_valid_date.return_value = True
136
- portfolio_synchronization.synchronize(portfolio, weekday, override_execution_datetime_validity=True)
137
- assert portfolio.assets.count() == 10
138
- assert 1.0 == pytest.approx(float(portfolio.assets.aggregate(s=Sum("weighting"))["s"]), rel=1e-4)
139
-
140
- @patch.object(PortfolioSynchronization, "_import_method", new_callable=PropertyMock)
141
- @patch.object(PortfolioSynchronization, "is_valid_date")
142
- @patch.object(Currency, "convert")
143
- def test_synchronize_tradeprosal(
144
- self,
145
- mock_convert,
146
- mock_is_valid_date,
147
- mock_import_method,
148
- portfolio_synchronization_factory,
149
- portfolio_factory,
150
- asset_position_factory,
151
- equity_factory,
152
- currency_factory,
153
- active_product,
154
- weekday,
155
- trade_factory,
156
- instrument_portfolio_through_model_factory,
157
- ):
158
- portfolio_synchronization = portfolio_synchronization_factory(is_automatic_validation=False)
159
-
160
- portfolio = portfolio_factory.create()
161
- portfolio.portfolio_synchronization = portfolio_synchronization
162
- portfolio.save()
163
-
164
- instrument_portfolio_through_model_factory.create(instrument=active_product, portfolio=portfolio)
165
- trade_factory.create(
166
- underlying_instrument=active_product,
167
- transaction_date=weekday,
168
- transaction_subtype=Trade.Type.SUBSCRIPTION,
169
- shares=100,
170
- )
171
-
172
- def callback(*args, **kwargs):
173
- for i in range(10):
174
- position = asset_position_factory.build(
175
- underlying_instrument=equity_factory.create(), currency=currency_factory.create()
176
- )
177
- position_dict = PositionDTO(
178
- underlying_instrument=position.underlying_instrument.id,
179
- instrument_type=position.underlying_instrument.instrument_type,
180
- date=weekday,
181
- asset_valuation_date=weekday,
182
- currency=position.currency.id,
183
- price=position.initial_price,
184
- shares=position.initial_shares,
185
- currency_fx_rate=position.initial_currency_fx_rate,
186
- weighting=position.weighting,
187
- is_estimated=position.is_estimated,
188
- )
189
- yield position_dict, position_dict
190
-
191
- mock_is_valid_date.return_value = True
192
- mock_import_method.return_value = callback
193
- mock_convert.return_value = Decimal(1.0)
194
- portfolio_synchronization.synchronize(portfolio, weekday, override_execution_datetime_validity=True)
195
- assert portfolio.trade_proposals.count() == 1
196
- assert portfolio.transactions.count() == 10
197
-
198
- def test_synchronize_as_task_si(self, portfolio_synchronization, portfolio, weekday):
199
- assert portfolio_synchronization.synchronize_as_task_si(
200
- portfolio, weekday
201
- ) == synchronize_portfolio_as_task.si(portfolio_synchronization.id, portfolio.id, weekday)
202
-
203
- def test_tasks_signature(
204
- self,
205
- portfolio_synchronization,
206
- portfolio_factory,
207
- weekday,
208
- trade_factory,
209
- product_factory,
210
- instrument_portfolio_through_model_factory,
211
- ):
212
- p1 = portfolio_factory.create()
213
- p1.portfolio_synchronization = portfolio_synchronization
214
- p1.save()
215
- product1 = product_factory.create(delisted_date=None, inception_date=weekday - timedelta(days=30))
216
- instrument_portfolio_through_model_factory.create(instrument=product1, portfolio=p1)
217
- trade_factory.create(
218
- underlying_instrument=product1,
219
- transaction_date=weekday,
220
- transaction_subtype=Trade.Type.SUBSCRIPTION,
221
- shares=100,
222
- )
223
-
224
- p2 = portfolio_factory.create()
225
- p2.portfolio_synchronization = portfolio_synchronization
226
- p2.save()
227
- product2 = product_factory.create(delisted_date=None, inception_date=weekday - timedelta(days=30))
228
- instrument_portfolio_through_model_factory.create(instrument=product2, portfolio=p2)
229
- trade_factory.create(
230
- underlying_instrument=product2,
231
- transaction_date=weekday,
232
- transaction_subtype=Trade.Type.SUBSCRIPTION,
233
- shares=100,
234
- )
235
-
236
- signatures = portfolio_synchronization._tasks_signature(sync_date=weekday)
237
- assert len(list(signatures)) == 2
238
-
239
- @patch.object(PortfolioSynchronization, "is_valid_date")
240
- def test_default_callback(
241
- self,
242
- mock_is_valid_date,
243
- active_product,
244
- weekday,
245
- portfolio_synchronization,
246
- portfolio_factory,
247
- index_factory,
248
- equity_factory,
249
- asset_position_factory,
250
- trade_factory,
251
- instrument_price_factory,
252
- instrument_portfolio_through_model_factory,
253
- ):
254
- while weekday.weekday() in [5, 6]:
255
- weekday += timedelta(days=1)
256
- mock_is_valid_date.return_value = True
257
- root_index = index_factory.create()
258
- root_index_portfolio = root_index.portfolio
259
-
260
- index1 = index_factory.create()
261
- index1_portfolio = index1.portfolio
262
-
263
- a1 = asset_position_factory.create(
264
- underlying_instrument=index1,
265
- portfolio=root_index_portfolio,
266
- weighting=0.6,
267
- initial_shares=None,
268
- initial_price=100,
269
- date=weekday,
270
- )
271
-
272
- index2 = index_factory.create()
273
- index2_portfolio = index2.portfolio
274
-
275
- a2 = asset_position_factory.create(
276
- underlying_instrument=index2,
277
- portfolio=root_index_portfolio,
278
- weighting=0.4,
279
- initial_shares=None,
280
- initial_price=100,
281
- date=weekday,
282
- )
283
-
284
- a1_1 = asset_position_factory.create(
285
- underlying_instrument=equity_factory.create(),
286
- portfolio=index1_portfolio,
287
- weighting=0.2,
288
- initial_shares=None,
289
- initial_price=100,
290
- date=weekday,
291
- )
292
- a2_1 = asset_position_factory.create(
293
- underlying_instrument=equity_factory.create(),
294
- portfolio=index1_portfolio,
295
- weighting=0.3,
296
- initial_shares=None,
297
- initial_price=100,
298
- date=weekday,
299
- )
300
- a3_1 = asset_position_factory.create(
301
- underlying_instrument=equity_factory.create(),
302
- portfolio=index1_portfolio,
303
- weighting=0.5,
304
- initial_shares=None,
305
- initial_price=100,
306
- date=weekday,
307
- )
308
-
309
- a1_2 = asset_position_factory.create(
310
- underlying_instrument=equity_factory.create(),
311
- portfolio=index2_portfolio,
312
- weighting=0.7,
313
- initial_shares=None,
314
- initial_price=100,
315
- date=weekday,
316
- )
317
- a2_2 = asset_position_factory.create(
318
- underlying_instrument=equity_factory.create(),
319
- portfolio=index2_portfolio,
320
- weighting=0.3,
321
- initial_shares=None,
322
- initial_price=100,
323
- date=weekday,
324
- )
325
-
326
- product_base_portfolio = active_product.primary_portfolio
327
- product_portfolio = portfolio_factory.create(portfolio_synchronization=portfolio_synchronization)
328
- instrument_portfolio_through_model_factory.create(instrument=active_product, portfolio=product_portfolio)
329
- trade_factory.create(
330
- underlying_instrument=active_product,
331
- transaction_date=weekday,
332
- transaction_subtype=Trade.Type.SUBSCRIPTION,
333
- shares=100,
334
- )
335
-
336
- product_portfolio.depends_on.add(root_index_portfolio)
337
-
338
- instrument_portfolio_through_model_factory.create(instrument=active_product, portfolio=product_portfolio)
339
-
340
- instrument_price_factory.create(instrument=active_product, date=weekday)
341
- trade_factory.create(
342
- underlying_instrument=active_product,
343
- portfolio=product_base_portfolio,
344
- transaction_date=weekday,
345
- shares=1000,
346
- transaction_subtype="SUBSCRIPTION",
347
- )
348
-
349
- portfolio_synchronization.synchronize(product_portfolio, weekday, override_execution_datetime_validity=True)
350
- assert product_portfolio.assets.filter(date=weekday).count() == 5
351
- assert float(a1_1.weighting) * float(a1.weighting) == pytest.approx(
352
- float(
353
- product_portfolio.assets.filter(
354
- portfolio_created=index1_portfolio,
355
- underlying_instrument=a1_1.underlying_instrument,
356
- date=weekday,
357
- )
358
- .first()
359
- .weighting
360
- )
361
- )
362
- assert float(a2_1.weighting) * float(a1.weighting) == pytest.approx(
363
- float(
364
- product_portfolio.assets.filter(
365
- portfolio_created=index1_portfolio,
366
- underlying_instrument=a2_1.underlying_instrument,
367
- date=weekday,
368
- )
369
- .first()
370
- .weighting
371
- )
372
- )
373
- assert float(a3_1.weighting) * float(a1.weighting) == pytest.approx(
374
- float(
375
- product_portfolio.assets.filter(
376
- portfolio_created=index1_portfolio,
377
- underlying_instrument=a3_1.underlying_instrument,
378
- date=weekday,
379
- )
380
- .first()
381
- .weighting
382
- )
383
- )
384
- assert float(a1_2.weighting) * float(a2.weighting) == pytest.approx(
385
- float(
386
- product_portfolio.assets.filter(
387
- portfolio_created=index2_portfolio,
388
- underlying_instrument=a1_2.underlying_instrument,
389
- date=weekday,
390
- )
391
- .first()
392
- .weighting
393
- )
394
- )
395
- assert float(a2_2.weighting) * float(a2.weighting) == pytest.approx(
396
- float(
397
- product_portfolio.assets.filter(
398
- portfolio_created=index2_portfolio,
399
- underlying_instrument=a2_2.underlying_instrument,
400
- date=weekday,
401
- )
402
- .first()
403
- .weighting
404
- )
405
- )
406
- assert Decimal(1.0) == pytest.approx(product_portfolio.assets.aggregate(s=Sum("weighting"))["s"])
407
-
408
- @patch.object(PortfolioSynchronization, "synchronize")
409
- def test_synchronize_portfolio_as_task(self, mock_synchronize, portfolio_synchronization, portfolio, weekday):
410
- synchronize_portfolio_as_task(portfolio_synchronization.id, portfolio.id, weekday)
411
- assert mock_synchronize.call_count == 1
412
-
413
-
414
- @pytest.mark.django_db
415
- class TestPriceComputation(PortfolioTestMixin):
416
- def test_init(self, price_computation):
417
- assert price_computation.id is not None
418
-
419
- def test_compute_price_as_task_si(self, price_computation, instrument, weekday):
420
- assert price_computation.compute_price_as_task_si(instrument, weekday) == compute_price_as_task.si(
421
- price_computation.id, instrument.id, weekday
422
- )
423
-
424
- def test_tasks_signature_with_sync_date(self, price_computation, product_factory, weekday):
425
- i1 = product_factory.create(inception_date=weekday - BDay(1), delisted_date=None)
426
- i1.price_computation = price_computation
427
- i1.save()
428
-
429
- i2 = product_factory.create(inception_date=weekday - BDay(1), delisted_date=None)
430
- i2.price_computation = price_computation
431
- i2.save()
432
-
433
- i_inactive = product_factory.create(
434
- inception_date=weekday - BDay(1), delisted_date=weekday - timedelta(days=1)
435
- )
436
- i_inactive.price_computation = price_computation
437
- i_inactive.save()
438
-
439
- signatures = price_computation._tasks_signature(sync_date=weekday)
440
-
441
- assert set(map(lambda x: (x.args[0], x.args[1], x.args[2]), signatures)) == {
442
- (price_computation.id, i1.id, weekday),
443
- (price_computation.id, i2.id, weekday),
444
- }
445
-
446
- def test_tasks_signature_without_sync_date(self, price_computation, product_factory, instrument_price_factory):
447
- thursday = date(2023, 10, 19)
448
- friday = date(2023, 10, 20)
449
- saturday = date(2023, 10, 21)
450
- sunday = date(2023, 10, 22)
451
- monday = date(2023, 10, 23)
452
- tuesday = date(2023, 10, 24)
453
- product = product_factory.create(inception_date=thursday, delisted_date=None)
454
- product.price_computation = price_computation
455
- product.save()
456
- instrument_price_factory.create(date=thursday, instrument=product)
457
-
458
- signatures_friday = price_computation._tasks_signature(to_date=friday)
459
- assert set(map(lambda x: (x.args[0], x.args[1], x.args[2]), signatures_friday)) == {
460
- (price_computation.id, product.id, thursday),
461
- }
462
-
463
- signatures_saturday = price_computation._tasks_signature(to_date=saturday)
464
- assert set(map(lambda x: (x.args[0], x.args[1], x.args[2]), signatures_saturday)) == {
465
- (price_computation.id, product.id, thursday),
466
- (price_computation.id, product.id, friday),
467
- }
468
-
469
- signatures_sunday = price_computation._tasks_signature(to_date=sunday)
470
- assert set(map(lambda x: (x.args[0], x.args[1], x.args[2]), signatures_sunday)) == {
471
- (price_computation.id, product.id, thursday),
472
- (price_computation.id, product.id, friday),
473
- }
474
-
475
- signatures_monday = price_computation._tasks_signature(to_date=monday)
476
- assert set(map(lambda x: (x.args[0], x.args[1], x.args[2]), signatures_monday)) == {
477
- (price_computation.id, product.id, thursday),
478
- (price_computation.id, product.id, friday),
479
- }
480
-
481
- signatures_tuesday = price_computation._tasks_signature(to_date=tuesday)
482
- assert set(map(lambda x: (x.args[0], x.args[1], x.args[2]), signatures_tuesday)) == {
483
- (price_computation.id, product.id, thursday),
484
- (price_computation.id, product.id, friday),
485
- (price_computation.id, product.id, monday),
486
- }
487
- instrument_price_factory.create(date=monday, instrument=product)
488
- signatures = price_computation._tasks_signature(to_date=tuesday)
489
- assert set(map(lambda x: (x.args[0], x.args[1], x.args[2]), signatures)) == {
490
- (price_computation.id, product.id, monday),
491
- }
492
-
493
- @patch.object(PriceComputation, "compute")
494
- def test_compute_price_as_task(self, mock_compute, price_computation, instrument, weekday):
495
- compute_price_as_task(price_computation.id, instrument.id, weekday)
496
- assert mock_compute.call_count == 1
497
-
498
- @patch.object(PriceComputation, "_import_method", new_callable=PropertyMock)
499
- def test_compute(
500
- self, mock_import_method, weekday, price_computation, instrument_factory, instrument_price_factory
501
- ):
502
- while weekday.weekday() in [5, 6]:
503
- weekday += timedelta(days=1)
504
- instrument = instrument_factory.create(inception_date=weekday - timedelta(days=1), delisted_date=None)
505
- instrument.price_computation = price_computation
506
- instrument.save()
507
- price = instrument_price_factory.build(instrument=instrument, date=weekday)
508
-
509
- def callback(*args, **kwargs):
510
- return {
511
- "instrument": instrument,
512
- "date": weekday,
513
- "gross_value": price.gross_value,
514
- "net_value": price.net_value,
515
- }
516
-
517
- mock_import_method.return_value = callback
518
- price_computation.compute(instrument, weekday, override_execution_datetime_validity=True)
519
- assert instrument.prices.count() == 1
520
- assert float(price.net_value) == pytest.approx(float(instrument.prices.first().net_value))
521
-
522
- def test_default_callback(
523
- self,
524
- weekday,
525
- price_computation,
526
- equity_factory,
527
- product_factory,
528
- asset_position_factory,
529
- instrument_price_factory,
530
- trade_factory,
531
- ):
532
- while weekday.weekday() in [5, 6]:
533
- weekday += timedelta(days=1)
534
-
535
- previous_sync_date = weekday - timedelta(days=1)
536
- while previous_sync_date.weekday() in [5, 6]:
537
- previous_sync_date -= timedelta(days=1)
538
-
539
- product = product_factory.create(
540
- price_computation=price_computation, inception_date=weekday - timedelta(days=1), delisted_date=None
541
- )
542
- portfolio = product.portfolio
543
-
544
- trade_factory.create(
545
- underlying_instrument=product,
546
- portfolio=portfolio,
547
- transaction_date=previous_sync_date,
548
- shares=1000,
549
- transaction_subtype="SUBSCRIPTION",
550
- )
551
-
552
- e1 = equity_factory.create()
553
- e2 = equity_factory.create()
554
- e3 = equity_factory.create()
555
-
556
- a1_1 = asset_position_factory.create(
557
- underlying_instrument=e1,
558
- portfolio=portfolio,
559
- date=previous_sync_date,
560
- weighting=Decimal(0.3),
561
- initial_price=Decimal(100),
562
- initial_shares=300,
563
- )
564
- a2_1 = asset_position_factory.create(
565
- underlying_instrument=e2,
566
- portfolio=portfolio,
567
- date=previous_sync_date,
568
- weighting=Decimal(0.5),
569
- initial_price=Decimal(100),
570
- initial_shares=500,
571
- )
572
- a3_1 = asset_position_factory.create(
573
- underlying_instrument=e3,
574
- portfolio=portfolio,
575
- date=previous_sync_date,
576
- weighting=Decimal(0.2),
577
- initial_price=Decimal(100),
578
- initial_shares=200,
579
- )
580
-
581
- a1_2 = asset_position_factory.create(
582
- underlying_instrument=e1,
583
- portfolio=portfolio,
584
- date=weekday,
585
- weighting=Decimal(0.3719),
586
- initial_price=Decimal(150),
587
- initial_shares=300,
588
- )
589
- a2_2 = asset_position_factory.create(
590
- underlying_instrument=e2,
591
- portfolio=portfolio,
592
- date=weekday,
593
- weighting=Decimal(0.4959),
594
- initial_price=Decimal(120),
595
- initial_shares=500,
596
- )
597
- a3_2 = asset_position_factory.create(
598
- underlying_instrument=e3,
599
- portfolio=portfolio,
600
- date=weekday,
601
- weighting=Decimal(0.1322),
602
- initial_price=Decimal(80),
603
- initial_shares=200,
604
- )
605
-
606
- price = instrument_price_factory.create(instrument=product, date=previous_sync_date, net_value=100)
607
- price_computation.compute(product, weekday, override_execution_datetime_validity=True)
608
-
609
- total_perf = (
610
- (a1_2._price / a1_1._price - 1) * a1_1.weighting
611
- + (a2_2._price / a2_1._price - 1) * a2_1.weighting
612
- + (a3_2._price / a3_1._price - 1) * a3_1.weighting
613
- )
614
- assert product.prices.count() == 2
615
- assert float(price.net_value * (Decimal(1.0) + total_perf)) == pytest.approx(
616
- float(product.prices.filter(date=weekday).first().net_value)
617
- )
@@ -1,25 +0,0 @@
1
- from wbcore import viewsets
2
- from wbcore.permissions.permissions import InternalUserPermissionMixin
3
- from wbportfolio.models import PortfolioSynchronization, PriceComputation
4
- from wbportfolio.serializers import (
5
- PortfolioSynchronizationRepresentationSerializer,
6
- PriceComputationRepresentationSerializer,
7
- )
8
-
9
-
10
- class PortfolioSynchronizationRepresentationViewSet(InternalUserPermissionMixin, viewsets.RepresentationViewSet):
11
- IDENTIFIER = "wbportfolio:portfoliosynchronization"
12
-
13
- ordering_fields = ordering = ("name",)
14
- search_fields = ("name",)
15
- queryset = PortfolioSynchronization.objects.all()
16
- serializer_class = PortfolioSynchronizationRepresentationSerializer
17
-
18
-
19
- class PriceComputationRepresentationViewSet(InternalUserPermissionMixin, viewsets.RepresentationViewSet):
20
- IDENTIFIER = "wbportfolio:portfoliosynchronization"
21
-
22
- ordering_fields = ordering = ("name",)
23
- search_fields = ("name",)
24
- queryset = PriceComputation.objects.all()
25
- serializer_class = PriceComputationRepresentationSerializer