wbportfolio 2.2.1__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 (486) hide show
  1. wbportfolio/__init__.py +1 -0
  2. wbportfolio/admin/__init__.py +12 -0
  3. wbportfolio/admin/asset.py +47 -0
  4. wbportfolio/admin/custodians.py +9 -0
  5. wbportfolio/admin/portfolio.py +127 -0
  6. wbportfolio/admin/portfolio_relationships.py +22 -0
  7. wbportfolio/admin/product_groups.py +42 -0
  8. wbportfolio/admin/products.py +80 -0
  9. wbportfolio/admin/reconciliations.py +14 -0
  10. wbportfolio/admin/registers.py +17 -0
  11. wbportfolio/admin/roles.py +19 -0
  12. wbportfolio/admin/synchronization/__init__.py +2 -0
  13. wbportfolio/admin/synchronization/admin.py +114 -0
  14. wbportfolio/admin/synchronization/portfolio_synchronization.py +18 -0
  15. wbportfolio/admin/synchronization/price_computation.py +21 -0
  16. wbportfolio/admin/transactions/__init__.py +5 -0
  17. wbportfolio/admin/transactions/claim.py +16 -0
  18. wbportfolio/admin/transactions/dividends.py +14 -0
  19. wbportfolio/admin/transactions/fees.py +35 -0
  20. wbportfolio/admin/transactions/trades.py +49 -0
  21. wbportfolio/admin/transactions/transactions.py +37 -0
  22. wbportfolio/analysis/__init__.py +0 -0
  23. wbportfolio/analysis/claims.py +235 -0
  24. wbportfolio/apps.py +5 -0
  25. wbportfolio/contrib/__init__.py +0 -0
  26. wbportfolio/contrib/company_portfolio/__init__.py +0 -0
  27. wbportfolio/contrib/company_portfolio/admin.py +28 -0
  28. wbportfolio/contrib/company_portfolio/apps.py +29 -0
  29. wbportfolio/contrib/company_portfolio/configs/__init__.py +3 -0
  30. wbportfolio/contrib/company_portfolio/configs/display.py +182 -0
  31. wbportfolio/contrib/company_portfolio/configs/endpoints.py +34 -0
  32. wbportfolio/contrib/company_portfolio/configs/previews.py +37 -0
  33. wbportfolio/contrib/company_portfolio/constants.py +1 -0
  34. wbportfolio/contrib/company_portfolio/dynamic_preferences_registry.py +87 -0
  35. wbportfolio/contrib/company_portfolio/factories.py +32 -0
  36. wbportfolio/contrib/company_portfolio/filters.py +127 -0
  37. wbportfolio/contrib/company_portfolio/management.py +19 -0
  38. wbportfolio/contrib/company_portfolio/migrations/0001_initial.py +214 -0
  39. wbportfolio/contrib/company_portfolio/migrations/__init__.py +0 -0
  40. wbportfolio/contrib/company_portfolio/models.py +334 -0
  41. wbportfolio/contrib/company_portfolio/scripts.py +76 -0
  42. wbportfolio/contrib/company_portfolio/serializers.py +303 -0
  43. wbportfolio/contrib/company_portfolio/tasks.py +19 -0
  44. wbportfolio/contrib/company_portfolio/tests/__init__.py +0 -0
  45. wbportfolio/contrib/company_portfolio/tests/conftest.py +161 -0
  46. wbportfolio/contrib/company_portfolio/tests/test_models.py +161 -0
  47. wbportfolio/contrib/company_portfolio/urls.py +29 -0
  48. wbportfolio/contrib/company_portfolio/viewsets.py +195 -0
  49. wbportfolio/defaults/__init__.py +0 -0
  50. wbportfolio/defaults/fees/__init__.py +0 -0
  51. wbportfolio/defaults/fees/default.py +92 -0
  52. wbportfolio/defaults/portfolio/__init__.py +0 -0
  53. wbportfolio/defaults/portfolio/default_rebalancing.py +45 -0
  54. wbportfolio/dynamic_preferences_registry.py +58 -0
  55. wbportfolio/factories/__init__.py +35 -0
  56. wbportfolio/factories/adjustments.py +17 -0
  57. wbportfolio/factories/assets.py +75 -0
  58. wbportfolio/factories/claim.py +39 -0
  59. wbportfolio/factories/custodians.py +11 -0
  60. wbportfolio/factories/dividends.py +14 -0
  61. wbportfolio/factories/fees.py +15 -0
  62. wbportfolio/factories/indexes.py +17 -0
  63. wbportfolio/factories/portfolio_cash_flow.py +20 -0
  64. wbportfolio/factories/portfolio_cash_targets.py +15 -0
  65. wbportfolio/factories/portfolio_swing_pricings.py +15 -0
  66. wbportfolio/factories/portfolios.py +59 -0
  67. wbportfolio/factories/product_groups.py +28 -0
  68. wbportfolio/factories/products.py +56 -0
  69. wbportfolio/factories/pytest_utils.py +121 -0
  70. wbportfolio/factories/reconciliations.py +23 -0
  71. wbportfolio/factories/roles.py +20 -0
  72. wbportfolio/factories/synchronization.py +40 -0
  73. wbportfolio/factories/trades.py +35 -0
  74. wbportfolio/factories/transactions.py +21 -0
  75. wbportfolio/fdm/__init__.py +0 -0
  76. wbportfolio/fdm/tasks.py +12 -0
  77. wbportfolio/filters/__init__.py +32 -0
  78. wbportfolio/filters/assets.py +485 -0
  79. wbportfolio/filters/assets_and_net_new_money_progression.py +42 -0
  80. wbportfolio/filters/custodians.py +10 -0
  81. wbportfolio/filters/esg.py +22 -0
  82. wbportfolio/filters/performances.py +171 -0
  83. wbportfolio/filters/portfolios.py +24 -0
  84. wbportfolio/filters/positions.py +178 -0
  85. wbportfolio/filters/products.py +157 -0
  86. wbportfolio/filters/roles.py +26 -0
  87. wbportfolio/filters/signals.py +92 -0
  88. wbportfolio/filters/transactions/__init__.py +20 -0
  89. wbportfolio/filters/transactions/claim.py +394 -0
  90. wbportfolio/filters/transactions/fees.py +66 -0
  91. wbportfolio/filters/transactions/trades.py +224 -0
  92. wbportfolio/filters/transactions/transactions.py +98 -0
  93. wbportfolio/import_export/__init__.py +0 -0
  94. wbportfolio/import_export/backends/__init__.py +2 -0
  95. wbportfolio/import_export/backends/ubs/__init__.py +3 -0
  96. wbportfolio/import_export/backends/ubs/asset_position.py +45 -0
  97. wbportfolio/import_export/backends/ubs/fees.py +63 -0
  98. wbportfolio/import_export/backends/ubs/instrument_price.py +44 -0
  99. wbportfolio/import_export/backends/ubs/mixin.py +15 -0
  100. wbportfolio/import_export/backends/utils.py +58 -0
  101. wbportfolio/import_export/backends/wbfdm/__init__.py +2 -0
  102. wbportfolio/import_export/backends/wbfdm/adjustment.py +50 -0
  103. wbportfolio/import_export/backends/wbfdm/dividend.py +16 -0
  104. wbportfolio/import_export/backends/wbfdm/mixin.py +15 -0
  105. wbportfolio/import_export/handlers/__init__.py +0 -0
  106. wbportfolio/import_export/handlers/adjustment.py +39 -0
  107. wbportfolio/import_export/handlers/asset_position.py +167 -0
  108. wbportfolio/import_export/handlers/dividend.py +80 -0
  109. wbportfolio/import_export/handlers/fees.py +58 -0
  110. wbportfolio/import_export/handlers/portfolio_cash_flow.py +57 -0
  111. wbportfolio/import_export/handlers/register.py +43 -0
  112. wbportfolio/import_export/handlers/trade.py +191 -0
  113. wbportfolio/import_export/parsers/__init__.py +0 -0
  114. wbportfolio/import_export/parsers/default_mapping.py +30 -0
  115. wbportfolio/import_export/parsers/jpmorgan/__init__.py +0 -0
  116. wbportfolio/import_export/parsers/jpmorgan/customer_trade.py +63 -0
  117. wbportfolio/import_export/parsers/jpmorgan/fees.py +64 -0
  118. wbportfolio/import_export/parsers/jpmorgan/strategy.py +116 -0
  119. wbportfolio/import_export/parsers/jpmorgan/valuation.py +41 -0
  120. wbportfolio/import_export/parsers/leonteq/__init__.py +0 -0
  121. wbportfolio/import_export/parsers/leonteq/customer_trade.py +47 -0
  122. wbportfolio/import_export/parsers/leonteq/equity.py +81 -0
  123. wbportfolio/import_export/parsers/leonteq/fees.py +70 -0
  124. wbportfolio/import_export/parsers/leonteq/trade.py +94 -0
  125. wbportfolio/import_export/parsers/leonteq/valuation.py +39 -0
  126. wbportfolio/import_export/parsers/natixis/__init__.py +0 -0
  127. wbportfolio/import_export/parsers/natixis/customer_trade.py +62 -0
  128. wbportfolio/import_export/parsers/natixis/d1_customer_trade.py +66 -0
  129. wbportfolio/import_export/parsers/natixis/d1_equity.py +80 -0
  130. wbportfolio/import_export/parsers/natixis/d1_fees.py +58 -0
  131. wbportfolio/import_export/parsers/natixis/d1_trade.py +70 -0
  132. wbportfolio/import_export/parsers/natixis/d1_valuation.py +41 -0
  133. wbportfolio/import_export/parsers/natixis/dividend.py +53 -0
  134. wbportfolio/import_export/parsers/natixis/equity.py +60 -0
  135. wbportfolio/import_export/parsers/natixis/fees.py +53 -0
  136. wbportfolio/import_export/parsers/natixis/trade.py +63 -0
  137. wbportfolio/import_export/parsers/natixis/utils.py +76 -0
  138. wbportfolio/import_export/parsers/natixis/valuation.py +46 -0
  139. wbportfolio/import_export/parsers/refinitiv/__init__.py +0 -0
  140. wbportfolio/import_export/parsers/refinitiv/adjustment.py +24 -0
  141. wbportfolio/import_export/parsers/sg_lux/__init__.py +0 -0
  142. wbportfolio/import_export/parsers/sg_lux/custodian_positions.py +70 -0
  143. wbportfolio/import_export/parsers/sg_lux/customer_trade.py +75 -0
  144. wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py +140 -0
  145. wbportfolio/import_export/parsers/sg_lux/customer_trade_slk.py +80 -0
  146. wbportfolio/import_export/parsers/sg_lux/customer_trade_without_pw.py +57 -0
  147. wbportfolio/import_export/parsers/sg_lux/equity.py +137 -0
  148. wbportfolio/import_export/parsers/sg_lux/fees.py +56 -0
  149. wbportfolio/import_export/parsers/sg_lux/perf_fees.py +51 -0
  150. wbportfolio/import_export/parsers/sg_lux/portfolio_cash_flow.py +29 -0
  151. wbportfolio/import_export/parsers/sg_lux/portfolio_future_cash_flow.py +36 -0
  152. wbportfolio/import_export/parsers/sg_lux/registers.py +210 -0
  153. wbportfolio/import_export/parsers/sg_lux/sylk.py +248 -0
  154. wbportfolio/import_export/parsers/sg_lux/utils.py +36 -0
  155. wbportfolio/import_export/parsers/sg_lux/valuation.py +53 -0
  156. wbportfolio/import_export/parsers/societe_generale/__init__.py +0 -0
  157. wbportfolio/import_export/parsers/societe_generale/customer_trade.py +54 -0
  158. wbportfolio/import_export/parsers/societe_generale/strategy.py +94 -0
  159. wbportfolio/import_export/parsers/societe_generale/valuation.py +37 -0
  160. wbportfolio/import_export/parsers/tellco/__init__.py +0 -0
  161. wbportfolio/import_export/parsers/tellco/customer_trade.py +64 -0
  162. wbportfolio/import_export/parsers/tellco/equity.py +86 -0
  163. wbportfolio/import_export/parsers/tellco/valuation.py +52 -0
  164. wbportfolio/import_export/parsers/ubs/__init__.py +0 -0
  165. wbportfolio/import_export/parsers/ubs/api/__init__.py +0 -0
  166. wbportfolio/import_export/parsers/ubs/api/asset_position.py +106 -0
  167. wbportfolio/import_export/parsers/ubs/api/fees.py +31 -0
  168. wbportfolio/import_export/parsers/ubs/api/instrument_price.py +20 -0
  169. wbportfolio/import_export/parsers/ubs/api/utils.py +0 -0
  170. wbportfolio/import_export/parsers/ubs/customer_trade.py +60 -0
  171. wbportfolio/import_export/parsers/ubs/equity.py +97 -0
  172. wbportfolio/import_export/parsers/ubs/historical_customer_trade.py +67 -0
  173. wbportfolio/import_export/parsers/ubs/valuation.py +52 -0
  174. wbportfolio/import_export/parsers/vontobel/__init__.py +0 -0
  175. wbportfolio/import_export/parsers/vontobel/asset_position.py +97 -0
  176. wbportfolio/import_export/parsers/vontobel/customer_trade.py +54 -0
  177. wbportfolio/import_export/parsers/vontobel/historical_customer_trade.py +40 -0
  178. wbportfolio/import_export/parsers/vontobel/instrument.py +34 -0
  179. wbportfolio/import_export/parsers/vontobel/management_fees.py +86 -0
  180. wbportfolio/import_export/parsers/vontobel/performance_fees.py +35 -0
  181. wbportfolio/import_export/parsers/vontobel/trade.py +38 -0
  182. wbportfolio/import_export/parsers/vontobel/utils.py +17 -0
  183. wbportfolio/import_export/parsers/vontobel/valuation.py +29 -0
  184. wbportfolio/import_export/resources/__init__.py +0 -0
  185. wbportfolio/import_export/resources/assets.py +68 -0
  186. wbportfolio/import_export/resources/trades.py +41 -0
  187. wbportfolio/import_export/utils.py +42 -0
  188. wbportfolio/metric/__init__.py +0 -0
  189. wbportfolio/metric/backends/__init__.py +2 -0
  190. wbportfolio/metric/backends/base.py +86 -0
  191. wbportfolio/metric/backends/constants.py +222 -0
  192. wbportfolio/metric/backends/portfolio_base.py +255 -0
  193. wbportfolio/metric/backends/portfolio_esg.py +66 -0
  194. wbportfolio/metric/tests/__init__.py +0 -0
  195. wbportfolio/metric/tests/conftest.py +4 -0
  196. wbportfolio/metric/tests/test_portfolio_base.py +135 -0
  197. wbportfolio/metric/tests/test_portfolio_esg.py +69 -0
  198. wbportfolio/migrations/0001_initial_squashed.py +13848 -0
  199. wbportfolio/migrations/0002_product_default_sub_account_squashed_0039_alter_assetallocation_company_and_more.py +3836 -0
  200. wbportfolio/migrations/0040_instrument_financial_instrument.py +26 -0
  201. wbportfolio/migrations/0041_remove_listresearch_research_ptr_and_more.py +129 -0
  202. wbportfolio/migrations/0042_instrumentlist_instrumentlistthroughmodel_and_more.py +71 -0
  203. wbportfolio/migrations/0043_alter_instrumentlistthroughmodel_options_and_more.py +238 -0
  204. wbportfolio/migrations/0044_alter_instrumentlist_identifier.py +35 -0
  205. wbportfolio/migrations/0045_alter_instrument_financial_instrument.py +26 -0
  206. wbportfolio/migrations/0046_add_product_default_account.py +166 -0
  207. wbportfolio/migrations/0047_remove_product_default_sub_account.py +14 -0
  208. wbportfolio/migrations/0048_alter_trade_status.py +29 -0
  209. wbportfolio/migrations/0049_trade_claimed_shares.py +25 -0
  210. wbportfolio/migrations/0050_fees_fee_date_fees_wbportfolio_transac_1f7a29_idx.py +44 -0
  211. wbportfolio/migrations/0051_delete_macroreview.py +11 -0
  212. wbportfolio/migrations/0052_remove_cash_instrument_ptr_and_more.py +888 -0
  213. wbportfolio/migrations/0053_remove_product_group.py +132 -0
  214. wbportfolio/migrations/0054_portfolioinstrumentpreferredclassificationthroughmodel_and_more.py +270 -0
  215. wbportfolio/migrations/0055_remove_product__custom_management_rebates_and_more.py +139 -0
  216. wbportfolio/migrations/0056_remove_companyportfoliodata_assets_under_management_currency_and_more.py +56 -0
  217. wbportfolio/migrations/0057_alter_portfolio_preferred_instrument_classifications_and_more.py +36 -0
  218. wbportfolio/migrations/0058_pmsinstrument.py +23 -0
  219. wbportfolio/migrations/0059_fees_unique_fees.py +51 -0
  220. wbportfolio/migrations/0060_alter_portfolioportfoliothroughmodel_type.py +21 -0
  221. wbportfolio/migrations/0061_portfolio_bank_accounts_product_bank_account_and_more.py +175 -0
  222. wbportfolio/migrations/0062_alter_dailyportfoliocashflow_options.py +20 -0
  223. wbportfolio/migrations/0063_accountreconciliation_accountreconciliationline_and_more.py +133 -0
  224. wbportfolio/migrations/0064_alter_portfolio_managers_portfolio_is_tracked_and_more.py +40 -0
  225. wbportfolio/migrations/0065_alter_portfolio_managers_claim_as_shares_and_more.py +73 -0
  226. wbportfolio/migrations/0066_assetposition_initial_shares_at_custodian_and_more.py +108 -0
  227. wbportfolio/migrations/0067_assetposition_unique_asset_position.py +77 -0
  228. wbportfolio/migrations/0068_trade_internal_trade_trade_marked_as_internal_and_more.py +59 -0
  229. wbportfolio/migrations/0069_remove_portfolio_is_invested_and_more.py +56 -0
  230. wbportfolio/migrations/0070_remove_assetposition_unique_asset_position_and_more.py +82 -0
  231. wbportfolio/migrations/0071_alter_trade_options_alter_trade_order.py +22 -0
  232. wbportfolio/migrations/__init__.py +0 -0
  233. wbportfolio/models/__init__.py +26 -0
  234. wbportfolio/models/adjustments.py +246 -0
  235. wbportfolio/models/asset.py +869 -0
  236. wbportfolio/models/custodians.py +101 -0
  237. wbportfolio/models/indexes.py +33 -0
  238. wbportfolio/models/mixins/__init__.py +0 -0
  239. wbportfolio/models/mixins/instruments.py +127 -0
  240. wbportfolio/models/mixins/liquidity_stress_test.py +1307 -0
  241. wbportfolio/models/portfolio.py +1039 -0
  242. wbportfolio/models/portfolio_cash_flow.py +167 -0
  243. wbportfolio/models/portfolio_cash_targets.py +46 -0
  244. wbportfolio/models/portfolio_relationship.py +135 -0
  245. wbportfolio/models/portfolio_swing_pricings.py +51 -0
  246. wbportfolio/models/product_groups.py +230 -0
  247. wbportfolio/models/products.py +569 -0
  248. wbportfolio/models/reconciliations/__init__.py +2 -0
  249. wbportfolio/models/reconciliations/account_reconciliation_lines.py +192 -0
  250. wbportfolio/models/reconciliations/account_reconciliations.py +102 -0
  251. wbportfolio/models/reconciliations/reconciliations.py +25 -0
  252. wbportfolio/models/registers.py +132 -0
  253. wbportfolio/models/roles.py +208 -0
  254. wbportfolio/models/synchronization/__init__.py +3 -0
  255. wbportfolio/models/synchronization/portfolio_synchronization.py +292 -0
  256. wbportfolio/models/synchronization/price_computation.py +200 -0
  257. wbportfolio/models/synchronization/synchronization.py +188 -0
  258. wbportfolio/models/transactions/__init__.py +7 -0
  259. wbportfolio/models/transactions/claim.py +634 -0
  260. wbportfolio/models/transactions/dividends.py +31 -0
  261. wbportfolio/models/transactions/expiry.py +7 -0
  262. wbportfolio/models/transactions/fees.py +153 -0
  263. wbportfolio/models/transactions/trade_proposals.py +502 -0
  264. wbportfolio/models/transactions/trades.py +704 -0
  265. wbportfolio/models/transactions/transactions.py +211 -0
  266. wbportfolio/models/utils.py +12 -0
  267. wbportfolio/permissions.py +13 -0
  268. wbportfolio/pms/__init__.py +0 -0
  269. wbportfolio/pms/statistics/__init__.py +0 -0
  270. wbportfolio/pms/trading/__init__.py +1 -0
  271. wbportfolio/pms/trading/handler.py +164 -0
  272. wbportfolio/pms/typing.py +194 -0
  273. wbportfolio/preferences.py +6 -0
  274. wbportfolio/reports/__init__.py +0 -0
  275. wbportfolio/reports/monthly_position_report.py +74 -0
  276. wbportfolio/risk_management/__init__.py +0 -0
  277. wbportfolio/risk_management/backends/__init__.py +11 -0
  278. wbportfolio/risk_management/backends/accounts.py +166 -0
  279. wbportfolio/risk_management/backends/controversy_portfolio.py +63 -0
  280. wbportfolio/risk_management/backends/exposure_portfolio.py +203 -0
  281. wbportfolio/risk_management/backends/instrument_list_portfolio.py +89 -0
  282. wbportfolio/risk_management/backends/liquidity_risk.py +86 -0
  283. wbportfolio/risk_management/backends/liquidity_stress_instrument.py +86 -0
  284. wbportfolio/risk_management/backends/mixins.py +220 -0
  285. wbportfolio/risk_management/backends/product_integrity.py +111 -0
  286. wbportfolio/risk_management/backends/stop_loss_instrument.py +24 -0
  287. wbportfolio/risk_management/backends/stop_loss_portfolio.py +36 -0
  288. wbportfolio/risk_management/backends/ucits_portfolio.py +63 -0
  289. wbportfolio/risk_management/tests/__init__.py +0 -0
  290. wbportfolio/risk_management/tests/conftest.py +15 -0
  291. wbportfolio/risk_management/tests/test_accounts.py +98 -0
  292. wbportfolio/risk_management/tests/test_controversy_portfolio.py +33 -0
  293. wbportfolio/risk_management/tests/test_exposure_portfolio.py +94 -0
  294. wbportfolio/risk_management/tests/test_instrument_list_portfolio.py +60 -0
  295. wbportfolio/risk_management/tests/test_liquidity_risk.py +47 -0
  296. wbportfolio/risk_management/tests/test_product_integrity.py +55 -0
  297. wbportfolio/risk_management/tests/test_stop_loss_instrument.py +110 -0
  298. wbportfolio/risk_management/tests/test_stop_loss_portfolio.py +119 -0
  299. wbportfolio/risk_management/tests/test_ucits_portfolio.py +39 -0
  300. wbportfolio/serializers/__init__.py +42 -0
  301. wbportfolio/serializers/adjustments.py +24 -0
  302. wbportfolio/serializers/assets.py +166 -0
  303. wbportfolio/serializers/custodians.py +26 -0
  304. wbportfolio/serializers/portfolio_cash_flow.py +48 -0
  305. wbportfolio/serializers/portfolio_cash_targets.py +20 -0
  306. wbportfolio/serializers/portfolio_relationship.py +53 -0
  307. wbportfolio/serializers/portfolio_swing_pricing.py +20 -0
  308. wbportfolio/serializers/portfolios.py +143 -0
  309. wbportfolio/serializers/positions.py +76 -0
  310. wbportfolio/serializers/product_group.py +88 -0
  311. wbportfolio/serializers/products.py +331 -0
  312. wbportfolio/serializers/reconciliations.py +171 -0
  313. wbportfolio/serializers/registers.py +72 -0
  314. wbportfolio/serializers/roles.py +60 -0
  315. wbportfolio/serializers/signals.py +157 -0
  316. wbportfolio/serializers/synchronization.py +18 -0
  317. wbportfolio/serializers/transactions/__init__.py +24 -0
  318. wbportfolio/serializers/transactions/claim.py +310 -0
  319. wbportfolio/serializers/transactions/dividends.py +18 -0
  320. wbportfolio/serializers/transactions/expiry.py +18 -0
  321. wbportfolio/serializers/transactions/fees.py +32 -0
  322. wbportfolio/serializers/transactions/trades.py +315 -0
  323. wbportfolio/serializers/transactions/transactions.py +84 -0
  324. wbportfolio/tasks.py +125 -0
  325. wbportfolio/tests/__init__.py +0 -0
  326. wbportfolio/tests/conftest.py +164 -0
  327. wbportfolio/tests/models/__init__.py +0 -0
  328. wbportfolio/tests/models/test_account_reconciliation.py +191 -0
  329. wbportfolio/tests/models/test_assets.py +193 -0
  330. wbportfolio/tests/models/test_custodians.py +12 -0
  331. wbportfolio/tests/models/test_customer_trades.py +113 -0
  332. wbportfolio/tests/models/test_dividends.py +7 -0
  333. wbportfolio/tests/models/test_imports.py +192 -0
  334. wbportfolio/tests/models/test_instrument_mixins.py +48 -0
  335. wbportfolio/tests/models/test_merge.py +133 -0
  336. wbportfolio/tests/models/test_portfolio_cash_flow.py +112 -0
  337. wbportfolio/tests/models/test_portfolio_cash_targets.py +27 -0
  338. wbportfolio/tests/models/test_portfolio_swing_pricings.py +42 -0
  339. wbportfolio/tests/models/test_portfolios.py +676 -0
  340. wbportfolio/tests/models/test_product_groups.py +80 -0
  341. wbportfolio/tests/models/test_products.py +187 -0
  342. wbportfolio/tests/models/test_roles.py +82 -0
  343. wbportfolio/tests/models/test_splits.py +233 -0
  344. wbportfolio/tests/models/test_synchronization.py +617 -0
  345. wbportfolio/tests/models/transactions/__init__.py +0 -0
  346. wbportfolio/tests/models/transactions/test_claim.py +129 -0
  347. wbportfolio/tests/models/transactions/test_fees.py +65 -0
  348. wbportfolio/tests/models/transactions/test_trades.py +204 -0
  349. wbportfolio/tests/models/utils.py +13 -0
  350. wbportfolio/tests/serializers/__init__.py +0 -0
  351. wbportfolio/tests/serializers/test_claims.py +21 -0
  352. wbportfolio/tests/signals.py +151 -0
  353. wbportfolio/tests/tests.py +31 -0
  354. wbportfolio/tests/viewsets/__init__.py +0 -0
  355. wbportfolio/tests/viewsets/test_assets.py +67 -0
  356. wbportfolio/tests/viewsets/test_performances.py +72 -0
  357. wbportfolio/tests/viewsets/test_products.py +92 -0
  358. wbportfolio/tests/viewsets/transactions/__init__.py +0 -0
  359. wbportfolio/tests/viewsets/transactions/test_claims.py +146 -0
  360. wbportfolio/urls.py +247 -0
  361. wbportfolio/utils.py +30 -0
  362. wbportfolio/viewsets/__init__.py +57 -0
  363. wbportfolio/viewsets/adjustments.py +46 -0
  364. wbportfolio/viewsets/assets.py +562 -0
  365. wbportfolio/viewsets/assets_and_net_new_money_progression.py +117 -0
  366. wbportfolio/viewsets/charts/__init__.py +1 -0
  367. wbportfolio/viewsets/charts/assets.py +247 -0
  368. wbportfolio/viewsets/configs/__init__.py +6 -0
  369. wbportfolio/viewsets/configs/buttons/__init__.py +23 -0
  370. wbportfolio/viewsets/configs/buttons/adjustments.py +13 -0
  371. wbportfolio/viewsets/configs/buttons/assets.py +145 -0
  372. wbportfolio/viewsets/configs/buttons/claims.py +83 -0
  373. wbportfolio/viewsets/configs/buttons/custodians.py +76 -0
  374. wbportfolio/viewsets/configs/buttons/fees.py +14 -0
  375. wbportfolio/viewsets/configs/buttons/mixins.py +88 -0
  376. wbportfolio/viewsets/configs/buttons/portfolios.py +115 -0
  377. wbportfolio/viewsets/configs/buttons/products.py +41 -0
  378. wbportfolio/viewsets/configs/buttons/reconciliations.py +65 -0
  379. wbportfolio/viewsets/configs/buttons/registers.py +11 -0
  380. wbportfolio/viewsets/configs/buttons/signals.py +68 -0
  381. wbportfolio/viewsets/configs/buttons/trade_proposals.py +25 -0
  382. wbportfolio/viewsets/configs/buttons/trades.py +144 -0
  383. wbportfolio/viewsets/configs/display/__init__.py +61 -0
  384. wbportfolio/viewsets/configs/display/adjustments.py +81 -0
  385. wbportfolio/viewsets/configs/display/assets.py +265 -0
  386. wbportfolio/viewsets/configs/display/claim.py +299 -0
  387. wbportfolio/viewsets/configs/display/custodians.py +24 -0
  388. wbportfolio/viewsets/configs/display/esg.py +88 -0
  389. wbportfolio/viewsets/configs/display/fees.py +133 -0
  390. wbportfolio/viewsets/configs/display/portfolio_cash_flow.py +103 -0
  391. wbportfolio/viewsets/configs/display/portfolio_relationship.py +38 -0
  392. wbportfolio/viewsets/configs/display/portfolios.py +125 -0
  393. wbportfolio/viewsets/configs/display/positions.py +75 -0
  394. wbportfolio/viewsets/configs/display/product_groups.py +54 -0
  395. wbportfolio/viewsets/configs/display/product_performance.py +241 -0
  396. wbportfolio/viewsets/configs/display/products.py +249 -0
  397. wbportfolio/viewsets/configs/display/reconciliations.py +151 -0
  398. wbportfolio/viewsets/configs/display/registers.py +71 -0
  399. wbportfolio/viewsets/configs/display/roles.py +49 -0
  400. wbportfolio/viewsets/configs/display/trade_proposals.py +97 -0
  401. wbportfolio/viewsets/configs/display/trades.py +359 -0
  402. wbportfolio/viewsets/configs/display/transactions.py +55 -0
  403. wbportfolio/viewsets/configs/endpoints/__init__.py +75 -0
  404. wbportfolio/viewsets/configs/endpoints/adjustments.py +17 -0
  405. wbportfolio/viewsets/configs/endpoints/assets.py +115 -0
  406. wbportfolio/viewsets/configs/endpoints/claim.py +106 -0
  407. wbportfolio/viewsets/configs/endpoints/custodians.py +6 -0
  408. wbportfolio/viewsets/configs/endpoints/esg.py +14 -0
  409. wbportfolio/viewsets/configs/endpoints/fees.py +26 -0
  410. wbportfolio/viewsets/configs/endpoints/portfolio_relationship.py +23 -0
  411. wbportfolio/viewsets/configs/endpoints/portfolios.py +43 -0
  412. wbportfolio/viewsets/configs/endpoints/positions.py +18 -0
  413. wbportfolio/viewsets/configs/endpoints/product_groups.py +11 -0
  414. wbportfolio/viewsets/configs/endpoints/product_performance.py +29 -0
  415. wbportfolio/viewsets/configs/endpoints/products.py +37 -0
  416. wbportfolio/viewsets/configs/endpoints/reconciliations.py +31 -0
  417. wbportfolio/viewsets/configs/endpoints/roles.py +9 -0
  418. wbportfolio/viewsets/configs/endpoints/trade_proposals.py +17 -0
  419. wbportfolio/viewsets/configs/endpoints/trades.py +82 -0
  420. wbportfolio/viewsets/configs/endpoints/transactions.py +17 -0
  421. wbportfolio/viewsets/configs/menu/__init__.py +30 -0
  422. wbportfolio/viewsets/configs/menu/adjustments.py +8 -0
  423. wbportfolio/viewsets/configs/menu/assets.py +8 -0
  424. wbportfolio/viewsets/configs/menu/claim.py +41 -0
  425. wbportfolio/viewsets/configs/menu/custodians.py +11 -0
  426. wbportfolio/viewsets/configs/menu/fees.py +13 -0
  427. wbportfolio/viewsets/configs/menu/instrument_prices.py +10 -0
  428. wbportfolio/viewsets/configs/menu/portfolio_cash_flow.py +8 -0
  429. wbportfolio/viewsets/configs/menu/portfolios.py +15 -0
  430. wbportfolio/viewsets/configs/menu/positions.py +14 -0
  431. wbportfolio/viewsets/configs/menu/product_groups.py +10 -0
  432. wbportfolio/viewsets/configs/menu/product_performance.py +25 -0
  433. wbportfolio/viewsets/configs/menu/products.py +15 -0
  434. wbportfolio/viewsets/configs/menu/reconciliations.py +7 -0
  435. wbportfolio/viewsets/configs/menu/registers.py +10 -0
  436. wbportfolio/viewsets/configs/menu/roles.py +16 -0
  437. wbportfolio/viewsets/configs/menu/trades.py +18 -0
  438. wbportfolio/viewsets/configs/menu/transactions.py +8 -0
  439. wbportfolio/viewsets/configs/previews/__init__.py +1 -0
  440. wbportfolio/viewsets/configs/previews/portfolios.py +21 -0
  441. wbportfolio/viewsets/configs/titles/__init__.py +65 -0
  442. wbportfolio/viewsets/configs/titles/adjustments.py +19 -0
  443. wbportfolio/viewsets/configs/titles/assets.py +57 -0
  444. wbportfolio/viewsets/configs/titles/assets_and_net_new_money_progression.py +6 -0
  445. wbportfolio/viewsets/configs/titles/claim.py +81 -0
  446. wbportfolio/viewsets/configs/titles/custodians.py +12 -0
  447. wbportfolio/viewsets/configs/titles/esg.py +10 -0
  448. wbportfolio/viewsets/configs/titles/fees.py +25 -0
  449. wbportfolio/viewsets/configs/titles/instrument_prices.py +20 -0
  450. wbportfolio/viewsets/configs/titles/portfolios.py +32 -0
  451. wbportfolio/viewsets/configs/titles/positions.py +11 -0
  452. wbportfolio/viewsets/configs/titles/product_groups.py +12 -0
  453. wbportfolio/viewsets/configs/titles/product_performance.py +16 -0
  454. wbportfolio/viewsets/configs/titles/products.py +6 -0
  455. wbportfolio/viewsets/configs/titles/registers.py +12 -0
  456. wbportfolio/viewsets/configs/titles/roles.py +23 -0
  457. wbportfolio/viewsets/configs/titles/trades.py +51 -0
  458. wbportfolio/viewsets/configs/titles/transactions.py +8 -0
  459. wbportfolio/viewsets/custodians.py +66 -0
  460. wbportfolio/viewsets/esg.py +165 -0
  461. wbportfolio/viewsets/mixins.py +48 -0
  462. wbportfolio/viewsets/portfolio_cash_flow.py +31 -0
  463. wbportfolio/viewsets/portfolio_cash_targets.py +8 -0
  464. wbportfolio/viewsets/portfolio_relationship.py +46 -0
  465. wbportfolio/viewsets/portfolio_swing_pricing.py +8 -0
  466. wbportfolio/viewsets/portfolios.py +154 -0
  467. wbportfolio/viewsets/positions.py +292 -0
  468. wbportfolio/viewsets/product_groups.py +84 -0
  469. wbportfolio/viewsets/product_performance.py +646 -0
  470. wbportfolio/viewsets/products.py +529 -0
  471. wbportfolio/viewsets/reconciliations.py +160 -0
  472. wbportfolio/viewsets/registers.py +75 -0
  473. wbportfolio/viewsets/roles.py +44 -0
  474. wbportfolio/viewsets/signals.py +42 -0
  475. wbportfolio/viewsets/synchronization.py +25 -0
  476. wbportfolio/viewsets/transactions/__init__.py +40 -0
  477. wbportfolio/viewsets/transactions/claim.py +933 -0
  478. wbportfolio/viewsets/transactions/fees.py +190 -0
  479. wbportfolio/viewsets/transactions/mixins.py +19 -0
  480. wbportfolio/viewsets/transactions/trade_proposals.py +93 -0
  481. wbportfolio/viewsets/transactions/trades.py +395 -0
  482. wbportfolio/viewsets/transactions/transactions.py +123 -0
  483. wbportfolio-2.2.1.dist-info/METADATA +21 -0
  484. wbportfolio-2.2.1.dist-info/RECORD +486 -0
  485. wbportfolio-2.2.1.dist-info/WHEEL +5 -0
  486. wbportfolio-2.2.1.dist-info/licenses/LICENSE +4 -0
@@ -0,0 +1,80 @@
1
+ from datetime import timedelta
2
+
3
+ import pytest
4
+ from faker import Faker
5
+ from pandas.tseries.offsets import BDay
6
+ from wbportfolio.models import Trade
7
+
8
+ fake = Faker()
9
+
10
+
11
+ @pytest.mark.django_db
12
+ class TestProductGroupModel:
13
+ def test_init(self, product_group):
14
+ assert product_group.id is not None
15
+
16
+ def test_get_total_fund_aum(
17
+ self,
18
+ portfolio_factory,
19
+ product_factory,
20
+ instrument_price_factory,
21
+ trade_factory,
22
+ product_group,
23
+ currency_fx_rates_factory,
24
+ weekday,
25
+ ):
26
+ p1 = product_factory.create(parent=product_group, inception_date=weekday)
27
+ p2 = product_factory.create(parent=product_group, inception_date=weekday)
28
+ portfolio1 = p1.portfolio
29
+ portfolio2 = p2.portfolio
30
+ shares1 = 1000
31
+ v1 = instrument_price_factory.create(date=weekday, instrument=p1, outstanding_shares=shares1)
32
+ c11 = trade_factory.create(
33
+ portfolio=portfolio1,
34
+ shares=shares1,
35
+ value_date=weekday,
36
+ underlying_instrument=p1,
37
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
38
+ )
39
+ fx_rate1 = p1.currency.fx_rates.get(date=v1.date)
40
+
41
+ c21 = trade_factory.create(
42
+ portfolio=portfolio2,
43
+ value_date=weekday,
44
+ underlying_instrument=p2,
45
+ transaction_subtype=Trade.Type.REDEMPTION,
46
+ )
47
+ v2 = instrument_price_factory.create(instrument=p2, date=weekday, outstanding_shares=c21.shares)
48
+ fx_rate2 = p2.currency.fx_rates.get(date=weekday)
49
+ p1.update_last_valuation_date()
50
+ p2.update_last_valuation_date()
51
+ assert float(product_group.get_total_fund_aum(weekday)) == pytest.approx(
52
+ float(v1.net_value) * float(c11.shares) / float(fx_rate1.value)
53
+ + float(v2.net_value) * float(c21.shares) / float(fx_rate2.value)
54
+ )
55
+ # test: get only trade at value_date
56
+ trade_factory.create(portfolio=portfolio1, transaction_date=weekday, value_date=weekday + timedelta(days=1))
57
+ assert float(product_group.get_total_fund_aum(weekday)) == pytest.approx(
58
+ float(v1.net_value) * float(c11.shares) / float(fx_rate1.value)
59
+ + float(v2.net_value) * float(c21.shares) / float(fx_rate2.value)
60
+ )
61
+
62
+ @pytest.mark.parametrize("val_date", [fake.date_object()])
63
+ def test_get_fund_product_table(self, product_group, product_factory, instrument_price_factory, val_date):
64
+ val_date = (val_date + BDay(1)).date()
65
+ p1 = product_factory.create(parent=product_group, inception_date=val_date)
66
+ p2 = product_factory.create(parent=product_group, inception_date=val_date)
67
+
68
+ v1 = instrument_price_factory.create(instrument=p1, date=val_date, calculated=False)
69
+ v2 = instrument_price_factory.create(instrument=p2, date=val_date, calculated=False)
70
+ df = product_group.get_fund_product_table(v2.date)
71
+ dfp1 = df[df["share class"] == p1.name]
72
+ assert dfp1["share class"].iloc[0] == p1.name
73
+ assert dfp1["launch date"].iloc[0] == f"{v1.date:%Y-%m-%d}"
74
+ assert dfp1["price"].iloc[0] == f"{v1.net_value:,.2f}"
75
+
76
+
77
+ @pytest.mark.django_db
78
+ class TestProductGroupRepresentantModel:
79
+ def test_init(self, product_group_representant):
80
+ assert product_group_representant.id is not None
@@ -0,0 +1,187 @@
1
+ import datetime
2
+ from decimal import Decimal
3
+
4
+ import pytest
5
+ from faker import Faker
6
+ from wbfdm.models import Instrument
7
+ from wbportfolio.models import Product, Trade
8
+
9
+ from .utils import PortfolioTestMixin
10
+
11
+ fake = Faker()
12
+
13
+
14
+ @pytest.mark.django_db
15
+ class TestProductModel(PortfolioTestMixin):
16
+ def test_init(self, product):
17
+ assert product.id is not None
18
+
19
+ def test_white_label(self, white_label_product):
20
+ assert white_label_product.white_label_product
21
+
22
+ def test_total_value(self, product_factory, trade_factory, instrument_price_factory):
23
+ product = product_factory.create()
24
+ t1 = trade_factory.create(
25
+ shares=100,
26
+ underlying_instrument=product,
27
+ transaction_date=datetime.date(2010, 1, 1) - datetime.timedelta(days=2),
28
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
29
+ )
30
+ t2 = trade_factory.create(
31
+ shares=50,
32
+ underlying_instrument=product,
33
+ transaction_date=datetime.date(2010, 1, 1) - datetime.timedelta(days=1),
34
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
35
+ )
36
+ t3 = trade_factory.create( # noise
37
+ shares=10,
38
+ underlying_instrument=product,
39
+ transaction_date=datetime.date(2010, 1, 1),
40
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
41
+ )
42
+ p1 = instrument_price_factory.create(
43
+ instrument=Instrument.objects.get(id=product.id), date=t1.transaction_date
44
+ )
45
+ p2 = instrument_price_factory.create(
46
+ instrument=Instrument.objects.get(id=product.id), date=t2.transaction_date
47
+ )
48
+ p3 = instrument_price_factory.create(
49
+ instrument=Instrument.objects.get(id=product.id), date=t3.transaction_date
50
+ )
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(
54
+ Decimal(p3.net_value * (t1.shares + t2.shares)), rel=Decimal("1e-4")
55
+ )
56
+
57
+ def test_total_shares_with_date(self, product_factory, trade_factory):
58
+ date1 = datetime.date(2010, 1, 1)
59
+ date2 = datetime.date(2010, 1, 2)
60
+ product = product_factory.create()
61
+ sub1 = trade_factory.create(
62
+ underlying_instrument=product,
63
+ transaction_date=date1 - datetime.timedelta(days=1),
64
+ value_date=date1,
65
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
66
+ )
67
+ sub2 = trade_factory.create(
68
+ underlying_instrument=product,
69
+ transaction_date=date2 - datetime.timedelta(days=1),
70
+ value_date=date2,
71
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
72
+ )
73
+ red1 = trade_factory.create(
74
+ underlying_instrument=product,
75
+ transaction_date=date1 - datetime.timedelta(days=1),
76
+ value_date=date1,
77
+ transaction_subtype=Trade.Type.REDEMPTION,
78
+ )
79
+ red2 = trade_factory.create(
80
+ underlying_instrument=product,
81
+ transaction_date=date2 - datetime.timedelta(days=1),
82
+ value_date=date2,
83
+ transaction_subtype=Trade.Type.REDEMPTION,
84
+ )
85
+ assert product.total_shares(date1 - datetime.timedelta(days=1)) == 0
86
+ assert product.total_shares(date1) == red1.shares + sub1.shares
87
+ assert product.total_shares(date2) == red1.shares + red2.shares + sub1.shares + sub2.shares
88
+
89
+ def test_nominal_value_with_date(self, product_factory, trade_factory):
90
+ product = product_factory.create()
91
+ trade_factory.create_batch(
92
+ 5,
93
+ shares=100,
94
+ underlying_instrument=product,
95
+ transaction_date=datetime.date(2010, 1, 1) - datetime.timedelta(days=1),
96
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
97
+ )
98
+ trade_factory.create_batch(
99
+ 5,
100
+ shares=100,
101
+ underlying_instrument=product,
102
+ transaction_date=datetime.date(2010, 1, 2) - datetime.timedelta(days=1),
103
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
104
+ )
105
+ assert product.nominal_value(datetime.date(2010, 1, 1)) == (500 * product.share_price)
106
+ assert product.nominal_value(datetime.date(2010, 1, 2)) == (1000 * product.share_price)
107
+
108
+ def test_get_title(self, product, user):
109
+ assert product.get_title()
110
+
111
+ def test_compute_str(self, product, user):
112
+ assert product.compute_str()
113
+
114
+ def test_urlify_title(self, product):
115
+ assert product.name.lower() in product.urlify_title
116
+
117
+ def test_get_products_internal_user(self, product, white_label_product, internal_user_factory):
118
+ assert set(Product.get_products(internal_user_factory.create().profile)) == {product, white_label_product}
119
+
120
+ def test_get_products_normal_user(self, product_factory, user, company):
121
+ public_product = product_factory.create()
122
+ assert set(Product.get_products(user.profile)) == {public_product}
123
+
124
+ white_label_product_profile = product_factory.create()
125
+ white_label_product_profile.white_label_customers.add(user.profile)
126
+
127
+ white_label_product_employer = product_factory.create()
128
+ user.profile.employers.add(company)
129
+ white_label_product_employer.white_label_customers.add(company)
130
+
131
+ assert set(Product.get_products(user.profile)) == {
132
+ white_label_product_employer,
133
+ white_label_product_profile,
134
+ public_product,
135
+ }
136
+
137
+ def test_subquery_is_white_label_product(self, product, white_label_product):
138
+ product_queryset = Product.objects.all().annotate(is_white_label=Product.subquery_is_white_label_product())
139
+ # for product in product_queryset:
140
+ # print(product, product.is_white_label)
141
+
142
+ # print(product_queryset.filter(is_white_label=True).count())
143
+
144
+ assert product_queryset.filter(is_white_label=True).count() == 1
145
+
146
+ def test_annotate_aum(
147
+ self, portfolio, product_factory, instrument_price_factory, trade_factory, currency_fx_rates_factory
148
+ ):
149
+ p1 = product_factory.create()
150
+ d = datetime.date(2021, 1, 1)
151
+ c1 = trade_factory.create(
152
+ portfolio=p1.portfolio,
153
+ underlying_instrument=p1,
154
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
155
+ value_date=d,
156
+ transaction_date=d,
157
+ )
158
+ v1 = instrument_price_factory.create(instrument=p1, date=d, outstanding_shares=c1.shares)
159
+ fx_rate = p1.currency.fx_rates.get(date=d)
160
+ qs = Product.annotate_last_aum(Product.objects.all(), val_date=d)
161
+ p1.update_last_valuation_date()
162
+ assert float(qs.first().assets_under_management_usd) == pytest.approx(
163
+ float(v1.net_value) * float(c1.shares) / float(fx_rate.value)
164
+ )
165
+
166
+ def test_account_merging(self, account_factory, product):
167
+ base_account = account_factory.create()
168
+ merged_account = account_factory.create()
169
+ product.default_account = merged_account
170
+ product.save()
171
+ base_account.merge(merged_account)
172
+ product.refresh_from_db()
173
+ assert product.default_account == base_account
174
+
175
+ def test_high_water_mark(self, product, instrument_price_factory):
176
+ initial_price = instrument_price_factory.create(instrument=product, net_value=100)
177
+ p1 = instrument_price_factory.create(instrument=product, net_value=200)
178
+ p2 = instrument_price_factory.create(instrument=product, net_value=100)
179
+ p3 = instrument_price_factory.create(instrument=product, net_value=300)
180
+ p4 = instrument_price_factory.create(instrument=product, net_value=100)
181
+ assert product.get_high_water_mark(p1.date) == initial_price.net_value
182
+ assert product.get_high_water_mark(p2.date) == p1.net_value
183
+ assert product.get_high_water_mark(p3.date) == p1.net_value
184
+ assert product.get_high_water_mark(p4.date) == p3.net_value
185
+ product.initial_high_water_mark = 400
186
+ product.save()
187
+ assert product.get_high_water_mark(p4.date) == 400
@@ -0,0 +1,82 @@
1
+ import pytest
2
+ from wbportfolio.factories import (
3
+ ManagerPortfolioRoleFactory,
4
+ ProductPortfolioRoleFactory,
5
+ )
6
+ from wbportfolio.models import PortfolioRole
7
+ from wbportfolio.permissions import is_analyst, is_manager, is_portfolio_manager
8
+
9
+
10
+ @pytest.mark.django_db
11
+ class TestPortfolioRoleModel:
12
+ def test_init_product_portfolio_role(self, product_portfolio_role):
13
+ assert product_portfolio_role.id is not None
14
+
15
+ def test_init_manager_portfolio_role(self, manager_portfolio_role):
16
+ assert manager_portfolio_role.id is not None
17
+
18
+ def test_str(self, product_portfolio_role):
19
+ assert (
20
+ str(product_portfolio_role)
21
+ == f"{product_portfolio_role.role_type} {product_portfolio_role.person.computed_str}"
22
+ )
23
+
24
+ # test is_manager
25
+
26
+ def test_is_manager_portfolio_role(self, user):
27
+ ManagerPortfolioRoleFactory.create(person=user.profile)
28
+ assert PortfolioRole.is_manager(user.profile)
29
+
30
+ def test_is_not_manager_portfolio_role(self, user):
31
+ assert not PortfolioRole.is_manager(user.profile)
32
+
33
+ def test_is_manager_portfolio_role_superuser(self, superuser):
34
+ superuser.is_superuser = True
35
+ superuser.save()
36
+ assert PortfolioRole.is_manager(superuser.profile)
37
+
38
+ # test is_portfolio_manager
39
+
40
+ def test_is_portfolio_manager_portfolio_role(self, user, product):
41
+ ProductPortfolioRoleFactory.create(
42
+ person=user.profile, instrument=product, role_type=PortfolioRole.RoleType.PORTFOLIO_MANAGER
43
+ )
44
+ assert PortfolioRole.is_portfolio_manager(user.profile, product)
45
+
46
+ def test_is_portfolio_manager_portfolio_role_manager(self, user, product):
47
+ ManagerPortfolioRoleFactory.create(person=user.profile)
48
+ assert PortfolioRole.is_portfolio_manager(user.profile, product)
49
+
50
+ def test_is_not_portfolio_manager_portfolio_role(self, user, product):
51
+ assert not PortfolioRole.is_portfolio_manager(user.profile, product)
52
+
53
+ def test_is_portfolio_manager_portfolio_role_superuser(self, superuser, product):
54
+ assert PortfolioRole.is_portfolio_manager(superuser.profile, product)
55
+
56
+ # test is_analyst
57
+
58
+ def test_is_analyst_portfolio_role(self, user, product):
59
+ ProductPortfolioRoleFactory.create(
60
+ person=user.profile, instrument=product, role_type=PortfolioRole.RoleType.ANALYST
61
+ )
62
+ assert PortfolioRole.is_analyst(user.profile, product)
63
+
64
+ def test_is_analyst_portfolio_role_manager(self, user, product):
65
+ ManagerPortfolioRoleFactory.create(person=user.profile)
66
+ assert PortfolioRole.is_analyst(user.profile, product)
67
+
68
+ def test_is_not_analyst_portfolio_role(self, user, product):
69
+ assert not PortfolioRole.is_analyst(user.profile, product)
70
+
71
+ def test_is_analyst_portfolio_role_superuser(self, superuser, product):
72
+ assert PortfolioRole.is_analyst(superuser.profile, product)
73
+
74
+ def test_permissions(self, user):
75
+ class Request:
76
+ def __init__(self, user):
77
+ self.user = user
78
+
79
+ request = Request(user)
80
+ assert is_manager(request) == PortfolioRole.is_manager(user.profile)
81
+ assert is_analyst(request) == PortfolioRole.is_analyst(user.profile)
82
+ assert is_portfolio_manager(request) == PortfolioRole.is_portfolio_manager(user.profile)
@@ -0,0 +1,233 @@
1
+ from decimal import Decimal
2
+ from unittest.mock import patch
3
+
4
+ import pytest
5
+ from faker import Faker
6
+ from pandas.tseries.offsets import BDay
7
+ from wbportfolio.factories import AdjustmentFactory
8
+ from wbportfolio.models import Adjustment, PortfolioRole
9
+ from wbportfolio.models.adjustments import post_adjustment_on_prices
10
+
11
+ fake = Faker()
12
+
13
+
14
+ @pytest.mark.django_db
15
+ class TestAdjustmentModel:
16
+ @pytest.fixture()
17
+ def applied_adjustment(self):
18
+ return AdjustmentFactory.create(status=Adjustment.Status.APPLIED)
19
+
20
+ @pytest.fixture()
21
+ def old_adjustment(self):
22
+ return AdjustmentFactory.create(status=Adjustment.Status.PENDING, date=fake.past_date())
23
+
24
+ # Test utilities function
25
+ @patch("wbportfolio.models.adjustments.chain")
26
+ def test_adjustment_creation(
27
+ self,
28
+ mock_fct,
29
+ user_factory,
30
+ adjustment_factory,
31
+ product_portfolio_role_factory,
32
+ asset_position_factory,
33
+ instrument,
34
+ ):
35
+ user_factory.create() # User without permission
36
+ user_porftolio_manager = user_factory.create()
37
+ product_portfolio_role_factory.create(
38
+ person=user_porftolio_manager.profile, role_type=PortfolioRole.RoleType.PORTFOLIO_MANAGER
39
+ )
40
+ asset_position_factory.create(underlying_instrument=instrument)
41
+ adjustment2 = adjustment_factory.create(instrument=instrument, date=fake.past_date())
42
+ assert mock_fct.call_count == 1
43
+ assert adjustment2.status == Adjustment.Status.PENDING
44
+
45
+ # If newly created instrument's adjustment happens in the future, its state becomes pending and an import task is triggered
46
+ adjustment1 = adjustment_factory.create(instrument=instrument)
47
+ assert adjustment1.status == Adjustment.Status.PENDING
48
+ assert mock_fct.call_count == 2
49
+
50
+ def test_cumulative_factor(self, adjustment_factory):
51
+ """
52
+ s1 -> s2 -> s3
53
+ """
54
+ s3 = adjustment_factory.create(status=Adjustment.Status.APPLIED)
55
+ s3.refresh_from_db()
56
+ assert s3.cumulative_factor == Decimal(1.0)
57
+
58
+ s2 = adjustment_factory.create(
59
+ status=Adjustment.Status.APPLIED, instrument=s3.instrument, date=s3.date - BDay(1)
60
+ )
61
+ s1 = adjustment_factory.create(
62
+ instrument=s3.instrument, date=s3.date - BDay(2)
63
+ ) # This adjustment is not applied and shouldn't be accounted for in the cumulative factor of the futures adjustments
64
+ s3.refresh_from_db()
65
+ s2.refresh_from_db()
66
+ s1.refresh_from_db()
67
+ assert s2.cumulative_factor == pytest.approx(
68
+ s3.factor, rel=Decimal(1e-3)
69
+ ) # Normal case, applied adjustment cumulative factor should reflect the parent factors
70
+ assert s1.cumulative_factor == pytest.approx(
71
+ s2.factor * s3.factor, rel=Decimal(1e-3)
72
+ ) # even if not applied, cumulative factor is computed normally
73
+
74
+ def test_apply_adjustment_on_assets(self, adjustment, asset_position_factory):
75
+ a1 = asset_position_factory.create(
76
+ underlying_instrument=adjustment.instrument, date=adjustment.adjustment_date
77
+ )
78
+ a2 = asset_position_factory.create(underlying_instrument=adjustment.instrument, date=adjustment.date)
79
+ adjustment.apply_adjustment_on_assets()
80
+ a1.refresh_from_db()
81
+ a2.refresh_from_db()
82
+ assert a1.applied_adjustment == adjustment
83
+ assert not a2.applied_adjustment
84
+
85
+ def test_future_adjustment_not_applied_on_assets(self, adjustment, asset_position_factory):
86
+ p = asset_position_factory.create(
87
+ underlying_instrument=adjustment.instrument, date=(adjustment.date + BDay(1)).date()
88
+ )
89
+
90
+ adjustment.apply_adjustment_on_assets()
91
+ p.refresh_from_db()
92
+ assert not p.applied_adjustment
93
+
94
+ def test_adjustment_played_out_of_order(self, adjustment_factory, asset_position_factory):
95
+ s1 = adjustment_factory.create()
96
+ s2 = adjustment_factory.create(instrument=s1.instrument, date=(s1.date + BDay(2)).date())
97
+
98
+ a0 = asset_position_factory.create(underlying_instrument=s1.instrument, date=s2.adjustment_date - BDay(3))
99
+ a1 = asset_position_factory.create(
100
+ underlying_instrument=s1.instrument, date=s2.adjustment_date - BDay(2), applied_adjustment=s2
101
+ )
102
+ a2 = asset_position_factory.create(
103
+ underlying_instrument=s1.instrument, date=s2.adjustment_date - BDay(1), applied_adjustment=s2
104
+ )
105
+ a3 = asset_position_factory.create(
106
+ underlying_instrument=s1.instrument, date=s2.adjustment_date, applied_adjustment=s2
107
+ )
108
+
109
+ s1.apply_adjustment_on_assets()
110
+ a0.refresh_from_db()
111
+ a1.refresh_from_db()
112
+ a2.refresh_from_db()
113
+ a3.refresh_from_db()
114
+
115
+ assert a0.applied_adjustment == s1
116
+ assert a1.applied_adjustment == s1
117
+ assert a2.applied_adjustment == s2
118
+ assert a3.applied_adjustment == s2
119
+
120
+ # Test Basic FSM workflow
121
+ @patch("wbportfolio.models.adjustments.apply_adjustment_on_assets_as_task.delay")
122
+ def test_apply(self, mock_fct, old_adjustment):
123
+ assert mock_fct.call_count == 0
124
+ old_adjustment.apply()
125
+ old_adjustment.save()
126
+ assert old_adjustment.status == Adjustment.Status.APPLIED
127
+ mock_fct.assert_called()
128
+
129
+ def test_deny(self, old_adjustment, user):
130
+ assert old_adjustment.status == Adjustment.Status.PENDING
131
+ old_adjustment.deny(by=user)
132
+ old_adjustment.save()
133
+ assert old_adjustment.last_handler == user.profile
134
+ assert old_adjustment.status == Adjustment.Status.DENIED
135
+
136
+ @patch("wbportfolio.models.adjustments.revert_adjustment_on_assets_as_task.delay")
137
+ def test_revert(self, mock_fct, adjustment_factory):
138
+ adjustment = adjustment_factory.create(status=Adjustment.Status.APPLIED)
139
+ assert mock_fct.call_count == 0
140
+ adjustment.revert()
141
+ adjustment.save()
142
+ assert adjustment.status == Adjustment.Status.PENDING
143
+ mock_fct.assert_called_with(adjustment.id)
144
+
145
+ def test_revert_adjustment_on_assets(self, adjustment_factory, asset_position_factory):
146
+ s1 = adjustment_factory.create(status=Adjustment.Status.APPLIED)
147
+ s2 = adjustment_factory.create(
148
+ date=s1.date + BDay(1), instrument=s1.instrument, status=Adjustment.Status.APPLIED
149
+ )
150
+ s3 = adjustment_factory.create(
151
+ date=s2.date + BDay(1), instrument=s1.instrument, status=Adjustment.Status.APPLIED
152
+ )
153
+
154
+ a1 = asset_position_factory.create(
155
+ underlying_instrument=s1.instrument, date=s1.adjustment_date, applied_adjustment=s1
156
+ )
157
+ a2 = asset_position_factory.create(
158
+ underlying_instrument=s1.instrument, date=s2.adjustment_date, applied_adjustment=s2
159
+ )
160
+ a3 = asset_position_factory.create(
161
+ underlying_instrument=s1.instrument, date=s3.adjustment_date, applied_adjustment=s3
162
+ )
163
+
164
+ s1.revert_adjustment_on_assets()
165
+ a1.refresh_from_db()
166
+ a2.refresh_from_db()
167
+ a3.refresh_from_db()
168
+ assert a1.applied_adjustment == s2
169
+ assert a2.applied_adjustment == s2
170
+ assert a3.applied_adjustment == s3
171
+
172
+ s2.revert_adjustment_on_assets()
173
+ a1.refresh_from_db()
174
+ a2.refresh_from_db()
175
+ a3.refresh_from_db()
176
+ assert a1.applied_adjustment == s3
177
+ assert a2.applied_adjustment == s3
178
+ assert a3.applied_adjustment == s3
179
+
180
+ s3.revert_adjustment_on_assets()
181
+ a1.refresh_from_db()
182
+ a2.refresh_from_db()
183
+ a3.refresh_from_db()
184
+ assert not a1.applied_adjustment
185
+ assert not a2.applied_adjustment
186
+ assert not a3.applied_adjustment
187
+
188
+ def test_post_adjustment_on_prices_without_assets(self, adjustment):
189
+ post_adjustment_on_prices(adjustment.id)
190
+ adjustment.refresh_from_db()
191
+ assert adjustment.status == Adjustment.Status.APPLIED
192
+
193
+ @patch.object(Adjustment, "automatically_applied_adjustments_on_assets")
194
+ def test_post_adjustment_automatically_approve_on_prices_with_assets(
195
+ self,
196
+ mock_check_fct,
197
+ adjustment,
198
+ asset_position_factory,
199
+ ):
200
+ a1 = asset_position_factory.create(
201
+ underlying_instrument=adjustment.instrument, date=adjustment.adjustment_date
202
+ )
203
+ mock_check_fct.return_value = True
204
+ post_adjustment_on_prices(adjustment.id)
205
+ a1.refresh_from_db()
206
+ adjustment.refresh_from_db()
207
+ a1.applied_adjustment == adjustment
208
+ assert adjustment.status == Adjustment.Status.APPLIED
209
+
210
+ @patch("wbportfolio.models.adjustments.send_notification")
211
+ @patch.object(Adjustment, "automatically_applied_adjustments_on_assets")
212
+ def test_post_adjustment_not_automatically_approve_on_prices_with_assets(
213
+ self,
214
+ mock_check_fct,
215
+ mock_delay_fct,
216
+ adjustment,
217
+ asset_position_factory,
218
+ user_factory,
219
+ product_portfolio_role_factory,
220
+ ):
221
+ asset_position_factory.create(
222
+ underlying_instrument=adjustment.instrument, date=(adjustment.date - BDay(1)).date()
223
+ )
224
+ user_factory.create() # User without permission
225
+ user_porftolio_manager = user_factory.create()
226
+ product_portfolio_role_factory.create(
227
+ person=user_porftolio_manager.profile, role_type=PortfolioRole.RoleType.PORTFOLIO_MANAGER
228
+ )
229
+ mock_check_fct.return_value = False
230
+ post_adjustment_on_prices(adjustment.id)
231
+ adjustment.refresh_from_db()
232
+ mock_delay_fct.call_args[0] == user_porftolio_manager.id
233
+ assert adjustment.status == Adjustment.Status.PENDING