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,191 @@
1
+ from datetime import date
2
+ from typing import TYPE_CHECKING
3
+
4
+ import pytest
5
+ from django.db import IntegrityError
6
+ from wbcrm.models import Account
7
+ from wbportfolio.models import AccountReconciliation, Claim
8
+ from wbportfolio.models.products import Product
9
+ from wbportfolio.models.reconciliations.account_reconciliation_lines import (
10
+ AccountReconciliationLine,
11
+ )
12
+
13
+ if TYPE_CHECKING:
14
+ from wbcore.contrib.authentication.models import User
15
+ from wbportfolio.factories import AccountReconciliationLineFactory, ClaimFactory
16
+
17
+
18
+ class TestAccountReconciliation:
19
+ def test_str(self):
20
+ account = Account(computed_str="Test Account")
21
+ reconciliation = AccountReconciliation(account=account, reconciliation_date=date(2021, 1, 1))
22
+ assert str(reconciliation) == "Test Account (01.01.2021)"
23
+
24
+ def test_get_endpoint_basename(self):
25
+ assert AccountReconciliation.get_endpoint_basename() == "wbportfolio:accountreconciliation"
26
+
27
+ def test_get_representation_endpoint(self):
28
+ assert (
29
+ AccountReconciliation.get_representation_endpoint()
30
+ == "wbportfolio:accountreconciliationrepresentation-list"
31
+ )
32
+
33
+ def test_get_representation_value_key(self):
34
+ assert AccountReconciliation.get_representation_value_key() == "id"
35
+
36
+ def test_get_representation_label_key(self):
37
+ assert AccountReconciliation.get_representation_label_key() == "{{account}} {{reconciliation_date}}"
38
+
39
+ @pytest.mark.django_db
40
+ def test_create_for_date_and_account(self, claim_factory: "ClaimFactory", account: Account, user: "User"):
41
+ for _ in range(10):
42
+ claim_factory.create(account=account, date=date(2020, 12, 30), status=Claim.Status.APPROVED)
43
+
44
+ AccountReconciliation.objects.create(
45
+ reconciliation_date=date(2021, 1, 1),
46
+ account=account,
47
+ creator=user,
48
+ )
49
+
50
+ assert (
51
+ AccountReconciliationLine.objects.count() == Claim.objects.distinct("product").count()
52
+ ), "There should be as many lines as unique products for claims"
53
+
54
+
55
+ class TestAccountReconciliationLine:
56
+ def test_str(self):
57
+ account = Account(computed_str="Test Account")
58
+ reconciliation = AccountReconciliation(account=account, reconciliation_date=date(2021, 1, 1))
59
+ product = Product(computed_str="Test Product")
60
+ line = AccountReconciliationLine(reconciliation=reconciliation, product=product, shares=100)
61
+
62
+ assert str(line) == "Test Account (01.01.2021): Test Product 100 shares"
63
+
64
+ def test_get_endpoint_basename(self):
65
+ assert AccountReconciliationLine.get_endpoint_basename() == "wbportfolio:accountreconciliationline"
66
+
67
+ def test_representation_endpoint(self):
68
+ assert (
69
+ AccountReconciliationLine.get_representation_endpoint()
70
+ == "wbportfolio:accountreconciliationlinerepresentation-list"
71
+ )
72
+
73
+ def test_representation_value_key(self):
74
+ assert AccountReconciliationLine.get_representation_value_key() == "id"
75
+
76
+ def test_representation_label_key(self):
77
+ assert AccountReconciliationLine.get_representation_label_key() == "{{reconciliation}}"
78
+
79
+ def test_save_calculate_nominal_value(self, mocker):
80
+ mocker.patch("django.db.models.Model.save")
81
+ product = Product(share_price=100)
82
+ line = AccountReconciliationLine(product=product, shares=100, nominal_value=None)
83
+ line.save()
84
+
85
+ assert line.nominal_value == 100 * 100
86
+
87
+ def test_save_calculate_shares(self, mocker):
88
+ mocker.patch("django.db.models.Model.save")
89
+ product = Product(share_price=100)
90
+ line = AccountReconciliationLine(product=product, nominal_value=10000, shares=None)
91
+ line.save()
92
+
93
+ assert line.shares == 100
94
+
95
+ def test_save_calculate_nominal_value_external(self, mocker):
96
+ mocker.patch("django.db.models.Model.save")
97
+ product = Product(share_price=100)
98
+ line = AccountReconciliationLine(product=product, shares_external=100, nominal_value_external=None)
99
+ line.save()
100
+
101
+ assert line.nominal_value_external == 100 * 100
102
+
103
+ def test_save_calculate_shares_external(self, mocker):
104
+ mocker.patch("django.db.models.Model.save")
105
+ product = Product(share_price=100)
106
+ line = AccountReconciliationLine(product=product, nominal_value_external=10000, shares_external=None)
107
+ line.save()
108
+
109
+ assert line.shares_external == 100
110
+
111
+ @pytest.mark.django_db
112
+ def test_unique_constraint(
113
+ self,
114
+ account_reconciliation: AccountReconciliation,
115
+ product: Product,
116
+ account_reconciliation_line_factory: "AccountReconciliationLineFactory",
117
+ ):
118
+ account_reconciliation_line_factory.create(reconciliation=account_reconciliation, product=product)
119
+ with pytest.raises(IntegrityError):
120
+ account_reconciliation_line_factory.create(reconciliation=account_reconciliation, product=product)
121
+
122
+ @pytest.mark.django_db
123
+ def test_currency_annotation(self, account_reconciliation_line: AccountReconciliationLine):
124
+ assert (
125
+ AccountReconciliationLine.objects.all().annotate_currency().first().currency
126
+ == account_reconciliation_line.product.currency.symbol
127
+ )
128
+
129
+ @pytest.mark.django_db
130
+ def test_assets_under_management_annotation(self, account_reconciliation_line: AccountReconciliationLine):
131
+ assert (
132
+ AccountReconciliationLine.objects.all().annotate_assets_under_management().first().assets_under_management
133
+ == account_reconciliation_line.shares * account_reconciliation_line.price
134
+ )
135
+
136
+ @pytest.mark.django_db
137
+ def test_assets_under_management_external_annotation(self, account_reconciliation_line: AccountReconciliationLine):
138
+ assert (
139
+ AccountReconciliationLine.objects.all()
140
+ .annotate_assets_under_management_external()
141
+ .first()
142
+ .assets_under_management_external
143
+ == account_reconciliation_line.shares_external * account_reconciliation_line.price
144
+ )
145
+
146
+ @pytest.mark.django_db
147
+ @pytest.mark.parametrize("account_reconciliation_line__shares", [100])
148
+ @pytest.mark.parametrize("account_reconciliation_line__shares_external", [101])
149
+ def test_is_not_equal_annotation(self, account_reconciliation_line: AccountReconciliationLine):
150
+ assert not AccountReconciliationLine.objects.all().annotate_is_equal().first().is_equal
151
+
152
+ @pytest.mark.django_db
153
+ @pytest.mark.parametrize("account_reconciliation_line__shares", [100])
154
+ @pytest.mark.parametrize("account_reconciliation_line__shares_external", [100])
155
+ def test_is_equal_annotation(self, account_reconciliation_line: AccountReconciliationLine):
156
+ assert AccountReconciliationLine.objects.all().annotate_is_equal().first().is_equal
157
+
158
+ @pytest.mark.django_db
159
+ def test_shares_diff_annotation(self, account_reconciliation_line: AccountReconciliationLine):
160
+ assert (
161
+ AccountReconciliationLine.objects.all().annotate_shares_diff().first().shares_diff
162
+ == account_reconciliation_line.shares_external - account_reconciliation_line.shares
163
+ )
164
+
165
+ @pytest.mark.django_db
166
+ def test_pct_diff_annotation(self, account_reconciliation_line: AccountReconciliationLine):
167
+ assert (
168
+ AccountReconciliationLine.objects.all().annotate_pct_diff().first().pct_diff
169
+ == (account_reconciliation_line.shares_external - account_reconciliation_line.shares)
170
+ / account_reconciliation_line.shares
171
+ )
172
+
173
+ @pytest.mark.django_db
174
+ def test_nominal_value_diff_annotation(self, account_reconciliation_line: AccountReconciliationLine):
175
+ assert (
176
+ AccountReconciliationLine.objects.all().annotate_nominal_value_diff().first().nominal_value_diff
177
+ == account_reconciliation_line.nominal_value_external - account_reconciliation_line.nominal_value
178
+ )
179
+
180
+ @pytest.mark.django_db
181
+ def test_assets_under_management_diff_annotation(self, account_reconciliation_line: AccountReconciliationLine):
182
+ assert (
183
+ AccountReconciliationLine.objects.all()
184
+ .annotate_assets_under_management()
185
+ .annotate_assets_under_management_external()
186
+ .annotate_assets_under_management_diff()
187
+ .first()
188
+ .assets_under_management_diff
189
+ == (account_reconciliation_line.shares_external * account_reconciliation_line.price)
190
+ - (account_reconciliation_line.shares * account_reconciliation_line.price)
191
+ )
@@ -0,0 +1,193 @@
1
+ from decimal import Decimal
2
+
3
+ import pytest
4
+ from pandas.tseries.offsets import BDay
5
+ from wbportfolio.models import AssetPosition, PortfolioRole
6
+
7
+
8
+ @pytest.mark.django_db
9
+ class TestAssetPositionModel:
10
+ def test_init(self, asset_position):
11
+ assert asset_position.id is not None
12
+
13
+ def test_currency_group_by(self, asset_position_factory, equity_factory, portfolio, currency):
14
+ asset_position_factory.create(
15
+ portfolio=portfolio, currency=currency, underlying_instrument=equity_factory.create()
16
+ )
17
+ asset_position_factory.create(
18
+ portfolio=portfolio, currency=currency, underlying_instrument=equity_factory.create()
19
+ )
20
+ assert (
21
+ AssetPosition.currency_group_by(AssetPosition.objects.all()).values("groupby_id").distinct().count() == 1
22
+ )
23
+
24
+ def test_country_group_by(self, asset_position_factory, portfolio, equity):
25
+ asset_position_factory.create(portfolio=portfolio, underlying_instrument=equity)
26
+ asset_position_factory.create(portfolio=portfolio, underlying_instrument=equity)
27
+ assert AssetPosition.country_group_by(AssetPosition.objects.all()).values("groupby_id").distinct().count() == 1
28
+
29
+ def test_exchange_group_by(self, asset_position_factory, portfolio, exchange):
30
+ asset_position_factory.create(portfolio=portfolio, exchange=exchange)
31
+ asset_position_factory.create(portfolio=portfolio, exchange=exchange)
32
+ assert (
33
+ AssetPosition.exchange_group_by(AssetPosition.objects.all()).values("groupby_id").distinct().count() == 1
34
+ )
35
+
36
+ def test_cash_group_by(self, asset_position_factory, portfolio, cash, equity):
37
+ asset_position_factory.create(portfolio=portfolio, underlying_instrument=cash)
38
+ asset_position_factory.create(portfolio=portfolio, underlying_instrument=cash)
39
+ asset_position_factory.create(portfolio=portfolio, underlying_instrument=equity)
40
+ assert AssetPosition.cash_group_by(AssetPosition.objects.all()).values("groupby_id").distinct().count() == 2
41
+
42
+ def test_equity_group_by(self, asset_position_factory, portfolio, equity):
43
+ asset_position_factory.create(portfolio=portfolio, underlying_instrument=equity)
44
+ asset_position_factory.create(portfolio=portfolio, underlying_instrument=equity)
45
+ assert AssetPosition.equity_group_by(AssetPosition.objects.all()).values("groupby_id").distinct().count() == 1
46
+
47
+ def test_marketcap_group_by(
48
+ self, asset_position_factory, equity_factory, portfolio, instrument_price_factory, currency_fx_rates_factory
49
+ ):
50
+ fx_rate = currency_fx_rates_factory.create(value=1)
51
+ i1 = instrument_price_factory.create(instrument=equity_factory.create(), market_capitalization=1000000000)
52
+ i2 = instrument_price_factory.create(instrument=equity_factory.create(), market_capitalization=30000000000)
53
+ asset_position_factory.create(
54
+ portfolio=portfolio,
55
+ underlying_instrument_price=i1,
56
+ currency_fx_rate_instrument_to_usd=fx_rate,
57
+ currency_fx_rate_portfolio_to_usd=fx_rate,
58
+ ) # small
59
+ asset_position_factory.create(
60
+ portfolio=portfolio,
61
+ underlying_instrument_price=i2,
62
+ currency_fx_rate_instrument_to_usd=fx_rate,
63
+ currency_fx_rate_portfolio_to_usd=fx_rate,
64
+ ) # larg
65
+
66
+ qs = AssetPosition.marketcap_group_by(AssetPosition.objects.all())
67
+ assert qs.values("aggregated_title").distinct().count() == 2
68
+ assert set(qs.distinct().values_list("aggregated_title", flat=True)) == {"10B to 50B", "< 2B"}
69
+
70
+ def test_liquidity_group_by(self, asset_position_factory, equity_factory, portfolio, instrument_price_factory):
71
+ a1 = asset_position_factory.create(
72
+ portfolio=portfolio, initial_shares=1, underlying_instrument=equity_factory.create()
73
+ ) # small
74
+ a2 = asset_position_factory.create(
75
+ portfolio=portfolio, initial_shares=1, underlying_instrument=equity_factory.create()
76
+ ) # larg
77
+
78
+ instrument_price_factory.create(instrument=a1.underlying_instrument, date=a1.date, volume_50d=1000000)
79
+ instrument_price_factory.create(instrument=a2.underlying_instrument, date=a2.date, volume_50d=3000000)
80
+
81
+ qs = AssetPosition.liquidity_group_by(AssetPosition.objects.all())
82
+ assert qs.values("aggregated_title").distinct().count() == 1
83
+
84
+ def test_get_shown_positions_superuser(self, portfolio, superuser, asset_position_factory):
85
+ superuser.is_superuser = True
86
+ superuser.save()
87
+ asset_position_factory.create_batch(10, portfolio=portfolio)
88
+ qs = AssetPosition.get_shown_positions(superuser.profile)
89
+ assert qs.count() == 10
90
+
91
+ def test_get_shown_positions_analyst(
92
+ self, portfolio_factory, product_factory, person, user, asset_position_factory, product_portfolio_role_factory
93
+ ):
94
+ user.profile = person
95
+ user.save()
96
+ p2 = portfolio_factory.create()
97
+ product = product_factory.create()
98
+ product_portfolio_role_factory.create(
99
+ person=person, instrument=product, role_type=PortfolioRole.RoleType.ANALYST
100
+ )
101
+ asset_position_factory.create_batch(10, portfolio=product.portfolio)
102
+ asset_position_factory.create_batch(10, portfolio=p2)
103
+ qs = AssetPosition.get_shown_positions(person)
104
+ assert qs.count() == 10
105
+
106
+ def test_analytical_objects(self, asset_position_factory, portfolio, equity_factory):
107
+ # Data creation
108
+ equity1 = equity_factory.create()
109
+ equity2 = equity_factory.create()
110
+ asset_position_factory.create_batch(5, underlying_instrument=equity1, portfolio=portfolio)
111
+ asset_position_factory.create_batch(5, underlying_instrument=equity2, portfolio=portfolio)
112
+
113
+ # We test calculation with equity1. equity2 allows to certify the temporal continuity of the portfolio.
114
+ qs_assets_equity1 = AssetPosition.analytical_objects.order_by("date").filter(underlying_instrument=equity1)
115
+ qs_prices_equity1 = equity1.prices.order_by("date")
116
+
117
+ # ap holds for AssetPosition and ep holds for Equity Price.
118
+ ap1, ap2 = qs_assets_equity1[:2]
119
+ ep1, ep2, ep3, ep4 = qs_prices_equity1[:4]
120
+
121
+ # Basic Performance
122
+ assert ap2.performance == float(ap2.price_fx_usd / ap1.price_fx_usd - 1)
123
+ assert ap2.performance == pytest.approx(float(ep2._net_value_usd / ep1._net_value_usd - Decimal(1)))
124
+
125
+ # We sell the equity (ap3 deleted) and then we buy it again (back in ap4).
126
+ # This is like having a missing data in between.
127
+ qs_assets_equity1.exclude(id__in=[ap1.id, ap2.id]).first().delete() # ap3
128
+ ap4 = qs_assets_equity1.exclude(id__in=[ap1.id, ap2.id]).first()
129
+
130
+ assert ap4.performance is None
131
+ assert ep4._net_value_usd / ep3._net_value_usd is not None
132
+
133
+ def test_get_underlying_instrument_price(self, asset_position_factory, instrument_price_factory):
134
+ """
135
+ In this test, we confirm that an asset position with no matching real price for the same date and underlying instrument will result in an empty underlying_instrument_price
136
+ """
137
+ a1 = asset_position_factory.create(underlying_instrument_price=None)
138
+ a1.save()
139
+ assert a1.underlying_instrument_price is None
140
+ p2_calculated = instrument_price_factory.create(calculated=True)
141
+ a2 = asset_position_factory.create(
142
+ underlying_instrument_price=None, underlying_instrument=p2_calculated.instrument, date=p2_calculated.date
143
+ )
144
+ a2.save()
145
+ assert a2.underlying_instrument_price is None
146
+ p2_real = instrument_price_factory.create(
147
+ calculated=False, date=p2_calculated.date, instrument=p2_calculated.instrument
148
+ )
149
+ a2.save()
150
+ assert a2.underlying_instrument_price == p2_real
151
+
152
+ def test_change_currency_recompute_currency_fx_rate_for_price_and_assets(
153
+ self, weekday, instrument, currency_fx_rates_factory, instrument_price_factory, asset_position_factory
154
+ ):
155
+ fx_rate = currency_fx_rates_factory.create(currency=instrument.currency, date=weekday)
156
+ fx_rate_other = currency_fx_rates_factory.create(date=weekday)
157
+
158
+ p = instrument_price_factory.create(instrument=instrument, date=weekday)
159
+ a = asset_position_factory.create(underlying_instrument=instrument, date=weekday)
160
+
161
+ assert p.currency_fx_rate_to_usd == fx_rate
162
+ assert a.currency_fx_rate_instrument_to_usd == fx_rate
163
+
164
+ instrument.currency = fx_rate_other.currency
165
+ instrument.save()
166
+
167
+ p.refresh_from_db()
168
+ a.refresh_from_db()
169
+
170
+ assert p.currency_fx_rate_to_usd == fx_rate_other
171
+ assert a.currency_fx_rate_instrument_to_usd == fx_rate_other
172
+
173
+ def test_create_price_set_assetposition(self, asset_position_factory, instrument_price_factory):
174
+ p0 = instrument_price_factory.create()
175
+ d1 = (p0.date + BDay(1)).date()
176
+ a1 = asset_position_factory.create(
177
+ date=d1, underlying_instrument=p0.instrument, underlying_instrument_price=None
178
+ )
179
+
180
+ # check it is linked to the only price
181
+ assert a1.underlying_instrument_price == p0
182
+ p1 = instrument_price_factory.create(instrument=p0.instrument, date=d1)
183
+ a1.refresh_from_db()
184
+ assert a1.underlying_instrument_price == p1
185
+
186
+ d_1 = (p0.date - BDay(1)).date()
187
+ a_1 = asset_position_factory.create(
188
+ date=d_1, underlying_instrument=p1.instrument, underlying_instrument_price=None
189
+ )
190
+ assert a_1.underlying_instrument_price is None
191
+ p_1 = instrument_price_factory.create(instrument=p1.instrument, date=d_1)
192
+ a_1.refresh_from_db()
193
+ assert a_1.underlying_instrument_price == p_1
@@ -0,0 +1,12 @@
1
+ import pytest
2
+
3
+
4
+ @pytest.mark.django_db
5
+ class TestCustodianModel:
6
+ def test_init(self, custodian_factory):
7
+ custodian = custodian_factory.create()
8
+ assert custodian.id is not None
9
+
10
+ def test_str(self, custodian_factory):
11
+ custodian = custodian_factory.create()
12
+ assert str(custodian) == f"{custodian.name}"
@@ -0,0 +1,113 @@
1
+ import random
2
+ from datetime import timedelta
3
+
4
+ import pytest
5
+ from django.db.models import ProtectedError
6
+ from faker import Faker
7
+ from wbportfolio.models import Trade
8
+ from wbportfolio.models.transactions.claim import Claim
9
+
10
+ fake = Faker()
11
+
12
+
13
+ @pytest.mark.django_db
14
+ class TestCustomerTradeModel:
15
+ def test_delete_without_claims(self, trade):
16
+ """
17
+ Simple test to check if a trade without claim is properly deleted
18
+ """
19
+ trade.delete()
20
+ with pytest.raises(Trade.DoesNotExist):
21
+ trade.refresh_from_db()
22
+
23
+ @pytest.mark.parametrize("claim_status", Claim.Status.names)
24
+ def test_delete_trade_with_claim(self, customer_trade_factory, claim_factory, claim_status):
25
+ """
26
+ Simple test to check if a trade with claim a valid claim (Pending or Approved9 can't be deleted but
27
+ a trade with a draft, withdrawn or auto approved claim can.
28
+ """
29
+ customer_trade = customer_trade_factory.create()
30
+ claim = claim_factory.create(trade=customer_trade, status=claim_status)
31
+
32
+ # If claim is among the non approved type, we expect the deletion to succeed and the claim to be unlinked
33
+ if claim.status in [Claim.Status.DRAFT, Claim.Status.AUTO_MATCHED, Claim.Status.WITHDRAWN]:
34
+ customer_trade.delete()
35
+ claim.refresh_from_db()
36
+ assert not claim.trade
37
+ with pytest.raises(Trade.DoesNotExist):
38
+ customer_trade.refresh_from_db()
39
+ else:
40
+ # If the claim is approved or pending, and in absence of any other similar trades to reassign the claim to, we expect the deletion to failed
41
+ with pytest.raises(ProtectedError):
42
+ customer_trade.delete()
43
+
44
+ def test_delete_trade_with_approved_claims_but_similar_trades_summing_0(
45
+ self, customer_trade_factory, claim_factory
46
+ ):
47
+ """
48
+ A test to check that even trade with approved claims attach can be deleted if the sum of the shares of the similar trades the same date equals to 0
49
+ """
50
+ trade1 = customer_trade_factory.create()
51
+ claim1 = claim_factory.create(trade=trade1, status=Claim.Status.APPROVED)
52
+ trade1.marked_for_deletion = True
53
+ trade1.save()
54
+
55
+ # Claims is attached, no other similar trade is present, we don't expect the deletion to be possible
56
+ with pytest.raises(ProtectedError):
57
+ trade1.delete()
58
+
59
+ # We create a negative trade that will annihilate trade1
60
+ trade2 = customer_trade_factory.create(
61
+ underlying_instrument=trade1.underlying_instrument,
62
+ portfolio=trade1.portfolio,
63
+ transaction_date=trade1.transaction_date,
64
+ shares=-trade1.shares,
65
+ )
66
+ # That we claim
67
+ claim2 = claim_factory.create(trade=trade2, status=Claim.Status.APPROVED)
68
+ trade2.marked_for_deletion = True
69
+ trade2.save()
70
+
71
+ # Now, sum of all trades linked to this product at that date sums to 0, we can destroy all trades and unliked the associated claims
72
+ trade1.delete()
73
+ claim1.refresh_from_db()
74
+ claim2.refresh_from_db()
75
+ assert not claim1.trade
76
+ assert claim1.status == Claim.Status.DRAFT
77
+ assert not claim2.trade
78
+ assert claim2.status == Claim.Status.DRAFT
79
+
80
+ # trade2 is unclaimed and can be safely deleted
81
+ trade2.delete()
82
+ with pytest.raises(Trade.DoesNotExist):
83
+ trade2.refresh_from_db()
84
+
85
+ @pytest.mark.parametrize(
86
+ "allowed_timedeltda", [random.randint(-Trade.TRADE_WINDOW_INTERVAL, Trade.TRADE_WINDOW_INTERVAL)]
87
+ )
88
+ def test_trade_delete_shift_claims(self, customer_trade_factory, claim_factory, allowed_timedeltda):
89
+ """
90
+ Test main functionality: when a trade is linked to an approved claim and is marked for deletion, we try to find the closest trade non marked for deletion or pending and unclaimed.
91
+
92
+ If this succeed, the claim is forwarded to this other trade. Otherwise, it will fail.
93
+ """
94
+ trade1 = customer_trade_factory.create()
95
+ claim1 = claim_factory.create(trade=trade1, status=Claim.Status.APPROVED)
96
+ trade1.marked_for_deletion = True
97
+ trade1.save()
98
+
99
+ # We expect the claim to be reassigned to this similar "non marked for deletion" trade
100
+ similar_unclaimed_trade = customer_trade_factory.create(
101
+ marked_for_deletion=False,
102
+ pending=False,
103
+ underlying_instrument=trade1.underlying_instrument,
104
+ portfolio=trade1.portfolio,
105
+ transaction_subtype=trade1.transaction_subtype,
106
+ transaction_date=trade1.transaction_date + timedelta(days=allowed_timedeltda),
107
+ shares=trade1.shares,
108
+ )
109
+ trade1.delete()
110
+ with pytest.raises(Trade.DoesNotExist):
111
+ trade1.refresh_from_db()
112
+ claim1.refresh_from_db()
113
+ assert claim1.trade == similar_unclaimed_trade
@@ -0,0 +1,7 @@
1
+ import pytest
2
+
3
+
4
+ @pytest.mark.django_db
5
+ class TestDividendModel:
6
+ def test_init(self, dividend_transaction):
7
+ assert dividend_transaction.id is not None