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,192 @@
1
+ from decimal import Decimal
2
+
3
+ import pytest
4
+ from django.forms.models import model_to_dict
5
+ from faker import Faker
6
+ from pandas.tseries.offsets import BDay
7
+ from wbfdm.import_export.handlers.instrument_price import InstrumentPriceImportHandler
8
+ from wbfdm.models import InstrumentPrice
9
+ from wbportfolio.import_export.handlers.asset_position import AssetPositionImportHandler
10
+ from wbportfolio.import_export.handlers.fees import FeesImportHandler
11
+ from wbportfolio.import_export.handlers.trade import TradeImportHandler
12
+ from wbportfolio.models import AssetPosition, Fees, Trade
13
+
14
+ fake = Faker()
15
+
16
+
17
+ @pytest.mark.django_db
18
+ class TestImportMixinModel:
19
+ def test_import_trade(self, import_source, product, portfolio, trade_factory):
20
+ def serialize(trade):
21
+ data = model_to_dict(trade)
22
+ data["transaction_date"] = trade.transaction_date.strftime("%Y-%m-%d")
23
+ data["value_date"] = trade.value_date.strftime("%Y-%m-%d")
24
+ data["underlying_instrument"] = {"id": product.id}
25
+ data["portfolio"] = portfolio.id
26
+ data["currency"] = {"key": portfolio.currency.key}
27
+ del data["id"]
28
+ del data["import_source"]
29
+ del data["transaction_ptr"]
30
+ return data
31
+
32
+ trade = trade_factory.build()
33
+ data = {"data": [serialize(trade)]}
34
+ handler = TradeImportHandler(import_source)
35
+
36
+ # Import non existing data
37
+ handler.process(data)
38
+ assert Trade.objects.count() == 1
39
+
40
+ # Import already existing data
41
+ # import_source.data['data'][0]['shares'] *= 2
42
+
43
+ handler.process(data)
44
+ assert Trade.objects.count() == 1
45
+
46
+ def test_import_price(self, import_source, product, instrument_price_factory, instrument):
47
+ def serialize(val):
48
+ data = model_to_dict(val)
49
+ for k, v in data.items():
50
+ if isinstance(v, Decimal):
51
+ data[k] = float(v)
52
+ data["date"] = f"{val.date:%Y-%m-%d}"
53
+ data["instrument"] = {"instrument_type": "product", "id": product.id}
54
+ del data["id"]
55
+ del data["import_source"]
56
+ del data["currency_fx_rate_to_usd"]
57
+ return data
58
+
59
+ val = instrument_price_factory.build(instrument=instrument)
60
+ data = {"data": [serialize(val)]}
61
+ handler = InstrumentPriceImportHandler(import_source)
62
+ # Import non existing data
63
+ handler.process(data)
64
+ assert InstrumentPrice.objects.count() == 1
65
+
66
+ # Import already existing data
67
+ data["data"][0]["net_value"] *= 2
68
+ handler.process(data)
69
+ assert InstrumentPrice.objects.count() == 1
70
+
71
+ def test_import_fees(self, import_source, product_factory, portfolio, cash_factory, fees_factory):
72
+ product = product_factory.create()
73
+ portfolio = product.primary_portfolio
74
+ cash_factory.create(currency=portfolio.currency)
75
+
76
+ def serialize(fees):
77
+ data = model_to_dict(fees)
78
+ data["transaction_date"] = fees.transaction_date.strftime("%Y-%m-%d")
79
+ data["value_date"] = fees.value_date.strftime("%Y-%m-%d")
80
+ data["portfolio"] = portfolio.id
81
+ data["linked_product"] = product.id
82
+ data["portfolio"] = portfolio.id
83
+ data["currency"] = {"key": portfolio.currency.key}
84
+ del data["calculated"]
85
+ del data["id"]
86
+ del data["import_source"]
87
+ del data["transaction_ptr"]
88
+ return data
89
+
90
+ fees = fees_factory.build(calculated=False)
91
+ data = {"data": [serialize(fees)]}
92
+ handler = FeesImportHandler(import_source)
93
+ # Import non existing data
94
+ handler.process(data)
95
+ assert Fees.objects.count() == 1
96
+
97
+ # Import already existing data
98
+ data["data"][0]["total_value"] *= 2
99
+ handler.process(data)
100
+ assert Fees.objects.count() == 1
101
+
102
+ def _serialize_position(self, pos, instrument, underlying):
103
+ return {
104
+ "date": f"{pos.date:%Y-%m-%d}",
105
+ "initial_shares": pos.initial_shares,
106
+ "initial_price": pos.initial_price,
107
+ "weighting": pos.weighting,
108
+ "initial_currency_fx_rate": Decimal(1),
109
+ "asset_valuation_date": f"{pos.asset_valuation_date:%Y-%m-%d}",
110
+ "currency": {"key": underlying.currency.key},
111
+ "portfolio": {"id": instrument.id, "instrument_type": instrument.security_instrument_type.key},
112
+ "underlying_instrument": {
113
+ "instrument_type": underlying.instrument_type.key,
114
+ "currency": {"key": underlying.currency.key},
115
+ "ticker": underlying.ticker,
116
+ "name": underlying.name,
117
+ "exchange": {"bbg_composite": "US"},
118
+ },
119
+ }
120
+
121
+ @pytest.mark.parametrize("val_date", [(fake.date_object())])
122
+ def test_import_assetposition_product(
123
+ self,
124
+ val_date,
125
+ import_source,
126
+ product_factory,
127
+ currency,
128
+ equity_factory,
129
+ index_factory,
130
+ cash_factory,
131
+ asset_position_factory,
132
+ ):
133
+ val_date = (val_date - BDay(0)).date()
134
+ product_portfolio = product_factory.create()
135
+ instruments = [
136
+ equity_factory.create(currency=currency, instrument_type__name="Equity"),
137
+ cash_factory.create(currency=currency, instrument_type__name="Cash"),
138
+ product_factory.create(currency=currency, instrument_type__name="Product"),
139
+ index_factory.create(currency=currency, instrument_type__name="Index"),
140
+ ]
141
+ data = {
142
+ "data": [
143
+ self._serialize_position(
144
+ asset_position_factory.build(date=val_date, underlying_instrument=instrument),
145
+ product_portfolio,
146
+ instrument,
147
+ )
148
+ for instrument in instruments
149
+ ]
150
+ }
151
+ handler = AssetPositionImportHandler(import_source)
152
+
153
+ # Import non existing data
154
+ handler.process(data)
155
+ for instrument in instruments:
156
+ # we check that the position was created
157
+ a = AssetPosition.objects.get(
158
+ underlying_instrument=instrument, date=val_date, portfolio=product_portfolio.portfolio
159
+ )
160
+ assert a
161
+ # and as this is an import handle by the assetposition handler, we expect the underlying instrument price to be created from the position initial price
162
+ assert InstrumentPrice.objects.get(
163
+ instrument=instrument, date=val_date, calculated=False, net_value=a.initial_price
164
+ )
165
+
166
+ # Import already existing data
167
+ data["data"][0]["initial_price"] *= 2
168
+
169
+ handler.process(data)
170
+ assert AssetPosition.objects.count() == 4
171
+
172
+ def test_import_assetposition_product_group(
173
+ self, import_source, product_group, currency, equity, asset_position_factory
174
+ ):
175
+ positions = asset_position_factory.build(underlying_instrument=equity)
176
+ data = {"data": [self._serialize_position(positions, product_group, equity)]}
177
+
178
+ # Import non existing data
179
+ handler = AssetPositionImportHandler(import_source)
180
+
181
+ handler.process(data)
182
+ assert AssetPosition.objects.count() == 1
183
+
184
+ def test_import_assetposition_index(self, import_source, index, currency, equity, asset_position_factory):
185
+ positions = asset_position_factory.build(underlying_instrument=equity)
186
+ data = {"data": [self._serialize_position(positions, index, equity)]}
187
+
188
+ # Import non existing data
189
+ handler = AssetPositionImportHandler(import_source)
190
+
191
+ handler.process(data)
192
+ assert AssetPosition.objects.count() == 1
@@ -0,0 +1,48 @@
1
+ from datetime import timedelta
2
+
3
+ import pandas as pd
4
+ import pytest
5
+ from faker import Faker
6
+
7
+ fake = Faker()
8
+
9
+
10
+ @pytest.mark.django_db
11
+ class TestInstrumentMixin:
12
+ def test_get_latest_valid_price(self, product, instrument_price_factory):
13
+ v1 = instrument_price_factory.create(instrument=product)
14
+ v2 = instrument_price_factory.create(instrument=product)
15
+ instrument_price_factory.create(instrument=product, calculated=True)
16
+
17
+ assert product.get_latest_valid_price() == v2
18
+ assert product.get_latest_valid_price(v1.date) == v1
19
+
20
+ def test_get_earliest_valid_price(self, product, instrument_price_factory):
21
+ instrument_price_factory.create(instrument=product, calculated=True)
22
+ v2 = instrument_price_factory.create(instrument=product)
23
+ v3 = instrument_price_factory.create(instrument=product)
24
+
25
+ assert product.get_earliest_valid_price() == v2
26
+ assert product.get_earliest_valid_price(v3.date) == v3
27
+
28
+ def test_get_price_range(self, product, instrument_price_factory):
29
+ v_low = instrument_price_factory.create(instrument=product, net_value=1)
30
+ instrument_price_factory.create(instrument=product, net_value=10)
31
+ v_high = instrument_price_factory.create(instrument=product, net_value=100)
32
+
33
+ res = product.get_price_range()
34
+ assert res["high"]["price"] == pytest.approx(float(v_high.net_value), rel=1e-4)
35
+ assert res["high"]["date"] == v_high.date
36
+ assert res["low"]["price"] == pytest.approx(float(v_low.net_value), rel=1e-4)
37
+ assert res["low"]["date"] == v_low.date
38
+ assert product.get_price_range(v_low.date - timedelta(days=1)) == dict()
39
+
40
+ def test_get_cumulative_shares(self, weekday, product, customer_trade_factory):
41
+ from_date = weekday
42
+ to_date = (weekday + pd.tseries.offsets.BDay(1)).date()
43
+ assert product.get_cumulative_shares(from_date, to_date).to_list() == [0.0, 0.0]
44
+
45
+ trade = customer_trade_factory.create(
46
+ transaction_date=weekday, underlying_instrument=product, portfolio=product.primary_portfolio
47
+ )
48
+ assert product.get_cumulative_shares(from_date, to_date).to_list() == [0.0, float(trade.shares)]
@@ -0,0 +1,133 @@
1
+ import pytest
2
+ from faker import Faker
3
+ from pandas.tseries.offsets import BDay
4
+ from wbfdm.factories import InstrumentFactory
5
+ from wbfdm.models import InstrumentPrice
6
+ from wbportfolio.models import (
7
+ Adjustment,
8
+ PortfolioInstrumentPreferredClassificationThroughModel,
9
+ )
10
+
11
+ fake = Faker()
12
+
13
+
14
+ @pytest.mark.django_db
15
+ class TestMergeInstrument:
16
+ @pytest.fixture()
17
+ def merged_instrument(self):
18
+ return InstrumentFactory.create()
19
+
20
+ @pytest.fixture()
21
+ def main_instrument(self):
22
+ return InstrumentFactory.create()
23
+
24
+ def test_assets(
25
+ self, main_instrument, merged_instrument, asset_position_factory, instrument_price_factory, weekday
26
+ ):
27
+ """
28
+ Test if the asset positions attached to the merged instrument are forwarded to the main instrument as well as the underlying instrument price
29
+ """
30
+ p1 = instrument_price_factory.create(instrument=main_instrument, calculated=False, date=weekday)
31
+ p2 = instrument_price_factory.create(instrument=merged_instrument, calculated=False, date=weekday)
32
+
33
+ a1 = asset_position_factory.create(underlying_instrument=main_instrument, date=weekday)
34
+ a2 = asset_position_factory.create(underlying_instrument=merged_instrument, date=weekday)
35
+
36
+ assert a1.underlying_instrument_price == p1
37
+ assert a2.underlying_instrument_price == p2
38
+ main_instrument.merge(merged_instrument)
39
+ a2.refresh_from_db()
40
+ a1.refresh_from_db()
41
+
42
+ assert a1.underlying_instrument == main_instrument # Make sure this doesn't change
43
+ assert a1.underlying_instrument_price == p1 # Make sure this doesn't change
44
+ assert a2.underlying_instrument == main_instrument
45
+ assert a2.underlying_instrument_price == p1
46
+
47
+ def test_roles(self, main_instrument, merged_instrument, product_portfolio_role_factory):
48
+ """
49
+ Test if the role attached to the merged instrument are forwarded to the main instrument
50
+ """
51
+ role = product_portfolio_role_factory.create(instrument=merged_instrument)
52
+ main_instrument.merge(merged_instrument)
53
+ role.refresh_from_db()
54
+ assert role.instrument == main_instrument
55
+
56
+ def test_classifications(self, main_instrument, merged_instrument, portfolio_factory, classification_factory):
57
+ """
58
+ Test if preferred classification are forwarded to the main instrument
59
+ """
60
+ main_portfolio = portfolio_factory.create()
61
+ merged_portfolio = portfolio_factory.create()
62
+
63
+ PortfolioInstrumentPreferredClassificationThroughModel.objects.create(
64
+ instrument=main_instrument, portfolio=main_portfolio, classification=classification_factory.create()
65
+ )
66
+ PortfolioInstrumentPreferredClassificationThroughModel.objects.create(
67
+ instrument=merged_instrument, portfolio=merged_portfolio, classification=classification_factory.create()
68
+ )
69
+ main_instrument.merge(merged_instrument)
70
+
71
+ assert set(main_instrument.preferred_portfolio_classifications.all()) == set(
72
+ [main_portfolio, merged_portfolio]
73
+ )
74
+
75
+ def test_transactions(self, main_instrument, merged_instrument, trade_factory):
76
+ """
77
+ Test if the attached transactions to the merged instrument are forwarded to the main instrument
78
+ """
79
+ merged_t = trade_factory.create(underlying_instrument=merged_instrument)
80
+ main_t = trade_factory.create(underlying_instrument=main_instrument)
81
+
82
+ main_instrument.merge(merged_instrument)
83
+ merged_t.refresh_from_db()
84
+ main_t.refresh_from_db()
85
+
86
+ merged_t.underlying_instrument = main_instrument
87
+ main_t.underlying_instrument = main_instrument # make sure this does not change
88
+
89
+ def test_instrument_prices(self, main_instrument, merged_instrument, instrument_price_factory, weekday):
90
+ """
91
+ Test if the attached prices to the merged instrument are forwarded to the main instrument but does not overlaps them (if price already exists, the overlapping merged price are simply deleted)
92
+ """
93
+ main_p1 = instrument_price_factory.create(instrument=main_instrument, calculated=False, date=weekday)
94
+
95
+ merged_p1 = instrument_price_factory.create(instrument=merged_instrument, calculated=False, date=weekday)
96
+ merged_p2 = instrument_price_factory.create(
97
+ instrument=merged_instrument, calculated=False, date=weekday + BDay(1)
98
+ )
99
+
100
+ assert main_instrument.valuations.count() == 1
101
+ main_instrument.merge(merged_instrument)
102
+
103
+ assert (
104
+ main_instrument.valuations.get(date=weekday).net_value == main_p1.net_value
105
+ ) # Check that the existing price was not overlaps by the merged instrument price at that day
106
+ assert (
107
+ main_instrument.valuations.get(date=weekday + BDay(1)).net_value == merged_p2.net_value
108
+ ) # Check that the price not existing from merged instrument was appended to the main instrument serie
109
+ with pytest.raises(InstrumentPrice.DoesNotExist):
110
+ merged_p1.refresh_from_db()
111
+
112
+ def test_adjustments(self, main_instrument, merged_instrument, adjustment_factory, weekday):
113
+ """
114
+ Test if the attached adjustments to the merged instrument are forwarded to the main instrument but does not overlaps them (if split already exists, the overlapping merged split are simply deleted)
115
+ """
116
+ main_s1 = adjustment_factory.create(instrument=main_instrument, date=weekday)
117
+
118
+ merged_s1 = adjustment_factory.create(instrument=merged_instrument, date=weekday)
119
+ merged_s2 = adjustment_factory.create(instrument=merged_instrument, date=weekday + BDay(1))
120
+ merged_s2.refresh_from_db() # We refresh from db to cast the decimal factor to its proper number of decimal places (kind of stupid but..)
121
+ main_s1.refresh_from_db()
122
+
123
+ assert main_instrument.pms_adjustments.count() == 1
124
+ main_instrument.merge(merged_instrument)
125
+
126
+ assert (
127
+ main_instrument.pms_adjustments.get(date=weekday).factor == main_s1.factor
128
+ ) # Check that the existing split was not overlaps by the merged instrument split at that day
129
+ assert (
130
+ main_instrument.pms_adjustments.get(date=weekday + BDay(1)).factor == merged_s2.factor
131
+ ) # Check that the split not existing from merged instrument was appended to the main instrument serie
132
+ with pytest.raises(Adjustment.DoesNotExist):
133
+ merged_s1.refresh_from_db()
@@ -0,0 +1,112 @@
1
+ from datetime import date
2
+ from decimal import Decimal
3
+ from typing import TYPE_CHECKING
4
+
5
+ import pytest
6
+ from django.db.utils import IntegrityError
7
+
8
+ if TYPE_CHECKING:
9
+ from wbportfolio.factories import DailyPortfolioCashFlowFactory
10
+ from wbportfolio.models.portfolio import Portfolio
11
+ from wbportfolio.models.portfolio_cash_flow import DailyPortfolioCashFlow
12
+ from wbportfolio.models.portfolio_cash_targets import PortfolioCashTarget
13
+
14
+
15
+ @pytest.mark.django_db
16
+ class TestDailyPortfolioCashFlowFactory:
17
+ def test_factory(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
18
+ assert daily_portfolio_cash_flow.pk is not None
19
+
20
+ def test_constraint_unique_value_date_portfolio(
21
+ self, portfolio: "Portfolio", daily_portfolio_cash_flow_factory: "DailyPortfolioCashFlowFactory"
22
+ ):
23
+ cf1 = daily_portfolio_cash_flow_factory.create(portfolio=portfolio)
24
+ cf2 = daily_portfolio_cash_flow_factory.create(portfolio=portfolio)
25
+
26
+ cf2.value_date = cf1.value_date
27
+ with pytest.raises(IntegrityError):
28
+ cf2.save()
29
+
30
+ @pytest.mark.parametrize("daily_portfolio_cash_flow__pending", [False])
31
+ def test_estimated_total_assets(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
32
+ dpcf = daily_portfolio_cash_flow
33
+ assert dpcf.estimated_total_assets == dpcf.total_assets
34
+
35
+ @pytest.mark.parametrize("daily_portfolio_cash_flow__pending", [True])
36
+ def test_estimated_total_assets_while_pending(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
37
+ dpcf = daily_portfolio_cash_flow
38
+ assert dpcf.estimated_total_assets == dpcf.total_assets + dpcf.cash_flow_forecast
39
+
40
+ def test_cash_flow_asset_ratio(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
41
+ dpcf = daily_portfolio_cash_flow
42
+ assert dpcf.cash_flow_asset_ratio == dpcf.cash_flow_forecast / dpcf.estimated_total_assets
43
+
44
+ @pytest.mark.parametrize("daily_portfolio_cash_flow__total_assets", [Decimal(0)])
45
+ @pytest.mark.parametrize("daily_portfolio_cash_flow__cash_flow_forecast", [Decimal(0)])
46
+ def test_cash_flow_asset_ratio_zero_assets(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
47
+ assert daily_portfolio_cash_flow.cash_flow_asset_ratio == Decimal(0)
48
+
49
+ def test_cash_pct(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
50
+ dpct = daily_portfolio_cash_flow
51
+ assert dpct.cash_pct == dpct.cash / dpct.estimated_total_assets
52
+
53
+ @pytest.mark.parametrize("daily_portfolio_cash_flow__total_assets", [Decimal(0)])
54
+ @pytest.mark.parametrize("daily_portfolio_cash_flow__cash_flow_forecast", [Decimal(0)])
55
+ def test_cash_pct_zero_assets(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
56
+ assert daily_portfolio_cash_flow.cash_pct == Decimal(0)
57
+
58
+ def test_true_cash(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
59
+ dpct = daily_portfolio_cash_flow
60
+ assert dpct.true_cash == dpct.cash + dpct.cash_flow_forecast
61
+
62
+ def test_true_cash_pct(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
63
+ dpct = daily_portfolio_cash_flow
64
+ assert dpct.true_cash_pct == dpct.true_cash / dpct.estimated_total_assets
65
+
66
+ @pytest.mark.parametrize("daily_portfolio_cash_flow__total_assets", [Decimal(0)])
67
+ @pytest.mark.parametrize("daily_portfolio_cash_flow__cash_flow_forecast", [Decimal(0)])
68
+ def test_true_cash_pct_zero_assets(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
69
+ assert daily_portfolio_cash_flow.true_cash_pct == Decimal(0)
70
+
71
+ def test_target_cash_no_target(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
72
+ dpct = daily_portfolio_cash_flow
73
+ assert dpct.target_cash == Decimal(0)
74
+
75
+ def test_target_cash(
76
+ self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow", portfolio_cash_target: "PortfolioCashTarget"
77
+ ):
78
+ dpct = daily_portfolio_cash_flow
79
+ portfolio_cash_target.portfolio = dpct.portfolio
80
+ portfolio_cash_target.valid_date = dpct.value_date
81
+ portfolio_cash_target.save()
82
+ dpct.save()
83
+ dpct.refresh_from_db()
84
+ assert dpct.target_cash == pytest.approx(portfolio_cash_target.target * dpct.estimated_total_assets)
85
+
86
+ def test_excess_cash(
87
+ self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow", portfolio_cash_target: "PortfolioCashTarget"
88
+ ):
89
+ dpct = daily_portfolio_cash_flow
90
+ portfolio_cash_target.portfolio = dpct.portfolio
91
+ portfolio_cash_target.valid_date = dpct.value_date
92
+ portfolio_cash_target.save()
93
+ dpct.save()
94
+ dpct.refresh_from_db()
95
+ assert dpct.excess_cash == dpct.true_cash - dpct.target_cash
96
+
97
+ def test_cash_from_yesterday(
98
+ self, portfolio: "Portfolio", daily_portfolio_cash_flow_factory: "DailyPortfolioCashFlowFactory"
99
+ ):
100
+ cf1 = daily_portfolio_cash_flow_factory.create(portfolio=portfolio, value_date=date(2020, 1, 1))
101
+ cf2 = daily_portfolio_cash_flow_factory.create(
102
+ portfolio=portfolio, value_date=date(2020, 1, 2), pending=True, rebalancing=Decimal(0)
103
+ )
104
+ cf1.refresh_from_db()
105
+ cf2.refresh_from_db()
106
+ assert cf2.cash == cf1.true_cash
107
+
108
+ @pytest.mark.parametrize("daily_portfolio_cash_flow__cash", [Decimal(1_000_000)])
109
+ @pytest.mark.parametrize("daily_portfolio_cash_flow__pending", [True])
110
+ def test_rebalancing(self, daily_portfolio_cash_flow: "DailyPortfolioCashFlow"):
111
+ dpcf = daily_portfolio_cash_flow
112
+ assert dpcf.cash == Decimal(1_000_000) - dpcf.rebalancing
@@ -0,0 +1,27 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ import pytest
4
+ from django.db.utils import IntegrityError
5
+
6
+ if TYPE_CHECKING:
7
+ from wbportfolio.factories.portfolio_swing_pricings import (
8
+ PortfolioCashTargetFactory,
9
+ )
10
+ from wbportfolio.models.portfolio import Portfolio
11
+ from wbportfolio.models.portfolio_swing_pricings import PortfolioCashTarget
12
+
13
+
14
+ @pytest.mark.django_db
15
+ class TestPortfolioCashTarget:
16
+ def test_factory(self, portfolio_cash_target: "PortfolioCashTarget"):
17
+ assert portfolio_cash_target.pk is not None
18
+
19
+ def test_constraint_unique_valid_date_portfolio(
20
+ self, portfolio: "Portfolio", portfolio_cash_target_factory: "PortfolioCashTargetFactory"
21
+ ):
22
+ target1 = portfolio_cash_target_factory.create(portfolio=portfolio)
23
+ target2 = portfolio_cash_target_factory.create(portfolio=portfolio)
24
+
25
+ target2.valid_date = target1.valid_date
26
+ with pytest.raises(IntegrityError):
27
+ target2.save()
@@ -0,0 +1,42 @@
1
+ from decimal import Decimal
2
+ from typing import TYPE_CHECKING
3
+
4
+ import pytest
5
+ from django.db.utils import IntegrityError
6
+
7
+ if TYPE_CHECKING:
8
+ from wbportfolio.factories.portfolio_swing_pricings import (
9
+ PortfolioSwingPricingFactory,
10
+ )
11
+ from wbportfolio.models.portfolio import Portfolio
12
+ from wbportfolio.models.portfolio_swing_pricings import PortfolioSwingPricing
13
+
14
+
15
+ @pytest.mark.django_db
16
+ class TestPortfolioSwingPricing:
17
+ def test_factory(self, portfolio_swing_pricing: "PortfolioSwingPricing"):
18
+ assert portfolio_swing_pricing.pk is not None
19
+
20
+ def test_constraint_negative_polarity(self, portfolio_swing_pricing: "PortfolioSwingPricing"):
21
+ portfolio_swing_pricing.negative_threshold = Decimal(0.1)
22
+ portfolio_swing_pricing.negative_swing_factor = Decimal(0.1)
23
+
24
+ with pytest.raises(IntegrityError):
25
+ portfolio_swing_pricing.save()
26
+
27
+ def test_constraint_positive_polarity(self, portfolio_swing_pricing: "PortfolioSwingPricing"):
28
+ portfolio_swing_pricing.positive_threshold = Decimal(-0.1)
29
+ portfolio_swing_pricing.positive_swing_factor = Decimal(-0.1)
30
+
31
+ with pytest.raises(IntegrityError):
32
+ portfolio_swing_pricing.save()
33
+
34
+ def test_constraint_unique_valid_date_portfolio(
35
+ self, portfolio: "Portfolio", portfolio_swing_pricing_factory: "PortfolioSwingPricingFactory"
36
+ ):
37
+ swing1 = portfolio_swing_pricing_factory.create(portfolio=portfolio)
38
+ swing2 = portfolio_swing_pricing_factory.create(portfolio=portfolio)
39
+
40
+ swing2.valid_date = swing1.valid_date
41
+ with pytest.raises(IntegrityError):
42
+ swing2.save()