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
@@ -3,6 +3,7 @@ from datetime import timedelta
3
3
  import pytest
4
4
  from faker import Faker
5
5
  from pandas.tseries.offsets import BDay
6
+
6
7
  from wbportfolio.models import Trade
7
8
 
8
9
  fake = Faker()
@@ -4,6 +4,7 @@ from decimal import Decimal
4
4
  import pytest
5
5
  from faker import Faker
6
6
  from wbfdm.models import Instrument
7
+
7
8
  from wbportfolio.models import Product, Trade
8
9
 
9
10
  from .utils import PortfolioTestMixin
@@ -48,9 +49,11 @@ class TestProductModel(PortfolioTestMixin):
48
49
  p3 = instrument_price_factory.create(
49
50
  instrument=Instrument.objects.get(id=product.id), date=t3.transaction_date
50
51
  )
51
- assert product.total_value(p1.date) == pytest.approx(Decimal(0), rel=Decimal("1e-4"))
52
- assert product.total_value(p2.date) == pytest.approx(Decimal(p2.net_value * (t1.shares)), rel=Decimal("1e-4"))
53
- assert product.total_value(p3.date) == pytest.approx(
52
+ assert product.get_total_aum_usd(p1.date) == pytest.approx(Decimal(0), rel=Decimal("1e-4"))
53
+ assert product.get_total_aum_usd(p2.date) == pytest.approx(
54
+ Decimal(p2.net_value * (t1.shares)), rel=Decimal("1e-4")
55
+ )
56
+ assert product.get_total_aum_usd(p3.date) == pytest.approx(
54
57
  Decimal(p3.net_value * (t1.shares + t2.shares)), rel=Decimal("1e-4")
55
58
  )
56
59
 
@@ -1,4 +1,5 @@
1
1
  import pytest
2
+
2
3
  from wbportfolio.factories import (
3
4
  ManagerPortfolioRoleFactory,
4
5
  ProductPortfolioRoleFactory,
@@ -4,6 +4,7 @@ from unittest.mock import patch
4
4
  import pytest
5
5
  from faker import Faker
6
6
  from pandas.tseries.offsets import BDay
7
+
7
8
  from wbportfolio.factories import AdjustmentFactory
8
9
  from wbportfolio.models import Adjustment, PortfolioRole
9
10
  from wbportfolio.models.adjustments import post_adjustment_on_prices
@@ -6,6 +6,7 @@ import pytest
6
6
  from django.contrib.auth import get_user_model
7
7
  from django.contrib.auth.models import Permission
8
8
  from faker import Faker
9
+
9
10
  from wbportfolio.models import Claim, Trade
10
11
 
11
12
  fake = Faker()
@@ -4,6 +4,7 @@ from decimal import Decimal
4
4
  import pytest
5
5
  from wbfdm.factories import CashFactory
6
6
  from wbfdm.models import InstrumentPrice
7
+
7
8
  from wbportfolio.models import FeeCalculation, Fees, Product
8
9
 
9
10
 
@@ -0,0 +1,81 @@
1
+ from datetime import date
2
+ from decimal import Decimal
3
+
4
+ import pandas as pd
5
+ import pytest
6
+ from django.core.exceptions import ValidationError
7
+ from pandas._libs.tslibs.offsets import BDay
8
+
9
+ from wbportfolio.models import AssetPosition, TradeProposal
10
+
11
+
12
+ @pytest.mark.django_db
13
+ class TestRebalancingModel:
14
+ def test_get_target_portfolio(self, rebalancing_model, portfolio, weekday, asset_position_factory):
15
+ with pytest.raises(ValidationError): # trigger value error because rebalancing not valid (no position yet)
16
+ rebalancing_model.get_target_portfolio(portfolio, (weekday + BDay(1)).date(), weekday)
17
+ a1 = asset_position_factory(weighting=0.7, portfolio=portfolio, date=weekday)
18
+ a2 = asset_position_factory(weighting=0.3, portfolio=portfolio, date=weekday)
19
+ target_portfolio = rebalancing_model.get_target_portfolio(portfolio, (weekday + BDay(1)).date(), weekday)
20
+ target_positions = target_portfolio.positions_map
21
+ assert target_positions[a1.underlying_instrument.id].weighting == 0.5
22
+ assert target_positions[a2.underlying_instrument.id].weighting == 0.5
23
+
24
+
25
+ @pytest.mark.django_db
26
+ class TestRebalancer:
27
+ def test_activation_date(self, rebalancer_factory, asset_position):
28
+ r1 = rebalancer_factory.create(portfolio=asset_position.portfolio)
29
+ assert r1.activation_date == asset_position.date
30
+
31
+ r2 = rebalancer_factory.create()
32
+ r2.activation_date = date.today()
33
+
34
+ def test_is_valid(self, weekday, rebalancer_factory):
35
+ # default frequency is monthly
36
+
37
+ first_of_the_month = weekday.replace(day=1)
38
+ rebalancer = rebalancer_factory.create(frequency="RRULE:FREQ=MONTHLY;", activation_date=first_of_the_month)
39
+ assert rebalancer.is_valid(first_of_the_month)
40
+ if weekday.day != 1:
41
+ assert not rebalancer.is_valid(weekday)
42
+
43
+ def test_evaluate_rebalancing(
44
+ self, weekday, rebalancer_factory, asset_position_factory, instrument_factory, instrument_price_factory
45
+ ):
46
+ rebalancer = rebalancer_factory.create(approve_trade_proposal_automatically=True)
47
+ trade_date = (weekday + BDay(1)).date()
48
+
49
+ i1 = instrument_factory.create()
50
+ i2 = instrument_factory.create()
51
+ instrument_price_factory.create(instrument=i1, date=weekday)
52
+ instrument_price_factory.create(instrument=i1, date=trade_date)
53
+ instrument_price_factory.create(instrument=i2, date=weekday)
54
+ instrument_price_factory.create(instrument=i2, date=trade_date)
55
+
56
+ a1 = asset_position_factory.create(
57
+ portfolio=rebalancer.portfolio, date=weekday, weighting=0.7, underlying_instrument=i1
58
+ )
59
+ a2 = asset_position_factory.create(
60
+ portfolio=rebalancer.portfolio, date=weekday, weighting=0.3, underlying_instrument=i2
61
+ )
62
+ trade_proposal = rebalancer.evaluate_rebalancing(trade_date)
63
+ assert trade_proposal.trades.count() == 2
64
+ assert trade_proposal.status == TradeProposal.Status.APPROVED
65
+ assert AssetPosition.objects.get(
66
+ portfolio=rebalancer.portfolio, date=trade_date, underlying_quote=a1.underlying_instrument
67
+ ).weighting == Decimal(0.5)
68
+ assert AssetPosition.objects.get(
69
+ portfolio=rebalancer.portfolio, date=trade_date, underlying_quote=a2.underlying_instrument
70
+ ).weighting == Decimal(0.5)
71
+
72
+ def test_get_rrule(self, weekday, rebalancer_factory):
73
+ # default frequency is monthly
74
+
75
+ first_of_the_month = weekday.replace(day=1)
76
+ until = first_of_the_month.replace(year=first_of_the_month.year + 1)
77
+ rebalancer = rebalancer_factory.create(frequency="RRULE:FREQ=MONTHLY;", activation_date=first_of_the_month)
78
+
79
+ valid_dates = set(rebalancer.get_rrule(until))
80
+ assert valid_dates
81
+ assert set(valid_dates) == set(pd.date_range(start=first_of_the_month, end=until, freq="MS"))
@@ -3,6 +3,7 @@ from decimal import Decimal
3
3
 
4
4
  import pytest
5
5
  from faker import Faker
6
+
6
7
  from wbportfolio.models import Product, Trade
7
8
 
8
9
  fake = Faker()
@@ -2,6 +2,7 @@ from datetime import timedelta
2
2
 
3
3
  import pytest
4
4
  from faker import Faker
5
+
5
6
  from wbportfolio.factories import ProductFactory
6
7
 
7
8
  fake = Faker()
File without changes
@@ -0,0 +1,35 @@
1
+ import pandas as pd
2
+
3
+ from wbportfolio.pms.analytics.portfolio import Portfolio
4
+
5
+
6
+ def test_get_next_weights():
7
+ w0 = 0.3
8
+ w1 = 0.5
9
+ w2 = 0.2
10
+ r0 = 0.1
11
+ r1 = 0.05
12
+ r2 = -0.23
13
+ weights = [w0, w1, w2]
14
+ returns = [r0, r1, r2]
15
+ portfolio = Portfolio(X=pd.DataFrame([returns]), weights=pd.Series(weights))
16
+ next_weights = portfolio.get_next_weights(pd.Series(returns))
17
+
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))
21
+
22
+
23
+ def test_get_estimate_net_value():
24
+ w0 = 0.3
25
+ w1 = 0.5
26
+ w2 = 0.2
27
+ r0 = 0.1
28
+ r1 = 0.05
29
+ r2 = -0.23
30
+ weights = [w0, w1, w2]
31
+ returns = [r0, r1, r2]
32
+ portfolio = Portfolio(X=pd.DataFrame([returns]), weights=pd.Series(weights), previous_weights=pd.Series(weights))
33
+ current_price = 100
34
+ net_asset_value = portfolio.get_estimate_net_value(current_price)
35
+ return net_asset_value == current_price * (1.0 + w0 * r0 + w1 * r1 + w2 * r2)
File without changes
@@ -0,0 +1,127 @@
1
+ from decimal import Decimal
2
+
3
+ import pytest
4
+ from pandas._libs.tslibs.offsets import BDay
5
+
6
+ from wbportfolio.factories import PortfolioFactory, TradeFactory, TradeProposalFactory
7
+ from wbportfolio.models import PortfolioPortfolioThroughModel, Trade, TradeProposal
8
+
9
+
10
+ @pytest.mark.django_db
11
+ class TestEquallyWeightedRebalancing:
12
+ @pytest.fixture()
13
+ def model(self, portfolio, weekday):
14
+ from wbportfolio.rebalancing.models import EquallyWeightedRebalancing
15
+
16
+ return EquallyWeightedRebalancing(portfolio, (weekday + BDay(1)).date(), weekday)
17
+
18
+ def test_is_valid(self, portfolio, weekday, model, asset_position_factory):
19
+ assert not model.is_valid()
20
+ asset_position_factory.create(portfolio=model.portfolio, date=model.last_effective_date)
21
+ assert model.is_valid()
22
+
23
+ def test_get_target_portfolio(self, portfolio, weekday, model, asset_position_factory):
24
+ a1 = asset_position_factory(weighting=0.7, portfolio=portfolio, date=model.last_effective_date)
25
+ a2 = asset_position_factory(weighting=0.3, portfolio=portfolio, date=model.last_effective_date)
26
+ target_portfolio = model.get_target_portfolio()
27
+ target_positions = target_portfolio.positions_map
28
+ assert target_positions[a1.underlying_instrument.id].weighting == Decimal(0.5)
29
+ assert target_positions[a2.underlying_instrument.id].weighting == Decimal(0.5)
30
+
31
+
32
+ @pytest.mark.django_db
33
+ class TestModelPortfolioRebalancing:
34
+ @pytest.fixture()
35
+ def model(self, portfolio, weekday):
36
+ from wbportfolio.rebalancing.models import ModelPortfolioRebalancing
37
+
38
+ return ModelPortfolioRebalancing(portfolio, (weekday + BDay(1)).date(), weekday)
39
+
40
+ def test_is_valid(self, portfolio, weekday, model, asset_position_factory):
41
+ assert not model.is_valid()
42
+ asset_position_factory.create(portfolio=model.portfolio, date=model.last_effective_date)
43
+ assert not model.is_valid()
44
+ model_portfolio = PortfolioFactory.create()
45
+ PortfolioPortfolioThroughModel.objects.create(
46
+ portfolio=model.portfolio,
47
+ dependency_portfolio=model_portfolio,
48
+ type=PortfolioPortfolioThroughModel.Type.MODEL,
49
+ )
50
+ asset_position_factory.create(portfolio=model.model_portfolio, date=model.last_effective_date)
51
+ assert model.is_valid()
52
+
53
+ def test_get_target_portfolio(self, portfolio, weekday, model, asset_position_factory):
54
+ model_portfolio = PortfolioFactory.create()
55
+ PortfolioPortfolioThroughModel.objects.create(
56
+ portfolio=model.portfolio,
57
+ dependency_portfolio=model_portfolio,
58
+ type=PortfolioPortfolioThroughModel.Type.MODEL,
59
+ )
60
+ asset_position_factory(portfolio=portfolio, date=model.last_effective_date) # noise
61
+ asset_position_factory(portfolio=portfolio, date=model.last_effective_date) # noise
62
+ a1 = asset_position_factory(weighting=0.8, portfolio=portfolio.model_portfolio, date=model.last_effective_date)
63
+ a2 = asset_position_factory(weighting=0.2, portfolio=portfolio.model_portfolio, date=model.last_effective_date)
64
+ target_portfolio = model.get_target_portfolio()
65
+ target_positions = target_portfolio.positions_map
66
+ assert target_positions[a1.underlying_instrument.id].weighting == Decimal("0.800000")
67
+ assert target_positions[a2.underlying_instrument.id].weighting == Decimal("0.200000")
68
+
69
+
70
+ @pytest.mark.django_db
71
+ class TestCompositeRebalancing:
72
+ @pytest.fixture()
73
+ def model(self, portfolio, weekday):
74
+ from wbportfolio.rebalancing.models import CompositeRebalancing
75
+
76
+ return CompositeRebalancing(portfolio, (weekday + BDay(1)).date(), weekday)
77
+
78
+ def test_is_valid(self, portfolio, weekday, model, asset_position_factory):
79
+ assert not model.is_valid()
80
+
81
+ trade_proposal = TradeProposalFactory.create(
82
+ portfolio=model.portfolio, trade_date=model.trade_date, status=TradeProposal.Status.APPROVED
83
+ )
84
+ TradeFactory.create(
85
+ portfolio=model.portfolio,
86
+ transaction_date=model.trade_date,
87
+ transaction_subtype=Trade.Type.BUY,
88
+ trade_proposal=trade_proposal,
89
+ weighting=0.7,
90
+ status=Trade.Status.EXECUTED,
91
+ )
92
+ TradeFactory.create(
93
+ portfolio=model.portfolio,
94
+ transaction_date=model.trade_date,
95
+ transaction_subtype=Trade.Type.BUY,
96
+ trade_proposal=trade_proposal,
97
+ weighting=0.3,
98
+ status=Trade.Status.EXECUTED,
99
+ )
100
+ assert model.is_valid()
101
+
102
+ def test_get_target_portfolio(self, portfolio, weekday, model, asset_position_factory):
103
+ trade_proposal = TradeProposalFactory.create(
104
+ portfolio=model.portfolio, trade_date=model.trade_date, status=TradeProposal.Status.APPROVED
105
+ )
106
+ asset_position_factory(portfolio=portfolio, date=model.last_effective_date) # noise
107
+ asset_position_factory(portfolio=portfolio, date=model.last_effective_date) # noise
108
+ t1 = TradeFactory.create(
109
+ portfolio=model.portfolio,
110
+ transaction_date=model.trade_date,
111
+ transaction_subtype=Trade.Type.BUY,
112
+ trade_proposal=trade_proposal,
113
+ weighting=0.8,
114
+ status=Trade.Status.EXECUTED,
115
+ )
116
+ t2 = TradeFactory.create(
117
+ portfolio=model.portfolio,
118
+ transaction_date=model.trade_date,
119
+ transaction_subtype=Trade.Type.BUY,
120
+ trade_proposal=trade_proposal,
121
+ weighting=0.2,
122
+ status=Trade.Status.EXECUTED,
123
+ )
124
+ target_portfolio = model.get_target_portfolio()
125
+ target_positions = target_portfolio.positions_map
126
+ assert target_positions[t1.underlying_instrument.id].weighting == Decimal("0.800000")
127
+ assert target_positions[t2.underlying_instrument.id].weighting == Decimal("0.200000")
@@ -1,4 +1,5 @@
1
1
  import pytest
2
+
2
3
  from wbportfolio.serializers import ClaimModelSerializer
3
4
 
4
5
 
@@ -2,10 +2,10 @@ from django.dispatch import receiver
2
2
  from wbcore.contrib.currency.factories import CurrencyFXRatesFactory
3
3
  from wbcore.contrib.directory.factories import CompanyFactory
4
4
  from wbcore.test.signals import custom_update_kwargs, get_custom_factory
5
+
5
6
  from wbportfolio.factories import (
6
7
  CustomerTradeFactory,
7
8
  InstrumentPriceFactory,
8
- ModelPortfolioFactory,
9
9
  ProductFactory,
10
10
  ProductPortfolioRoleFactory,
11
11
  TradeProposalFactory,
@@ -19,7 +19,6 @@ from wbportfolio.viewsets import (
19
19
  CustomerDistributionInstrumentChartViewSet,
20
20
  DistributionChartViewSet,
21
21
  DistributionTableViewSet,
22
- ModelPortfolioModelViewSet,
23
22
  NominalProductChartView,
24
23
  PortfolioRoleInstrumentModelViewSet,
25
24
  ProductCustomerModelViewSet,
@@ -42,11 +41,6 @@ def receive_factory_subscription_redemption_instrument(sender, *args, **kwargs):
42
41
  return CustomerTradeFactory
43
42
 
44
43
 
45
- @receiver(get_custom_factory, sender=ModelPortfolioModelViewSet)
46
- def receive_factory_model_portfolio(sender, *args, **kwargs):
47
- return ModelPortfolioFactory
48
-
49
-
50
44
  @receiver(get_custom_factory, sender=PortfolioRoleInstrumentModelViewSet)
51
45
  def receive_factory_product_portfolio_role(sender, *args, **kwargs):
52
46
  return ProductPortfolioRoleFactory
@@ -17,6 +17,8 @@ for key, value in default_config.items():
17
17
  "AccountReconciliationLineModelViewSet",
18
18
  "AccountReconciliation",
19
19
  "AccountReconciliationLine",
20
+ "TopDownPortfolioCompositionPandasAPIView",
21
+ "CompositionModelPortfolioPandasView",
20
22
  # "ClaimModelViewSet",
21
23
  # "ClaimModelSerializer",
22
24
  ],
@@ -2,6 +2,7 @@ import pytest
2
2
  from faker import Faker
3
3
  from pandas.tseries.offsets import BDay
4
4
  from rest_framework.test import APIRequestFactory
5
+
5
6
  from wbportfolio.viewsets.positions import AggregatedAssetPositionLiquidityPandasView
6
7
 
7
8
  fake = Faker()
@@ -6,6 +6,7 @@ from faker import Faker
6
6
  from pandas.tseries.offsets import BDay, BMonthEnd, BYearEnd
7
7
  from rest_framework.reverse import reverse
8
8
  from rest_framework.test import force_authenticate
9
+
9
10
  from wbportfolio.factories import InstrumentPriceFactory, ProductFactory
10
11
  from wbportfolio.viewsets.product_performance import PerformanceComparisonPandasView
11
12
 
@@ -9,6 +9,7 @@ from psycopg.types.range import DateRange
9
9
  from rest_framework.reverse import reverse
10
10
  from rest_framework.test import force_authenticate
11
11
  from wbfdm.models import InstrumentPrice
12
+
12
13
  from wbportfolio.models import InstrumentPortfolioThroughModel, Product
13
14
  from wbportfolio.viewsets import ProductModelViewSet
14
15
 
@@ -8,6 +8,7 @@ from wbcore.contrib.directory.models import Entry
8
8
  from wbcore.utils.strings import format_number
9
9
  from wbcrm.factories.accounts import AccountFactory, AccountRoleFactory
10
10
  from wbcrm.models import Account
11
+
11
12
  from wbportfolio.factories.claim import ClaimFactory
12
13
  from wbportfolio.models.transactions.claim import Claim
13
14
  from wbportfolio.viewsets.transactions.claim import (
wbportfolio/urls.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from django.urls import include, path
2
2
  from wbcore.routers import WBCoreRouter
3
+
3
4
  from wbportfolio import viewsets
4
5
 
5
6
  router = WBCoreRouter()
@@ -21,22 +22,10 @@ router.register(r"portfolio", viewsets.PortfolioModelViewSet, basename="portfoli
21
22
  router.register(r"portfoliocashflow", viewsets.DailyPortfolioCashFlowModelViewSet, basename="portfoliocashflow")
22
23
  router.register(r"portfolioswingpricing", viewsets.PortfolioSwingPricingModelViewSet, basename="portfolioswingpricing")
23
24
  router.register(r"portfoliocashtarget", viewsets.PortfolioCashTargetModelViewSet, basename="portfoliocashtarget")
24
- router.register(r"modelportfolio", viewsets.ModelPortfolioModelViewSet, basename="modelportfolio")
25
25
 
26
26
  router.register(
27
27
  r"portfoliorepresentation", viewsets.PortfolioRepresentationViewSet, basename="portfoliorepresentation"
28
28
  )
29
- router.register(
30
- r"portfoliosynchronizationrepresentation",
31
- viewsets.PortfolioSynchronizationRepresentationViewSet,
32
- basename="portfoliosynchronizationrepresentation",
33
- )
34
- router.register(
35
- r"pricecomputationrepresentation",
36
- viewsets.PriceComputationRepresentationViewSet,
37
- basename="pricecomputationrepresentation",
38
- )
39
-
40
29
 
41
30
  router.register(r"portfoliorole", viewsets.PortfolioRoleModelViewSet, basename="portfoliorole")
42
31
 
@@ -52,6 +41,21 @@ router.register(r"traderepresentation", viewsets.TradeRepresentationViewSet, bas
52
41
  router.register(
53
42
  r"tradeproposalrepresentation", viewsets.TradeProposalRepresentationViewSet, basename="tradeproposalrepresentation"
54
43
  )
44
+ router.register(
45
+ r"rebalancingmodelrepresentation",
46
+ viewsets.RebalancingModelRepresentationViewSet,
47
+ basename="rebalancingmodelrepresentation",
48
+ )
49
+ router.register(
50
+ r"rebalancerrepresentation",
51
+ viewsets.RebalancerRepresentationViewSet,
52
+ basename="rebalancerrepresentation",
53
+ )
54
+ router.register(
55
+ r"rebalancer",
56
+ viewsets.RebalancerModelViewSet,
57
+ basename="rebalancer",
58
+ )
55
59
  router.register(
56
60
  r"feesproductperformance", viewsets.ProductPerformanceFeesModelViewSet, basename="feesproductperformance"
57
61
  )
@@ -115,6 +119,16 @@ portfolio_router.register(
115
119
  viewsets.ESGMetricAggregationPortfolioPandasViewSet,
116
120
  basename="portfolio-esgaggregation",
117
121
  )
122
+ portfolio_router.register(
123
+ r"treegraphchart",
124
+ viewsets.PortfolioTreeGraphChartViewSet,
125
+ basename="portfolio-treegraphchart",
126
+ )
127
+ portfolio_router.register(
128
+ r"topdowncomposition",
129
+ viewsets.TopDownPortfolioCompositionPandasAPIView,
130
+ basename="portfolio-topdowncomposition",
131
+ )
118
132
 
119
133
  router.register(r"register", viewsets.RegisterModelViewSet, basename="register")
120
134
  router.register(r"registerrepresentation", viewsets.RegisterRepresentationViewSet, basename="registerrepresentation")
@@ -11,10 +11,11 @@ from .charts import *
11
11
  from .custodians import CustodianModelViewSet, CustodianRepresentationViewSet
12
12
  from .portfolio_relationship import InstrumentPreferedClassificationThroughProductModelViewSet
13
13
  from .portfolios import (
14
- ModelPortfolioModelViewSet,
15
14
  PortfolioModelViewSet,
16
15
  PortfolioPortfolioThroughModelViewSet,
17
16
  PortfolioRepresentationViewSet,
17
+ PortfolioTreeGraphChartViewSet,
18
+ TopDownPortfolioCompositionPandasAPIView
18
19
  )
19
20
  from .positions import (
20
21
  AggregatedAssetPositionLiquidityPandasView,
@@ -23,10 +24,6 @@ from .positions import (
23
24
  from .registers import RegisterModelViewSet, RegisterRepresentationViewSet
24
25
  from .roles import PortfolioRoleInstrumentModelViewSet, PortfolioRoleModelViewSet
25
26
  from .signals import *
26
- from .synchronization import (
27
- PortfolioSynchronizationRepresentationViewSet,
28
- PriceComputationRepresentationViewSet,
29
- )
30
27
  from .transactions import *
31
28
  from .adjustments import AdjustmentEquityModelViewSet, AdjustmentModelViewSet
32
29
  from .product_groups import ProductGroupModelViewSet, ProductGroupRepresentationViewSet
@@ -1,5 +1,6 @@
1
1
  from wbcore import viewsets
2
2
  from wbcore.permissions.permissions import InternalUserPermissionMixin
3
+
3
4
  from wbportfolio.models import Adjustment
4
5
  from wbportfolio.serializers import AdjustmentModelSerializer
5
6