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,129 @@
1
+ import random
2
+ from datetime import timedelta
3
+ from decimal import Decimal
4
+
5
+ import pytest
6
+ from django.contrib.auth import get_user_model
7
+ from django.contrib.auth.models import Permission
8
+ from faker import Faker
9
+ from wbportfolio.models import Claim, Trade
10
+
11
+ fake = Faker()
12
+
13
+
14
+ @pytest.mark.django_db
15
+ class TestClaimModel:
16
+ def test_init(self, claim):
17
+ assert claim.id is not None
18
+
19
+ def test_filter_claims_for_user(self, user, claim_factory, account_factory, account_role_factory):
20
+ from wbcore.permissions.registry import user_registry
21
+
22
+ no_account_claims = claim_factory.create(account=None)
23
+ private_claim = claim_factory.create(account=account_factory.create(is_public=False))
24
+ public_claim = claim_factory.create(account=account_role_factory.create(entry=user.profile.entry_ptr).account)
25
+ # if normal user, can only see its own claim, with an attached account
26
+ assert set(Claim.objects.filter_for_user(user)) == {public_claim}
27
+ user.user_permissions.add(
28
+ Permission.objects.get(content_type__app_label="authentication", codename="is_internal_user")
29
+ )
30
+ user_registry.reset_cache()
31
+ user = get_user_model().objects.get(
32
+ id=user.id
33
+ ) # we refetch user to clear perm cache, doesn't happen with refresh_from_db
34
+ # if insternal user, they can also see claim without account
35
+ assert set(Claim.objects.filter_for_user(user)) == {no_account_claims, public_claim}
36
+ user.user_permissions.add(
37
+ Permission.objects.get(content_type__app_label="wbcrm", codename="administrate_account")
38
+ )
39
+ user = get_user_model().objects.get(
40
+ id=user.id
41
+ ) # we refetch user to clear perm cache, doesn't happen with refresh_from_db
42
+ # administrator can see everything
43
+ assert set(Claim.objects.filter_for_user(user)) == {no_account_claims, public_claim, private_claim}
44
+
45
+ def test_account_merging(self, account_factory, claim):
46
+ base_account = account_factory.create()
47
+ merged_account = claim.account
48
+ assert base_account.reference_id != merged_account.reference_id
49
+ base_account.merge(merged_account)
50
+ claim.refresh_from_db()
51
+ assert claim.account == base_account
52
+ assert claim.reference == str(merged_account.reference_id)
53
+
54
+ @pytest.mark.parametrize(
55
+ "shares,unvalid_status",
56
+ [
57
+ (
58
+ Decimal(50),
59
+ random.choice(
60
+ [Claim.Status.WITHDRAWN, Claim.Status.AUTO_MATCHED, Claim.Status.PENDING, Claim.Status.APPROVED]
61
+ ),
62
+ )
63
+ ],
64
+ )
65
+ def test_auto_match_on_claim_save(
66
+ self, weekday, shares, unvalid_status, product, claim_factory, customer_trade_factory
67
+ ):
68
+ valid_trade = customer_trade_factory.create(
69
+ underlying_instrument=product,
70
+ portfolio=product.primary_portfolio,
71
+ transaction_date=fake.date_between(
72
+ weekday - timedelta(days=Trade.TRADE_WINDOW_INTERVAL),
73
+ weekday + timedelta(days=Trade.TRADE_WINDOW_INTERVAL),
74
+ ),
75
+ shares=fake.pydecimal(min_value=int(shares - 1), max_value=int(shares + 1)),
76
+ )
77
+ claim = claim_factory.create(product=product, date=weekday, shares=shares, trade=None, status=unvalid_status)
78
+ assert claim.status == unvalid_status
79
+ assert claim.trade is None
80
+
81
+ claim = claim_factory.create(
82
+ product=product, date=weekday, shares=shares, trade=None, status=Claim.Status.DRAFT
83
+ )
84
+ assert claim.status == Claim.Status.AUTO_MATCHED
85
+ assert claim.trade == valid_trade
86
+
87
+ @pytest.mark.parametrize(
88
+ "shares,unvalid_status",
89
+ [
90
+ (
91
+ Decimal(50),
92
+ random.choice(
93
+ [Claim.Status.WITHDRAWN, Claim.Status.AUTO_MATCHED, Claim.Status.PENDING, Claim.Status.APPROVED]
94
+ ),
95
+ )
96
+ ],
97
+ )
98
+ def test_claim_auto_match_on_trade_creation(
99
+ self, weekday, shares, unvalid_status, product, claim_factory, customer_trade_factory
100
+ ):
101
+ claim = claim_factory.create(product=product, date=weekday, shares=shares, trade=None, status=unvalid_status)
102
+ trade = customer_trade_factory.create(
103
+ underlying_instrument=product,
104
+ portfolio=product.primary_portfolio,
105
+ transaction_date=fake.date_between(
106
+ weekday - timedelta(days=Trade.TRADE_WINDOW_INTERVAL),
107
+ weekday + timedelta(days=Trade.TRADE_WINDOW_INTERVAL),
108
+ ),
109
+ shares=fake.pydecimal(min_value=int(shares - 1), max_value=int(shares + 1)),
110
+ )
111
+ assert claim.status == unvalid_status
112
+ assert claim.trade is None
113
+
114
+ # we delete the trade and set the claim status to draft, where we expect a newly created trade to be automatch
115
+ trade.delete()
116
+ claim.status = Claim.Status.DRAFT
117
+ claim.save()
118
+ trade = customer_trade_factory.create(
119
+ underlying_instrument=product,
120
+ portfolio=product.primary_portfolio,
121
+ transaction_date=fake.date_between(
122
+ weekday - timedelta(days=Trade.TRADE_WINDOW_INTERVAL),
123
+ weekday + timedelta(days=Trade.TRADE_WINDOW_INTERVAL),
124
+ ),
125
+ shares=fake.pydecimal(min_value=int(shares - 1), max_value=int(shares + 1)),
126
+ )
127
+ claim.refresh_from_db()
128
+ assert claim.status == Claim.Status.AUTO_MATCHED
129
+ assert claim.trade == trade
@@ -0,0 +1,65 @@
1
+ from datetime import timedelta
2
+ from decimal import Decimal
3
+
4
+ import pytest
5
+ from wbfdm.factories import CashFactory
6
+ from wbfdm.models import InstrumentPrice
7
+ from wbportfolio.models import FeeCalculation, Fees, Product
8
+
9
+
10
+ def fees_calculation(price_id):
11
+ price = InstrumentPrice.objects.get(id=price_id)
12
+ product = Product.objects.get(id=price.instrument.id)
13
+ cash = CashFactory.create(currency=product.currency)
14
+ yield {
15
+ "portfolio": product.primary_portfolio,
16
+ "linked_product": product,
17
+ "transaction_date": price.date,
18
+ "transaction_subtype": Fees.Type.MANAGEMENT,
19
+ "underlying_instrument": cash,
20
+ "currency": product.currency,
21
+ "calculated": True,
22
+ "total_value": price.net_value,
23
+ "total_value_fx_portfolio": price.net_value,
24
+ "total_value_gross": price.net_value,
25
+ "total_value_gross_fx_portfolio": price.net_value,
26
+ }
27
+
28
+
29
+ @pytest.mark.django_db
30
+ class TestFeesModel:
31
+ def test_init(self, fees):
32
+ assert fees.id is not None
33
+
34
+ def test_filter_only_valid_fees(self, fees_factory):
35
+ fees_d0 = (
36
+ fees_factory.create()
37
+ ) # no matter if estimated or not, we expect this fee to be on the resulting queryset
38
+ calculated_fees_d1 = fees_factory.create( # there will be a real fee for that date, type and product, so this will be filtered out
39
+ calculated=True,
40
+ linked_product=fees_d0.linked_product,
41
+ transaction_date=(fees_d0.transaction_date + timedelta(days=1)),
42
+ transaction_subtype=fees_d0.transaction_subtype,
43
+ )
44
+ real_fees_d1 = fees_factory.create(
45
+ calculated=False,
46
+ linked_product=calculated_fees_d1.linked_product,
47
+ transaction_date=calculated_fees_d1.transaction_date,
48
+ transaction_subtype=calculated_fees_d1.transaction_subtype,
49
+ )
50
+
51
+ assert set(Fees.valid_objects.all()) == {fees_d0, real_fees_d1}
52
+ assert set(Fees.objects.filter_only_valid_fees()) == {fees_d0, real_fees_d1}
53
+
54
+ def test_compute_fee_from_price(self, product_factory, instrument_price_factory):
55
+ # we check the mechanism of computing fees on valuation creation
56
+ fee_calculation = FeeCalculation.objects.create(
57
+ name="base computation", import_path="wbportfolio.tests.models.transactions.test_fees"
58
+ )
59
+ product = product_factory.create(fee_calculation=fee_calculation)
60
+ price = instrument_price_factory.create(instrument=product) # post save must be called to compute fees
61
+
62
+ fees = Fees.objects.get(
63
+ linked_product=product, fee_date=price.date, calculated=True, transaction_subtype=Fees.Type.MANAGEMENT
64
+ )
65
+ assert fees.total_value == pytest.approx(price.net_value, rel=Decimal(1e-4))
@@ -0,0 +1,204 @@
1
+ from datetime import timedelta
2
+ from decimal import Decimal
3
+
4
+ import pytest
5
+ from faker import Faker
6
+ from wbportfolio.models import Product, Trade
7
+
8
+ fake = Faker()
9
+
10
+
11
+ @pytest.mark.django_db
12
+ class TestTradeModel:
13
+ def test_init(self, trade):
14
+ assert trade.id is not None
15
+
16
+ def test_str(self, product, trade_factory):
17
+ trade = trade_factory.create(underlying_instrument=product)
18
+ assert str(trade) == f"{trade.product.ticker}:{trade.shares} ({trade.bank})"
19
+
20
+ def test_save_with_price(self, trade_factory):
21
+ trade = trade_factory(price=150)
22
+ assert trade.price == 150
23
+
24
+ def test_subquery_net_money(self, trade_factory, instrument_price_factory, product):
25
+ v = instrument_price_factory.create(instrument=product)
26
+ c2 = trade_factory.create(
27
+ underlying_instrument=product,
28
+ transaction_date=v.date - timedelta(days=1),
29
+ shares=-1,
30
+ transaction_subtype=Trade.Type.REDEMPTION,
31
+ )
32
+ c1 = trade_factory.create(
33
+ underlying_instrument=product,
34
+ transaction_date=v.date - timedelta(days=1),
35
+ shares=1,
36
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
37
+ )
38
+ trade_factory.create( # noise
39
+ underlying_instrument=product,
40
+ transaction_date=v.date,
41
+ shares=1,
42
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
43
+ )
44
+ products_pos = Product.objects.annotate(value=Trade.subquery_net_money(v.date, v.date))
45
+ assert products_pos.first().value == pytest.approx(c2.shares * c2.price + c1.shares * c1.price)
46
+
47
+ products_pos = Product.objects.annotate(value=Trade.subquery_net_money(v.date, v.date, only_positive=True))
48
+ assert products_pos.first().value == pytest.approx(c1.shares * c1.price)
49
+
50
+ products_neg = Product.objects.annotate(value=Trade.subquery_net_money(v.date, v.date, only_negative=True))
51
+ assert products_neg.first().value == pytest.approx(c2.shares * c2.price)
52
+
53
+ @pytest.mark.parametrize("date", [(fake.date_object())])
54
+ def test_subquery_shares_per_product(self, trade_factory, product, date):
55
+ a1 = trade_factory.create(
56
+ underlying_instrument=product,
57
+ transaction_date=date - timedelta(days=1),
58
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
59
+ )
60
+ trade_factory.create( # noise
61
+ underlying_instrument=product, transaction_date=date, transaction_subtype=Trade.Type.SUBSCRIPTION
62
+ )
63
+ qs = Product.objects.annotate(total_shares=Trade.subquery_shares_per_underlying_instrument(date))
64
+ assert qs.first().total_shares == a1.shares
65
+
66
+ @pytest.mark.parametrize("date", [(fake.date_object())])
67
+ def test_subquery_shares_per_product_per_date(self, trade_factory, product, date):
68
+ a1 = trade_factory.create(
69
+ underlying_instrument=product,
70
+ transaction_date=date - timedelta(days=1),
71
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
72
+ )
73
+ trade_factory.create( # noise
74
+ underlying_instrument=product, transaction_date=date, transaction_subtype=Trade.Type.SUBSCRIPTION
75
+ )
76
+ qs = Product.objects.annotate(total_shares=Trade.subquery_shares_per_underlying_instrument(date))
77
+ assert qs.first().total_shares == a1.shares
78
+
79
+ @pytest.mark.parametrize("date", [(fake.date_object())])
80
+ def test_subquery_net_new_money(self, trade_factory, product, date):
81
+ a1 = trade_factory.create(
82
+ underlying_instrument=product,
83
+ transaction_date=date - timedelta(days=1),
84
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
85
+ )
86
+ trade_factory.create( # noise
87
+ underlying_instrument=product,
88
+ transaction_date=date,
89
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
90
+ )
91
+ qs = Product.objects.annotate(total_shares=Trade.subquery_shares_per_underlying_instrument(date))
92
+ assert qs.first().total_shares == a1.shares
93
+
94
+ def test_claim_update_claimed_shares(self, customer_trade_factory, claim_factory):
95
+ customer_trade = customer_trade_factory.create()
96
+ claim_factory.create(status="PENDING", trade=customer_trade)
97
+ customer_trade.refresh_from_db()
98
+ assert customer_trade.claimed_shares == 0
99
+ c2 = claim_factory.create(status="APPROVED", trade=customer_trade)
100
+ customer_trade.refresh_from_db()
101
+ assert customer_trade.claimed_shares == c2.shares
102
+
103
+
104
+ @pytest.mark.django_db
105
+ class TestTradeProposalModel:
106
+ def test_init(self, trade_proposal):
107
+ assert trade_proposal.id is not None
108
+
109
+
110
+ @pytest.mark.django_db
111
+ class TestTradeInstrumentPrice:
112
+ def test_shares(self, portfolio, product_factory, trade_factory, instrument_price_factory):
113
+ product = product_factory.create()
114
+ price = instrument_price_factory.create(instrument=product)
115
+ trade1 = trade_factory.create(
116
+ portfolio=product.primary_portfolio,
117
+ underlying_instrument=product,
118
+ transaction_date=price.date - timedelta(days=1),
119
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
120
+ )
121
+ trade2 = trade_factory.create(
122
+ portfolio=product.primary_portfolio,
123
+ underlying_instrument=product,
124
+ transaction_date=price.date - timedelta(days=1),
125
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
126
+ )
127
+ trade_factory.create( # noise
128
+ portfolio=product.primary_portfolio,
129
+ underlying_instrument=product,
130
+ transaction_date=price.date,
131
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
132
+ )
133
+ assert price.shares == pytest.approx(trade1.shares + trade2.shares)
134
+
135
+ def test_nominal_value(self, portfolio, product_factory, trade_factory, instrument_price_factory):
136
+ product = product_factory.create()
137
+ price = instrument_price_factory.create(instrument=product)
138
+ trade1 = trade_factory.create(
139
+ portfolio=product.primary_portfolio,
140
+ underlying_instrument=product,
141
+ transaction_date=price.date - timedelta(days=1),
142
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
143
+ )
144
+ trade2 = trade_factory.create(
145
+ portfolio=product.primary_portfolio,
146
+ underlying_instrument=product,
147
+ transaction_date=price.date - timedelta(days=1),
148
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
149
+ )
150
+ trade_factory.create( # noise
151
+ portfolio=product.primary_portfolio,
152
+ underlying_instrument=product,
153
+ transaction_date=price.date,
154
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
155
+ )
156
+ assert price.nominal_value == pytest.approx(Decimal(product.share_price * (trade1.shares + trade2.shares)))
157
+
158
+ def test_subscription_link_to_internal_trade(self, weekday, trade_factory, product):
159
+ shares = 100
160
+ trade_factory.create(shares=shares, transaction_subtype=Trade.Type.BUY, transaction_date=weekday) # noise 1
161
+ trade_factory.create(
162
+ shares=shares,
163
+ transaction_subtype=Trade.Type.BUY,
164
+ transaction_date=weekday + timedelta(days=Trade.TRADE_WINDOW_INTERVAL + 1),
165
+ underlying_instrument=product,
166
+ ) # noise 2
167
+ subscription_trade1 = trade_factory.create(
168
+ shares=shares,
169
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
170
+ transaction_date=weekday,
171
+ underlying_instrument=product,
172
+ ) # noise 3
173
+ internal_trade = trade_factory.create(
174
+ shares=shares, transaction_subtype=Trade.Type.BUY, transaction_date=weekday, underlying_instrument=product
175
+ )
176
+ subscription_trade2 = trade_factory.create(
177
+ shares=shares,
178
+ transaction_subtype=Trade.Type.SUBSCRIPTION,
179
+ transaction_date=weekday,
180
+ underlying_instrument=product,
181
+ )
182
+
183
+ subscription_trade1.link_to_internal_trade()
184
+ assert subscription_trade1.internal_trade == internal_trade
185
+ assert subscription_trade1.marked_as_internal is True
186
+
187
+ # Reset attribute to test the other side
188
+ subscription_trade1.internal_trade = None
189
+ subscription_trade1.marked_as_internal = False
190
+ subscription_trade1.save()
191
+
192
+ # Check that linking from the other site but with two similar subscriptions won't link
193
+ internal_trade.link_to_internal_trade()
194
+ subscription_trade1.refresh_from_db()
195
+ subscription_trade2.refresh_from_db()
196
+ assert subscription_trade1.internal_trade is None
197
+ assert subscription_trade2.internal_trade is None
198
+
199
+ # Check that with now only one subscription, the linking is successful
200
+ subscription_trade2.delete()
201
+ internal_trade.link_to_internal_trade()
202
+ subscription_trade1.refresh_from_db()
203
+ assert subscription_trade1.internal_trade == internal_trade
204
+ assert subscription_trade1.marked_as_internal is True
@@ -0,0 +1,13 @@
1
+ from datetime import timedelta
2
+
3
+ import pytest
4
+ from faker import Faker
5
+ from wbportfolio.factories import ProductFactory
6
+
7
+ fake = Faker()
8
+
9
+
10
+ class PortfolioTestMixin:
11
+ @pytest.fixture
12
+ def active_product(self, weekday):
13
+ return ProductFactory.create(inception_date=weekday - timedelta(days=30), delisted_date=None)
File without changes
@@ -0,0 +1,21 @@
1
+ import pytest
2
+ from wbportfolio.serializers import ClaimModelSerializer
3
+
4
+
5
+ @pytest.mark.django_db
6
+ class TestClaimModelSerializer:
7
+ def test_serialize(self, claim):
8
+ claim.trade_type = True
9
+ claim.quantity = 1000
10
+ serializer = ClaimModelSerializer(claim)
11
+ assert serializer.data
12
+
13
+ def test_validate(self):
14
+ data = {
15
+ "trade_type": True,
16
+ "quantity": 1000,
17
+ "as_shares": True,
18
+ "date": "2024-01-01",
19
+ }
20
+ serializer = ClaimModelSerializer(data=data)
21
+ assert serializer.is_valid()
@@ -0,0 +1,151 @@
1
+ from django.dispatch import receiver
2
+ from wbcore.contrib.currency.factories import CurrencyFXRatesFactory
3
+ from wbcore.contrib.directory.factories import CompanyFactory
4
+ from wbcore.test.signals import custom_update_kwargs, get_custom_factory
5
+ from wbportfolio.factories import (
6
+ CustomerTradeFactory,
7
+ InstrumentPriceFactory,
8
+ ModelPortfolioFactory,
9
+ ProductFactory,
10
+ ProductPortfolioRoleFactory,
11
+ TradeProposalFactory,
12
+ )
13
+ from wbportfolio.viewsets import (
14
+ AssetPositionInstrumentModelViewSet,
15
+ AssetPositionUnderlyingInstrumentChartViewSet,
16
+ AUMProductChartView,
17
+ ClaimEntryModelViewSet,
18
+ CustodianDistributionInstrumentChartViewSet,
19
+ CustomerDistributionInstrumentChartViewSet,
20
+ DistributionChartViewSet,
21
+ DistributionTableViewSet,
22
+ ModelPortfolioModelViewSet,
23
+ NominalProductChartView,
24
+ PortfolioRoleInstrumentModelViewSet,
25
+ ProductCustomerModelViewSet,
26
+ ProductPerformanceFeesModelViewSet,
27
+ SubscriptionRedemptionInstrumentModelViewSet,
28
+ SubscriptionRedemptionModelViewSet,
29
+ TradeInstrumentModelViewSet,
30
+ TradeProposalPortfolioModelViewSet,
31
+ TradeTradeProposalModelViewSet,
32
+ )
33
+
34
+ # =================================================================================================================
35
+ # CUSTOM FACTORY
36
+ # =================================================================================================================
37
+
38
+
39
+ @receiver(get_custom_factory, sender=SubscriptionRedemptionModelViewSet)
40
+ @receiver(get_custom_factory, sender=SubscriptionRedemptionInstrumentModelViewSet)
41
+ def receive_factory_subscription_redemption_instrument(sender, *args, **kwargs):
42
+ return CustomerTradeFactory
43
+
44
+
45
+ @receiver(get_custom_factory, sender=ModelPortfolioModelViewSet)
46
+ def receive_factory_model_portfolio(sender, *args, **kwargs):
47
+ return ModelPortfolioFactory
48
+
49
+
50
+ @receiver(get_custom_factory, sender=PortfolioRoleInstrumentModelViewSet)
51
+ def receive_factory_product_portfolio_role(sender, *args, **kwargs):
52
+ return ProductPortfolioRoleFactory
53
+
54
+
55
+ # =================================================================================================================
56
+ # UPDATE DATA
57
+ # =================================================================================================================
58
+
59
+
60
+ # =================================================================================================================
61
+ # UPDATE KWARGS
62
+ # =================================================================================================================
63
+ @receiver(custom_update_kwargs, sender=TradeTradeProposalModelViewSet)
64
+ def receive_kwargs_trade_trade_proposal_product(sender, *args, **kwargs):
65
+ if obj := kwargs.get("obj_factory"):
66
+ trade_proposal = TradeProposalFactory.create()
67
+ obj.trade_proposal = trade_proposal
68
+ obj.save()
69
+ return {"trade_proposal_id": trade_proposal.id}
70
+ return {}
71
+
72
+
73
+ @receiver(custom_update_kwargs, sender=TradeProposalPortfolioModelViewSet)
74
+ def receive_kwargs_trade_trade_proposal_portfolio(sender, *args, **kwargs):
75
+ if obj := kwargs.get("obj_factory"):
76
+ return {"portfolio_id": obj.portfolio.id}
77
+ return {}
78
+
79
+
80
+ @receiver(custom_update_kwargs, sender=TradeInstrumentModelViewSet)
81
+ @receiver(custom_update_kwargs, sender=AssetPositionInstrumentModelViewSet)
82
+ @receiver(custom_update_kwargs, sender=SubscriptionRedemptionInstrumentModelViewSet)
83
+ @receiver(custom_update_kwargs, sender=AssetPositionUnderlyingInstrumentChartViewSet)
84
+ def receive_kwargs_instrument(sender, *args, **kwargs):
85
+ if kwargs.get("underlying_instrument_id"):
86
+ return {"instrument_id": kwargs.get("underlying_instrument_id")}
87
+ return {}
88
+
89
+
90
+ @receiver(custom_update_kwargs, sender=ProductCustomerModelViewSet)
91
+ def receive_kwargs_product_customer_instrument(sender, *args, **kwargs):
92
+ if obj := kwargs.get("obj_factory"):
93
+ InstrumentPriceFactory.create(instrument=obj, calculated=False)
94
+ obj.update_last_valuation_date()
95
+ return {}
96
+
97
+
98
+ @receiver(custom_update_kwargs, sender=PortfolioRoleInstrumentModelViewSet)
99
+ def receive_kwargs_portfolio_role_instrument(sender, *args, **kwargs):
100
+ if obj := kwargs.get("obj_factory"):
101
+ return {"instrument_id": obj.instrument.id}
102
+ return {}
103
+
104
+
105
+ @receiver(custom_update_kwargs, sender=DistributionChartViewSet)
106
+ @receiver(custom_update_kwargs, sender=DistributionTableViewSet)
107
+ def receive_kwargs_distribution_chart_instrument_id(sender, *args, **kwargs):
108
+ if instrument_id := kwargs.get("underlying_instrument_id"):
109
+ return {"instrument_id": instrument_id}
110
+ return {}
111
+
112
+
113
+ @receiver(custom_update_kwargs, sender=ProductPerformanceFeesModelViewSet)
114
+ def receive_kwargs_product_performance_fees(sender, *args, **kwargs):
115
+ CurrencyFXRatesFactory()
116
+ return {}
117
+
118
+
119
+ @receiver(custom_update_kwargs, sender=CustodianDistributionInstrumentChartViewSet)
120
+ def receive_kwargs_custodian_distribution_instrument(sender, *args, **kwargs):
121
+ if instrument_id := kwargs.get("underlying_instrument_id"):
122
+ return {"instrument_id": instrument_id}
123
+ return {}
124
+
125
+
126
+ @receiver(custom_update_kwargs, sender=NominalProductChartView)
127
+ @receiver(custom_update_kwargs, sender=AUMProductChartView)
128
+ def receive_kwargs_aum_product(sender, *args, **kwargs):
129
+ if obj := kwargs.get("obj_factory"):
130
+ product = ProductFactory()
131
+ obj.instrument = product
132
+ obj.save()
133
+ return {"product_id": product.id}
134
+ return {}
135
+
136
+
137
+ @receiver(custom_update_kwargs, sender=ClaimEntryModelViewSet)
138
+ def receive_kwargs_claim_entry(sender, *args, **kwargs):
139
+ if claim := kwargs.get("obj_factory"):
140
+ owner = CompanyFactory.create()
141
+ claim.account.owner = owner
142
+ claim.account.save()
143
+ return {"entry_id": owner.id}
144
+ return {}
145
+
146
+
147
+ @receiver(custom_update_kwargs, sender=CustomerDistributionInstrumentChartViewSet)
148
+ def receive_kwargs_customer_distribution(sender, *args, **kwargs):
149
+ if trade := kwargs.get("obj_factory"):
150
+ return {"instrument_id": trade.underlying_instrument.id}
151
+ return {}
@@ -0,0 +1,31 @@
1
+ import pytest
2
+ from wbcore.test import GenerateTest, default_config
3
+
4
+ config = {}
5
+ for key, value in default_config.items():
6
+ config[key] = list(
7
+ filter(
8
+ lambda x: x.__module__.startswith("wbportfolio")
9
+ and x.__name__
10
+ not in [
11
+ "AggregatedAssetPositionLiquidityPandasView",
12
+ "TradeTradeProposalModelViewSet",
13
+ "PortfolioSwingPricing",
14
+ "PortfolioCashTarget",
15
+ "PortfolioSwingPricingModelViewSet",
16
+ "AccountReconciliationModelViewSet",
17
+ "AccountReconciliationLineModelViewSet",
18
+ "AccountReconciliation",
19
+ "AccountReconciliationLine",
20
+ # "ClaimModelViewSet",
21
+ # "ClaimModelSerializer",
22
+ ],
23
+ value,
24
+ )
25
+ )
26
+
27
+
28
+ @pytest.mark.django_db
29
+ @GenerateTest(config)
30
+ class TestProject:
31
+ pass
File without changes