wbportfolio 1.43.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 (506) 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 +153 -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 +248 -0
  92. wbportfolio/filters/transactions/transactions.py +98 -0
  93. wbportfolio/fixtures/product_factsheets.yaml +1 -0
  94. wbportfolio/fixtures/wbportfolio.yaml.gz +0 -0
  95. wbportfolio/fixtures/wbrisk_management.yaml.gz +0 -0
  96. wbportfolio/import_export/__init__.py +0 -0
  97. wbportfolio/import_export/backends/__init__.py +2 -0
  98. wbportfolio/import_export/backends/refinitiv/adjustment.py +40 -0
  99. wbportfolio/import_export/backends/ubs/__init__.py +3 -0
  100. wbportfolio/import_export/backends/ubs/asset_position.py +45 -0
  101. wbportfolio/import_export/backends/ubs/fees.py +63 -0
  102. wbportfolio/import_export/backends/ubs/instrument_price.py +44 -0
  103. wbportfolio/import_export/backends/ubs/mixin.py +15 -0
  104. wbportfolio/import_export/backends/utils.py +58 -0
  105. wbportfolio/import_export/backends/wbfdm/__init__.py +2 -0
  106. wbportfolio/import_export/backends/wbfdm/adjustment.py +50 -0
  107. wbportfolio/import_export/backends/wbfdm/dividend.py +16 -0
  108. wbportfolio/import_export/backends/wbfdm/mixin.py +15 -0
  109. wbportfolio/import_export/handlers/__init__.py +0 -0
  110. wbportfolio/import_export/handlers/adjustment.py +39 -0
  111. wbportfolio/import_export/handlers/asset_position.py +167 -0
  112. wbportfolio/import_export/handlers/dividend.py +80 -0
  113. wbportfolio/import_export/handlers/fees.py +58 -0
  114. wbportfolio/import_export/handlers/portfolio_cash_flow.py +57 -0
  115. wbportfolio/import_export/handlers/register.py +43 -0
  116. wbportfolio/import_export/handlers/trade.py +191 -0
  117. wbportfolio/import_export/parsers/__init__.py +0 -0
  118. wbportfolio/import_export/parsers/default_mapping.py +30 -0
  119. wbportfolio/import_export/parsers/jpmorgan/__init__.py +0 -0
  120. wbportfolio/import_export/parsers/jpmorgan/customer_trade.py +63 -0
  121. wbportfolio/import_export/parsers/jpmorgan/fees.py +64 -0
  122. wbportfolio/import_export/parsers/jpmorgan/strategy.py +116 -0
  123. wbportfolio/import_export/parsers/jpmorgan/valuation.py +41 -0
  124. wbportfolio/import_export/parsers/leonteq/__init__.py +0 -0
  125. wbportfolio/import_export/parsers/leonteq/customer_trade.py +47 -0
  126. wbportfolio/import_export/parsers/leonteq/equity.py +81 -0
  127. wbportfolio/import_export/parsers/leonteq/fees.py +70 -0
  128. wbportfolio/import_export/parsers/leonteq/trade.py +94 -0
  129. wbportfolio/import_export/parsers/leonteq/valuation.py +39 -0
  130. wbportfolio/import_export/parsers/natixis/__init__.py +0 -0
  131. wbportfolio/import_export/parsers/natixis/customer_trade.py +62 -0
  132. wbportfolio/import_export/parsers/natixis/d1_customer_trade.py +66 -0
  133. wbportfolio/import_export/parsers/natixis/d1_equity.py +80 -0
  134. wbportfolio/import_export/parsers/natixis/d1_fees.py +58 -0
  135. wbportfolio/import_export/parsers/natixis/d1_trade.py +70 -0
  136. wbportfolio/import_export/parsers/natixis/d1_valuation.py +41 -0
  137. wbportfolio/import_export/parsers/natixis/dividend.py +53 -0
  138. wbportfolio/import_export/parsers/natixis/equity.py +60 -0
  139. wbportfolio/import_export/parsers/natixis/fees.py +53 -0
  140. wbportfolio/import_export/parsers/natixis/trade.py +63 -0
  141. wbportfolio/import_export/parsers/natixis/utils.py +76 -0
  142. wbportfolio/import_export/parsers/natixis/valuation.py +46 -0
  143. wbportfolio/import_export/parsers/refinitiv/__init__.py +0 -0
  144. wbportfolio/import_export/parsers/refinitiv/adjustment.py +24 -0
  145. wbportfolio/import_export/parsers/sg_lux/__init__.py +0 -0
  146. wbportfolio/import_export/parsers/sg_lux/custodian_positions.py +70 -0
  147. wbportfolio/import_export/parsers/sg_lux/customer_trade.py +75 -0
  148. wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py +140 -0
  149. wbportfolio/import_export/parsers/sg_lux/customer_trade_slk.py +80 -0
  150. wbportfolio/import_export/parsers/sg_lux/customer_trade_without_pw.py +57 -0
  151. wbportfolio/import_export/parsers/sg_lux/equity.py +161 -0
  152. wbportfolio/import_export/parsers/sg_lux/fees.py +56 -0
  153. wbportfolio/import_export/parsers/sg_lux/perf_fees.py +51 -0
  154. wbportfolio/import_export/parsers/sg_lux/portfolio_cash_flow.py +29 -0
  155. wbportfolio/import_export/parsers/sg_lux/portfolio_future_cash_flow.py +36 -0
  156. wbportfolio/import_export/parsers/sg_lux/registers.py +210 -0
  157. wbportfolio/import_export/parsers/sg_lux/sylk.py +248 -0
  158. wbportfolio/import_export/parsers/sg_lux/utils.py +36 -0
  159. wbportfolio/import_export/parsers/sg_lux/valuation.py +53 -0
  160. wbportfolio/import_export/parsers/societe_generale/__init__.py +0 -0
  161. wbportfolio/import_export/parsers/societe_generale/customer_trade.py +54 -0
  162. wbportfolio/import_export/parsers/societe_generale/strategy.py +94 -0
  163. wbportfolio/import_export/parsers/societe_generale/valuation.py +37 -0
  164. wbportfolio/import_export/parsers/tellco/__init__.py +0 -0
  165. wbportfolio/import_export/parsers/tellco/customer_trade.py +64 -0
  166. wbportfolio/import_export/parsers/tellco/equity.py +86 -0
  167. wbportfolio/import_export/parsers/tellco/valuation.py +52 -0
  168. wbportfolio/import_export/parsers/ubs/__init__.py +0 -0
  169. wbportfolio/import_export/parsers/ubs/api/__init__.py +0 -0
  170. wbportfolio/import_export/parsers/ubs/api/asset_position.py +106 -0
  171. wbportfolio/import_export/parsers/ubs/api/fees.py +31 -0
  172. wbportfolio/import_export/parsers/ubs/api/instrument_price.py +20 -0
  173. wbportfolio/import_export/parsers/ubs/api/utils.py +0 -0
  174. wbportfolio/import_export/parsers/ubs/customer_trade.py +60 -0
  175. wbportfolio/import_export/parsers/ubs/equity.py +97 -0
  176. wbportfolio/import_export/parsers/ubs/historical_customer_trade.py +67 -0
  177. wbportfolio/import_export/parsers/ubs/valuation.py +52 -0
  178. wbportfolio/import_export/parsers/vontobel/__init__.py +0 -0
  179. wbportfolio/import_export/parsers/vontobel/asset_position.py +97 -0
  180. wbportfolio/import_export/parsers/vontobel/customer_trade.py +54 -0
  181. wbportfolio/import_export/parsers/vontobel/historical_customer_trade.py +40 -0
  182. wbportfolio/import_export/parsers/vontobel/instrument.py +34 -0
  183. wbportfolio/import_export/parsers/vontobel/management_fees.py +86 -0
  184. wbportfolio/import_export/parsers/vontobel/performance_fees.py +35 -0
  185. wbportfolio/import_export/parsers/vontobel/trade.py +38 -0
  186. wbportfolio/import_export/parsers/vontobel/utils.py +17 -0
  187. wbportfolio/import_export/parsers/vontobel/valuation.py +29 -0
  188. wbportfolio/import_export/resources/__init__.py +0 -0
  189. wbportfolio/import_export/resources/assets.py +68 -0
  190. wbportfolio/import_export/resources/trades.py +41 -0
  191. wbportfolio/import_export/utils.py +42 -0
  192. wbportfolio/jinja2/wbportfolio/sql/aum_nnm.sql +119 -0
  193. wbportfolio/kpi_handlers/nnm.py +163 -0
  194. wbportfolio/metric/__init__.py +0 -0
  195. wbportfolio/metric/backends/__init__.py +2 -0
  196. wbportfolio/metric/backends/base.py +86 -0
  197. wbportfolio/metric/backends/constants.py +222 -0
  198. wbportfolio/metric/backends/portfolio_base.py +255 -0
  199. wbportfolio/metric/backends/portfolio_esg.py +66 -0
  200. wbportfolio/metric/tests/__init__.py +0 -0
  201. wbportfolio/metric/tests/conftest.py +4 -0
  202. wbportfolio/metric/tests/test_portfolio_base.py +135 -0
  203. wbportfolio/metric/tests/test_portfolio_esg.py +69 -0
  204. wbportfolio/migrations/0001_initial_squashed.py +13848 -0
  205. wbportfolio/migrations/0002_product_default_sub_account_squashed_0039_alter_assetallocation_company_and_more.py +3836 -0
  206. wbportfolio/migrations/0040_instrument_financial_instrument.py +26 -0
  207. wbportfolio/migrations/0041_remove_listresearch_research_ptr_and_more.py +129 -0
  208. wbportfolio/migrations/0042_instrumentlist_instrumentlistthroughmodel_and_more.py +71 -0
  209. wbportfolio/migrations/0043_alter_instrumentlistthroughmodel_options_and_more.py +238 -0
  210. wbportfolio/migrations/0044_alter_instrumentlist_identifier.py +35 -0
  211. wbportfolio/migrations/0045_alter_instrument_financial_instrument.py +26 -0
  212. wbportfolio/migrations/0046_add_product_default_account.py +166 -0
  213. wbportfolio/migrations/0047_remove_product_default_sub_account.py +14 -0
  214. wbportfolio/migrations/0048_alter_trade_status.py +29 -0
  215. wbportfolio/migrations/0049_trade_claimed_shares.py +25 -0
  216. wbportfolio/migrations/0050_fees_fee_date_fees_wbportfolio_transac_1f7a29_idx.py +44 -0
  217. wbportfolio/migrations/0051_delete_macroreview.py +11 -0
  218. wbportfolio/migrations/0052_remove_cash_instrument_ptr_and_more.py +888 -0
  219. wbportfolio/migrations/0053_remove_product_group.py +132 -0
  220. wbportfolio/migrations/0054_portfolioinstrumentpreferredclassificationthroughmodel_and_more.py +270 -0
  221. wbportfolio/migrations/0055_remove_product__custom_management_rebates_and_more.py +139 -0
  222. wbportfolio/migrations/0056_remove_companyportfoliodata_assets_under_management_currency_and_more.py +56 -0
  223. wbportfolio/migrations/0057_alter_portfolio_preferred_instrument_classifications_and_more.py +36 -0
  224. wbportfolio/migrations/0058_pmsinstrument.py +23 -0
  225. wbportfolio/migrations/0059_fees_unique_fees.py +51 -0
  226. wbportfolio/migrations/0060_alter_portfolioportfoliothroughmodel_type.py +21 -0
  227. wbportfolio/migrations/0061_portfolio_bank_accounts_product_bank_account_and_more.py +175 -0
  228. wbportfolio/migrations/0062_alter_dailyportfoliocashflow_options.py +20 -0
  229. wbportfolio/migrations/0063_accountreconciliation_accountreconciliationline_and_more.py +133 -0
  230. wbportfolio/migrations/0064_alter_portfolio_managers_portfolio_is_tracked_and_more.py +40 -0
  231. wbportfolio/migrations/0065_alter_portfolio_managers_claim_as_shares_and_more.py +73 -0
  232. wbportfolio/migrations/0066_assetposition_initial_shares_at_custodian_and_more.py +108 -0
  233. wbportfolio/migrations/0067_assetposition_unique_asset_position.py +77 -0
  234. wbportfolio/migrations/0068_trade_internal_trade_trade_marked_as_internal_and_more.py +59 -0
  235. wbportfolio/migrations/0069_remove_portfolio_is_invested_and_more.py +56 -0
  236. wbportfolio/migrations/0070_remove_assetposition_unique_asset_position_and_more.py +82 -0
  237. wbportfolio/migrations/0071_alter_trade_options_alter_trade_order.py +22 -0
  238. wbportfolio/migrations/__init__.py +0 -0
  239. wbportfolio/models/__init__.py +26 -0
  240. wbportfolio/models/adjustments.py +246 -0
  241. wbportfolio/models/asset.py +869 -0
  242. wbportfolio/models/custodians.py +101 -0
  243. wbportfolio/models/indexes.py +33 -0
  244. wbportfolio/models/llm/wbcrm/analyze_relationship.py +58 -0
  245. wbportfolio/models/mixins/__init__.py +0 -0
  246. wbportfolio/models/mixins/instruments.py +127 -0
  247. wbportfolio/models/mixins/liquidity_stress_test.py +1307 -0
  248. wbportfolio/models/portfolio.py +1039 -0
  249. wbportfolio/models/portfolio_cash_flow.py +167 -0
  250. wbportfolio/models/portfolio_cash_targets.py +46 -0
  251. wbportfolio/models/portfolio_relationship.py +135 -0
  252. wbportfolio/models/portfolio_swing_pricings.py +51 -0
  253. wbportfolio/models/product_groups.py +230 -0
  254. wbportfolio/models/products.py +569 -0
  255. wbportfolio/models/reconciliations/__init__.py +2 -0
  256. wbportfolio/models/reconciliations/account_reconciliation_lines.py +192 -0
  257. wbportfolio/models/reconciliations/account_reconciliations.py +102 -0
  258. wbportfolio/models/reconciliations/reconciliations.py +25 -0
  259. wbportfolio/models/registers.py +132 -0
  260. wbportfolio/models/roles.py +208 -0
  261. wbportfolio/models/synchronization/__init__.py +3 -0
  262. wbportfolio/models/synchronization/portfolio_synchronization.py +292 -0
  263. wbportfolio/models/synchronization/price_computation.py +200 -0
  264. wbportfolio/models/synchronization/synchronization.py +188 -0
  265. wbportfolio/models/transactions/__init__.py +7 -0
  266. wbportfolio/models/transactions/claim.py +634 -0
  267. wbportfolio/models/transactions/dividends.py +31 -0
  268. wbportfolio/models/transactions/expiry.py +7 -0
  269. wbportfolio/models/transactions/fees.py +153 -0
  270. wbportfolio/models/transactions/trade_proposals.py +502 -0
  271. wbportfolio/models/transactions/trades.py +704 -0
  272. wbportfolio/models/transactions/transactions.py +211 -0
  273. wbportfolio/models/utils.py +12 -0
  274. wbportfolio/permissions.py +13 -0
  275. wbportfolio/pms/__init__.py +0 -0
  276. wbportfolio/pms/statistics/__init__.py +0 -0
  277. wbportfolio/pms/trading/__init__.py +1 -0
  278. wbportfolio/pms/trading/handler.py +164 -0
  279. wbportfolio/pms/typing.py +194 -0
  280. wbportfolio/preferences.py +6 -0
  281. wbportfolio/reports/__init__.py +0 -0
  282. wbportfolio/reports/monthly_position_report.py +74 -0
  283. wbportfolio/risk_management/__init__.py +0 -0
  284. wbportfolio/risk_management/backends/__init__.py +11 -0
  285. wbportfolio/risk_management/backends/accounts.py +166 -0
  286. wbportfolio/risk_management/backends/controversy_portfolio.py +63 -0
  287. wbportfolio/risk_management/backends/exposure_portfolio.py +203 -0
  288. wbportfolio/risk_management/backends/instrument_list_portfolio.py +89 -0
  289. wbportfolio/risk_management/backends/liquidity_risk.py +86 -0
  290. wbportfolio/risk_management/backends/liquidity_stress_instrument.py +86 -0
  291. wbportfolio/risk_management/backends/mixins.py +220 -0
  292. wbportfolio/risk_management/backends/product_integrity.py +111 -0
  293. wbportfolio/risk_management/backends/stop_loss_instrument.py +24 -0
  294. wbportfolio/risk_management/backends/stop_loss_portfolio.py +36 -0
  295. wbportfolio/risk_management/backends/ucits_portfolio.py +63 -0
  296. wbportfolio/risk_management/tests/__init__.py +0 -0
  297. wbportfolio/risk_management/tests/conftest.py +15 -0
  298. wbportfolio/risk_management/tests/test_accounts.py +98 -0
  299. wbportfolio/risk_management/tests/test_controversy_portfolio.py +33 -0
  300. wbportfolio/risk_management/tests/test_exposure_portfolio.py +94 -0
  301. wbportfolio/risk_management/tests/test_instrument_list_portfolio.py +60 -0
  302. wbportfolio/risk_management/tests/test_liquidity_risk.py +47 -0
  303. wbportfolio/risk_management/tests/test_product_integrity.py +55 -0
  304. wbportfolio/risk_management/tests/test_stop_loss_instrument.py +110 -0
  305. wbportfolio/risk_management/tests/test_stop_loss_portfolio.py +119 -0
  306. wbportfolio/risk_management/tests/test_ucits_portfolio.py +39 -0
  307. wbportfolio/serializers/__init__.py +42 -0
  308. wbportfolio/serializers/adjustments.py +24 -0
  309. wbportfolio/serializers/assets.py +166 -0
  310. wbportfolio/serializers/custodians.py +26 -0
  311. wbportfolio/serializers/portfolio_cash_flow.py +48 -0
  312. wbportfolio/serializers/portfolio_cash_targets.py +20 -0
  313. wbportfolio/serializers/portfolio_relationship.py +53 -0
  314. wbportfolio/serializers/portfolio_swing_pricing.py +20 -0
  315. wbportfolio/serializers/portfolios.py +143 -0
  316. wbportfolio/serializers/positions.py +76 -0
  317. wbportfolio/serializers/product_group.py +88 -0
  318. wbportfolio/serializers/products.py +331 -0
  319. wbportfolio/serializers/reconciliations.py +173 -0
  320. wbportfolio/serializers/registers.py +72 -0
  321. wbportfolio/serializers/roles.py +60 -0
  322. wbportfolio/serializers/signals.py +157 -0
  323. wbportfolio/serializers/synchronization.py +18 -0
  324. wbportfolio/serializers/transactions/__init__.py +24 -0
  325. wbportfolio/serializers/transactions/claim.py +310 -0
  326. wbportfolio/serializers/transactions/dividends.py +18 -0
  327. wbportfolio/serializers/transactions/expiry.py +18 -0
  328. wbportfolio/serializers/transactions/fees.py +32 -0
  329. wbportfolio/serializers/transactions/trades.py +315 -0
  330. wbportfolio/serializers/transactions/transactions.py +84 -0
  331. wbportfolio/static/wbportfolio/css/macro_review.css +17 -0
  332. wbportfolio/static/wbportfolio/markdown/documentation/account_holding_reconciliation.md +16 -0
  333. wbportfolio/static/wbportfolio/markdown/documentation/aggregate_asset_position_liquidity.md +25 -0
  334. wbportfolio/static/wbportfolio/markdown/documentation/company.md +78 -0
  335. wbportfolio/static/wbportfolio/markdown/documentation/earnings_instrument.md +14 -0
  336. wbportfolio/static/wbportfolio/markdown/documentation/financial_analysis_instrument_ratios.md +94 -0
  337. wbportfolio/static/wbportfolio/markdown/documentation/financial_statistics.md +44 -0
  338. wbportfolio/static/wbportfolio/markdown/documentation/person.md +70 -0
  339. wbportfolio/tasks.py +125 -0
  340. wbportfolio/templates/portfolio/email/customer_report.html +6 -0
  341. wbportfolio/templates/portfolio/email/customer_trade_notification.html +26 -0
  342. wbportfolio/templates/portfolio/email/email_base_template.html +420 -0
  343. wbportfolio/templates/portfolio/email/rebalancing_report.html +34 -0
  344. wbportfolio/templates/portfolio/macro/macro_review.html +88 -0
  345. wbportfolio/tests/__init__.py +0 -0
  346. wbportfolio/tests/conftest.py +164 -0
  347. wbportfolio/tests/models/__init__.py +0 -0
  348. wbportfolio/tests/models/test_account_reconciliation.py +191 -0
  349. wbportfolio/tests/models/test_assets.py +193 -0
  350. wbportfolio/tests/models/test_custodians.py +12 -0
  351. wbportfolio/tests/models/test_customer_trades.py +113 -0
  352. wbportfolio/tests/models/test_dividends.py +7 -0
  353. wbportfolio/tests/models/test_imports.py +192 -0
  354. wbportfolio/tests/models/test_instrument_mixins.py +48 -0
  355. wbportfolio/tests/models/test_merge.py +133 -0
  356. wbportfolio/tests/models/test_portfolio_cash_flow.py +112 -0
  357. wbportfolio/tests/models/test_portfolio_cash_targets.py +27 -0
  358. wbportfolio/tests/models/test_portfolio_swing_pricings.py +42 -0
  359. wbportfolio/tests/models/test_portfolios.py +676 -0
  360. wbportfolio/tests/models/test_product_groups.py +80 -0
  361. wbportfolio/tests/models/test_products.py +187 -0
  362. wbportfolio/tests/models/test_roles.py +82 -0
  363. wbportfolio/tests/models/test_splits.py +233 -0
  364. wbportfolio/tests/models/test_synchronization.py +617 -0
  365. wbportfolio/tests/models/transactions/__init__.py +0 -0
  366. wbportfolio/tests/models/transactions/test_claim.py +129 -0
  367. wbportfolio/tests/models/transactions/test_fees.py +65 -0
  368. wbportfolio/tests/models/transactions/test_trades.py +204 -0
  369. wbportfolio/tests/models/utils.py +13 -0
  370. wbportfolio/tests/serializers/__init__.py +0 -0
  371. wbportfolio/tests/serializers/test_claims.py +21 -0
  372. wbportfolio/tests/signals.py +151 -0
  373. wbportfolio/tests/tests.py +31 -0
  374. wbportfolio/tests/viewsets/__init__.py +0 -0
  375. wbportfolio/tests/viewsets/test_assets.py +67 -0
  376. wbportfolio/tests/viewsets/test_performances.py +72 -0
  377. wbportfolio/tests/viewsets/test_products.py +92 -0
  378. wbportfolio/tests/viewsets/transactions/__init__.py +0 -0
  379. wbportfolio/tests/viewsets/transactions/test_claims.py +146 -0
  380. wbportfolio/urls.py +247 -0
  381. wbportfolio/utils.py +30 -0
  382. wbportfolio/viewsets/__init__.py +57 -0
  383. wbportfolio/viewsets/adjustments.py +46 -0
  384. wbportfolio/viewsets/assets.py +562 -0
  385. wbportfolio/viewsets/assets_and_net_new_money_progression.py +117 -0
  386. wbportfolio/viewsets/charts/__init__.py +1 -0
  387. wbportfolio/viewsets/charts/assets.py +247 -0
  388. wbportfolio/viewsets/configs/__init__.py +6 -0
  389. wbportfolio/viewsets/configs/buttons/__init__.py +23 -0
  390. wbportfolio/viewsets/configs/buttons/adjustments.py +13 -0
  391. wbportfolio/viewsets/configs/buttons/assets.py +145 -0
  392. wbportfolio/viewsets/configs/buttons/claims.py +83 -0
  393. wbportfolio/viewsets/configs/buttons/custodians.py +76 -0
  394. wbportfolio/viewsets/configs/buttons/fees.py +14 -0
  395. wbportfolio/viewsets/configs/buttons/mixins.py +88 -0
  396. wbportfolio/viewsets/configs/buttons/portfolios.py +115 -0
  397. wbportfolio/viewsets/configs/buttons/products.py +41 -0
  398. wbportfolio/viewsets/configs/buttons/reconciliations.py +65 -0
  399. wbportfolio/viewsets/configs/buttons/registers.py +11 -0
  400. wbportfolio/viewsets/configs/buttons/signals.py +68 -0
  401. wbportfolio/viewsets/configs/buttons/trade_proposals.py +25 -0
  402. wbportfolio/viewsets/configs/buttons/trades.py +144 -0
  403. wbportfolio/viewsets/configs/display/__init__.py +61 -0
  404. wbportfolio/viewsets/configs/display/adjustments.py +81 -0
  405. wbportfolio/viewsets/configs/display/assets.py +265 -0
  406. wbportfolio/viewsets/configs/display/claim.py +299 -0
  407. wbportfolio/viewsets/configs/display/custodians.py +24 -0
  408. wbportfolio/viewsets/configs/display/esg.py +88 -0
  409. wbportfolio/viewsets/configs/display/fees.py +133 -0
  410. wbportfolio/viewsets/configs/display/portfolio_cash_flow.py +103 -0
  411. wbportfolio/viewsets/configs/display/portfolio_relationship.py +38 -0
  412. wbportfolio/viewsets/configs/display/portfolios.py +125 -0
  413. wbportfolio/viewsets/configs/display/positions.py +75 -0
  414. wbportfolio/viewsets/configs/display/product_groups.py +54 -0
  415. wbportfolio/viewsets/configs/display/product_performance.py +241 -0
  416. wbportfolio/viewsets/configs/display/products.py +249 -0
  417. wbportfolio/viewsets/configs/display/reconciliations.py +151 -0
  418. wbportfolio/viewsets/configs/display/registers.py +71 -0
  419. wbportfolio/viewsets/configs/display/roles.py +49 -0
  420. wbportfolio/viewsets/configs/display/trade_proposals.py +97 -0
  421. wbportfolio/viewsets/configs/display/trades.py +359 -0
  422. wbportfolio/viewsets/configs/display/transactions.py +55 -0
  423. wbportfolio/viewsets/configs/endpoints/__init__.py +75 -0
  424. wbportfolio/viewsets/configs/endpoints/adjustments.py +17 -0
  425. wbportfolio/viewsets/configs/endpoints/assets.py +115 -0
  426. wbportfolio/viewsets/configs/endpoints/claim.py +106 -0
  427. wbportfolio/viewsets/configs/endpoints/custodians.py +6 -0
  428. wbportfolio/viewsets/configs/endpoints/esg.py +14 -0
  429. wbportfolio/viewsets/configs/endpoints/fees.py +26 -0
  430. wbportfolio/viewsets/configs/endpoints/portfolio_relationship.py +23 -0
  431. wbportfolio/viewsets/configs/endpoints/portfolios.py +43 -0
  432. wbportfolio/viewsets/configs/endpoints/positions.py +18 -0
  433. wbportfolio/viewsets/configs/endpoints/product_groups.py +11 -0
  434. wbportfolio/viewsets/configs/endpoints/product_performance.py +29 -0
  435. wbportfolio/viewsets/configs/endpoints/products.py +37 -0
  436. wbportfolio/viewsets/configs/endpoints/reconciliations.py +31 -0
  437. wbportfolio/viewsets/configs/endpoints/roles.py +9 -0
  438. wbportfolio/viewsets/configs/endpoints/trade_proposals.py +17 -0
  439. wbportfolio/viewsets/configs/endpoints/trades.py +82 -0
  440. wbportfolio/viewsets/configs/endpoints/transactions.py +17 -0
  441. wbportfolio/viewsets/configs/menu/__init__.py +30 -0
  442. wbportfolio/viewsets/configs/menu/adjustments.py +8 -0
  443. wbportfolio/viewsets/configs/menu/assets.py +8 -0
  444. wbportfolio/viewsets/configs/menu/claim.py +41 -0
  445. wbportfolio/viewsets/configs/menu/custodians.py +11 -0
  446. wbportfolio/viewsets/configs/menu/fees.py +13 -0
  447. wbportfolio/viewsets/configs/menu/instrument_prices.py +10 -0
  448. wbportfolio/viewsets/configs/menu/portfolio_cash_flow.py +8 -0
  449. wbportfolio/viewsets/configs/menu/portfolios.py +15 -0
  450. wbportfolio/viewsets/configs/menu/positions.py +14 -0
  451. wbportfolio/viewsets/configs/menu/product_groups.py +10 -0
  452. wbportfolio/viewsets/configs/menu/product_performance.py +25 -0
  453. wbportfolio/viewsets/configs/menu/products.py +15 -0
  454. wbportfolio/viewsets/configs/menu/reconciliations.py +7 -0
  455. wbportfolio/viewsets/configs/menu/registers.py +10 -0
  456. wbportfolio/viewsets/configs/menu/roles.py +16 -0
  457. wbportfolio/viewsets/configs/menu/trades.py +18 -0
  458. wbportfolio/viewsets/configs/menu/transactions.py +8 -0
  459. wbportfolio/viewsets/configs/previews/__init__.py +1 -0
  460. wbportfolio/viewsets/configs/previews/portfolios.py +21 -0
  461. wbportfolio/viewsets/configs/titles/__init__.py +65 -0
  462. wbportfolio/viewsets/configs/titles/adjustments.py +19 -0
  463. wbportfolio/viewsets/configs/titles/assets.py +57 -0
  464. wbportfolio/viewsets/configs/titles/assets_and_net_new_money_progression.py +6 -0
  465. wbportfolio/viewsets/configs/titles/claim.py +81 -0
  466. wbportfolio/viewsets/configs/titles/custodians.py +12 -0
  467. wbportfolio/viewsets/configs/titles/esg.py +10 -0
  468. wbportfolio/viewsets/configs/titles/fees.py +25 -0
  469. wbportfolio/viewsets/configs/titles/instrument_prices.py +20 -0
  470. wbportfolio/viewsets/configs/titles/portfolios.py +32 -0
  471. wbportfolio/viewsets/configs/titles/positions.py +11 -0
  472. wbportfolio/viewsets/configs/titles/product_groups.py +12 -0
  473. wbportfolio/viewsets/configs/titles/product_performance.py +16 -0
  474. wbportfolio/viewsets/configs/titles/products.py +6 -0
  475. wbportfolio/viewsets/configs/titles/registers.py +12 -0
  476. wbportfolio/viewsets/configs/titles/roles.py +23 -0
  477. wbportfolio/viewsets/configs/titles/trades.py +51 -0
  478. wbportfolio/viewsets/configs/titles/transactions.py +8 -0
  479. wbportfolio/viewsets/custodians.py +66 -0
  480. wbportfolio/viewsets/esg.py +165 -0
  481. wbportfolio/viewsets/mixins.py +48 -0
  482. wbportfolio/viewsets/portfolio_cash_flow.py +31 -0
  483. wbportfolio/viewsets/portfolio_cash_targets.py +8 -0
  484. wbportfolio/viewsets/portfolio_relationship.py +46 -0
  485. wbportfolio/viewsets/portfolio_swing_pricing.py +8 -0
  486. wbportfolio/viewsets/portfolios.py +154 -0
  487. wbportfolio/viewsets/positions.py +292 -0
  488. wbportfolio/viewsets/product_groups.py +84 -0
  489. wbportfolio/viewsets/product_performance.py +646 -0
  490. wbportfolio/viewsets/products.py +529 -0
  491. wbportfolio/viewsets/reconciliations.py +160 -0
  492. wbportfolio/viewsets/registers.py +75 -0
  493. wbportfolio/viewsets/roles.py +44 -0
  494. wbportfolio/viewsets/signals.py +42 -0
  495. wbportfolio/viewsets/synchronization.py +25 -0
  496. wbportfolio/viewsets/transactions/__init__.py +40 -0
  497. wbportfolio/viewsets/transactions/claim.py +933 -0
  498. wbportfolio/viewsets/transactions/fees.py +190 -0
  499. wbportfolio/viewsets/transactions/mixins.py +19 -0
  500. wbportfolio/viewsets/transactions/trade_proposals.py +93 -0
  501. wbportfolio/viewsets/transactions/trades.py +395 -0
  502. wbportfolio/viewsets/transactions/transactions.py +123 -0
  503. wbportfolio-1.43.1.dist-info/METADATA +22 -0
  504. wbportfolio-1.43.1.dist-info/RECORD +506 -0
  505. wbportfolio-1.43.1.dist-info/WHEEL +5 -0
  506. wbportfolio-1.43.1.dist-info/licenses/LICENSE +4 -0
@@ -0,0 +1,255 @@
1
+ from contextlib import suppress
2
+ from datetime import date
3
+ from typing import Generator
4
+
5
+ import pandas as pd
6
+ from django.db.models import Q
7
+ from django.utils.functional import cached_property
8
+ from wbfdm.contrib.metric.backends.base import Metric
9
+ from wbfdm.contrib.metric.decorators import register
10
+ from wbfdm.enums import Financial, MarketData
11
+ from wbfdm.models import Instrument
12
+ from wbportfolio.models import Portfolio
13
+
14
+ from .base import PortfolioMetricBaseBackend
15
+ from .constants import (
16
+ PORTFOLIO_CAPITAL_EMPLOYED,
17
+ PORTFOLIO_EBIT,
18
+ PORTFOLIO_LIABILITIES,
19
+ PORTFOLIO_ROCE,
20
+ PORTFOLIO_TOTAL_ASSETS,
21
+ )
22
+
23
+
24
+ class DataLoader:
25
+ def __init__(
26
+ self, portfolio: Portfolio, val_date: date, past_offset: int = 4, forecast_offset: int = 4, freq: str = "YE"
27
+ ):
28
+ self.portfolio = portfolio
29
+ self.val_date = val_date
30
+ self.instruments = Instrument.objects.filter(id__in=self.base_df.index)
31
+ self.past_offset = past_offset
32
+ self.forecast_offset = forecast_offset
33
+ self.pivot_dates = list(
34
+ pd.date_range(end=self.val_date, periods=self.past_offset, freq=freq, inclusive="left")
35
+ ) + list(pd.date_range(start=self.val_date, periods=self.past_offset, freq=freq, inclusive="right"))
36
+ self.columns_map = {
37
+ self.val_date.year + offset: f"fy{offset + 1}".replace("-", "_")
38
+ for offset in range(-1 * self.past_offset, self.forecast_offset)
39
+ }
40
+ self.from_year = self.val_date.year - self.past_offset
41
+ self.to_year = self.val_date.year + self.forecast_offset - 1
42
+
43
+ self.empty_series = pd.Series(dtype="float64")
44
+
45
+ def _get_financial_df(self, financial: Financial, target_currency: str = "USD", **kwargs) -> pd.DataFrame:
46
+ """
47
+ Private helper method to gather necessary financial data from the finanical dataloader and pivot it into Instrument*Years format
48
+
49
+ Args:
50
+ financial: Financial Metric
51
+ target_currency: Target currency
52
+ **kwargs: fdm datalaoder keyword argument
53
+
54
+ Returns:
55
+ A dataframe whose index is the instrument ids and columns are the years offsets
56
+ """
57
+
58
+ df = pd.DataFrame(
59
+ self.instruments.dl.financials(
60
+ values=[financial],
61
+ from_year=self.from_year,
62
+ to_year=self.to_year,
63
+ target_currency=target_currency,
64
+ **kwargs,
65
+ )
66
+ )
67
+ if df.empty:
68
+ raise ValueError(f"No financial data for {financial.value}")
69
+ return df.pivot_table(index="instrument_id", values="value", columns="year", aggfunc="first")
70
+
71
+ # Data Input
72
+ @cached_property
73
+ def market_capitalization_df(self) -> pd.DataFrame:
74
+ df_list = []
75
+ for pivot_date in self.pivot_dates:
76
+ try:
77
+ mkt_caps = pd.DataFrame(
78
+ self.instruments.dl.market_data(
79
+ values=[MarketData.MARKET_CAPITALIZATION],
80
+ from_date=(pivot_date - pd.tseries.offsets.BDay(3)).date(),
81
+ to_date=pivot_date,
82
+ target_currency="USD",
83
+ )
84
+ )
85
+ res = (
86
+ mkt_caps[["instrument_id", "market_capitalization"]]
87
+ .groupby("instrument_id")
88
+ .last()["market_capitalization"]
89
+ ).astype(float)
90
+ except KeyError:
91
+ res = self.empty_series
92
+ res = res.rename(pivot_date.year)
93
+ df_list.append(res)
94
+ return pd.concat(df_list, axis=1).ffill(axis=1)
95
+
96
+ @cached_property
97
+ def ebit_usd_df(self) -> pd.DataFrame:
98
+ return self._get_financial_df(Financial.EBIT)
99
+
100
+ @cached_property
101
+ def total_assets_usd_df(self) -> pd.DataFrame:
102
+ return self._get_financial_df(Financial.TOTAL_ASSETS)
103
+
104
+ @cached_property
105
+ def liabilities_usd_df(self) -> pd.DataFrame:
106
+ return self._get_financial_df(Financial.CURRENT_LIABILITIES)
107
+
108
+ # Generate basic portfolio data as a dataframe
109
+ @cached_property
110
+ def base_df(self) -> pd.DataFrame:
111
+ df = pd.DataFrame(
112
+ self.portfolio.assets.filter(date=self.val_date)
113
+ .exclude(Q(underlying_instrument__is_cash=True) | Q(underlying_instrument__is_cash_equivalent=True))
114
+ .values("underlying_instrument", "shares", "price_fx_usd", "total_value_fx_usd", "weighting"),
115
+ columns=["underlying_instrument", "shares", "price_fx_usd", "total_value_fx_usd", "weighting"],
116
+ )
117
+ df = (
118
+ df.groupby("underlying_instrument")
119
+ .agg(
120
+ {
121
+ "shares": "sum",
122
+ "price_fx_usd": "first",
123
+ "total_value_fx_usd": "sum",
124
+ "weighting": "sum",
125
+ }
126
+ )
127
+ .astype(float)
128
+ )
129
+ df["weighting"] = df["weighting"] / df["weighting"].sum()
130
+ df.index = df.index.rename("instrument_id")
131
+ return df
132
+
133
+ # Portfolio Metrics
134
+
135
+ @cached_property
136
+ def portfolio_ownership_df(self) -> pd.DataFrame:
137
+ return (1.0 / self.market_capitalization_df).multiply(self.base_df.total_value_fx_usd, axis=0)
138
+
139
+ @cached_property
140
+ def portfolio_ebit_usd_df(self) -> pd.DataFrame:
141
+ df = self.portfolio_ownership_df * self.ebit_usd_df
142
+ df.aggregate = df.sum(axis=0)
143
+ return df
144
+
145
+ @cached_property
146
+ def portfolio_total_assets_usd_df(self) -> pd.DataFrame:
147
+ df = self.portfolio_ownership_df * self.total_assets_usd_df
148
+ df.aggregate = df.sum(axis=0)
149
+ return df
150
+
151
+ @cached_property
152
+ def portfolio_liabilities_usd_df(self) -> pd.DataFrame:
153
+ df = self.portfolio_ownership_df * self.liabilities_usd_df
154
+ df.aggregate = df.sum(axis=0)
155
+ return df
156
+
157
+ @cached_property
158
+ def portfolio_aggregation_capital_employed(self) -> pd.Series:
159
+ return self.portfolio_total_assets_usd_df.aggregate - self.portfolio_liabilities_usd_df.aggregate
160
+
161
+ @cached_property
162
+ def portfolio_aggregation_roce(self) -> pd.Series:
163
+ return self.portfolio_ebit_usd_df.aggregate / self.portfolio_aggregation_capital_employed.rolling(2).mean()
164
+
165
+
166
+ @register()
167
+ class PortfolioBaseMetricBackend(PortfolioMetricBaseBackend):
168
+ portfolio_ebit = PORTFOLIO_EBIT
169
+ portfolio_total_assets = PORTFOLIO_TOTAL_ASSETS
170
+ portfolio_liabilities = PORTFOLIO_LIABILITIES
171
+ portfolio_capital_employed = PORTFOLIO_CAPITAL_EMPLOYED
172
+ portfolio_roce = PORTFOLIO_ROCE
173
+
174
+ keys = [
175
+ PORTFOLIO_EBIT,
176
+ PORTFOLIO_TOTAL_ASSETS,
177
+ PORTFOLIO_LIABILITIES,
178
+ PORTFOLIO_CAPITAL_EMPLOYED,
179
+ PORTFOLIO_ROCE,
180
+ ]
181
+
182
+ def compute_metrics(self, basket: Portfolio) -> Generator[Metric, None, None]:
183
+ val_date = self._get_valid_date(basket)
184
+ dataloader = DataLoader(basket, val_date)
185
+
186
+ # Load all necessary metrics into a Metric DTO from the dataloader
187
+ with suppress(ValueError):
188
+ portfolio_ebit_metrics = self.convert_df_into_metrics(
189
+ dataloader.portfolio_ebit_usd_df,
190
+ "portfolio_ebit",
191
+ dataloader.portfolio.id,
192
+ val_date,
193
+ columns_map=dataloader.columns_map,
194
+ )
195
+ portfolio_aggregation_ebit_metrics = self.convert_df_into_metrics(
196
+ dataloader.portfolio_ebit_usd_df.aggregate,
197
+ "portfolio_ebit",
198
+ dataloader.portfolio.id,
199
+ val_date,
200
+ columns_map=dataloader.columns_map,
201
+ dependency_metrics=portfolio_ebit_metrics,
202
+ )
203
+
204
+ portfolio_total_assets_metrics = self.convert_df_into_metrics(
205
+ dataloader.portfolio_total_assets_usd_df,
206
+ "portfolio_total_assets",
207
+ dataloader.portfolio.id,
208
+ val_date,
209
+ columns_map=dataloader.columns_map,
210
+ )
211
+ portfolio_aggregation_total_assets_metrics = self.convert_df_into_metrics(
212
+ dataloader.portfolio_total_assets_usd_df.aggregate,
213
+ "portfolio_total_assets",
214
+ dataloader.portfolio.id,
215
+ val_date,
216
+ columns_map=dataloader.columns_map,
217
+ dependency_metrics=portfolio_total_assets_metrics,
218
+ )
219
+
220
+ portfolio_liabilities_metrics = self.convert_df_into_metrics(
221
+ dataloader.portfolio_liabilities_usd_df,
222
+ "portfolio_liabilities",
223
+ dataloader.portfolio.id,
224
+ val_date,
225
+ columns_map=dataloader.columns_map,
226
+ )
227
+ portfolio_aggregation_liabilities_metrics = self.convert_df_into_metrics(
228
+ dataloader.portfolio_liabilities_usd_df.aggregate,
229
+ "portfolio_liabilities",
230
+ dataloader.portfolio.id,
231
+ val_date,
232
+ columns_map=dataloader.columns_map,
233
+ dependency_metrics=portfolio_liabilities_metrics,
234
+ )
235
+
236
+ portfolio_aggregation_capital_metrics = self.convert_df_into_metrics(
237
+ dataloader.portfolio_aggregation_capital_employed,
238
+ "portfolio_capital_employed",
239
+ dataloader.portfolio.id,
240
+ val_date,
241
+ columns_map=dataloader.columns_map,
242
+ dependency_metrics=portfolio_aggregation_total_assets_metrics
243
+ + portfolio_aggregation_liabilities_metrics,
244
+ )
245
+
246
+ portfolio_aggregation_roce_metrics = self.convert_df_into_metrics(
247
+ dataloader.portfolio_aggregation_roce,
248
+ "portfolio_roce",
249
+ dataloader.portfolio.id,
250
+ val_date,
251
+ columns_map=dataloader.columns_map,
252
+ dependency_metrics=portfolio_aggregation_ebit_metrics + portfolio_aggregation_capital_metrics,
253
+ )
254
+
255
+ yield from portfolio_aggregation_roce_metrics
@@ -0,0 +1,66 @@
1
+ from typing import Generator
2
+
3
+ import pandas as pd
4
+ from wbfdm.analysis.esg.enums import ESGAggregation
5
+ from wbfdm.analysis.esg.esg_analysis import DataLoader, get_esg_df
6
+ from wbfdm.contrib.metric.decorators import register
7
+ from wbfdm.contrib.metric.dto import Metric, MetricField, MetricKey
8
+ from wbportfolio.models import Portfolio
9
+
10
+ from .base import PortfolioMetricBaseBackend
11
+ from .portfolio_base import DataLoader as PortfolioDataLoader
12
+
13
+
14
+ def _generate_metric_key_from_enum():
15
+ metric_keys = []
16
+ for key, label in ESGAggregation.choices():
17
+ metric_keys.append(
18
+ MetricKey(
19
+ key=f"portfolio_esg_{key.lower()}",
20
+ label=label,
21
+ subfields=[
22
+ MetricField(key="score", label="Score"),
23
+ ],
24
+ )
25
+ )
26
+ return metric_keys
27
+
28
+
29
+ PORTFOLIO_ESG_KEYS = _generate_metric_key_from_enum()
30
+
31
+
32
+ @register()
33
+ class PortfolioESGMetricBackend(PortfolioMetricBaseBackend):
34
+ keys = PORTFOLIO_ESG_KEYS
35
+
36
+ def _get_metrics_for_esg_aggregation(
37
+ self, instruments, weights: pd.Series, total_value_fx_usd: pd.Series, esg_aggregation: ESGAggregation
38
+ ) -> pd.Series:
39
+ dl = DataLoader(
40
+ weights, get_esg_df(instruments, esg_aggregation.get_esg_code()), self.val_date, total_value_fx_usd
41
+ )
42
+ df = dl.compute(esg_aggregation)
43
+
44
+ return df.rename("score").to_frame()
45
+
46
+ def compute_metrics(self, basket: Portfolio) -> Generator[Metric, None, None]:
47
+ val_date = self._get_valid_date(basket)
48
+ portfolio_dataloader = PortfolioDataLoader(basket, val_date)
49
+ base_df = portfolio_dataloader.base_df
50
+
51
+ if not base_df.empty:
52
+ for esg in ESGAggregation:
53
+ df = self._get_metrics_for_esg_aggregation(
54
+ portfolio_dataloader.instruments, base_df["weighting"], base_df["total_value_fx_usd"], esg
55
+ )
56
+ df_aggregated = df.sum(axis=0)
57
+ metrics = self.convert_df_into_metrics(df, f"portfolio_esg_{esg.name.lower()}", basket.id, val_date)
58
+ aggregated_metrics = self.convert_df_into_metrics(
59
+ df_aggregated,
60
+ f"portfolio_esg_{esg.name.lower()}",
61
+ basket.id,
62
+ val_date,
63
+ dependency_metrics=metrics,
64
+ )
65
+ yield from metrics
66
+ yield from aggregated_metrics
File without changes
@@ -0,0 +1,4 @@
1
+ from wbfdm.contrib.metric.factories import InstrumentMetricFactory
2
+ from wbportfolio.tests.conftest import * # noqa
3
+
4
+ register(InstrumentMetricFactory)
@@ -0,0 +1,135 @@
1
+ from unittest.mock import patch
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ import pytest
6
+ from django.db.models import Sum
7
+ from faker import Faker
8
+ from wbfdm.dataloaders.proxies import InstrumentDataloaderProxy
9
+ from wbfdm.enums import Financial
10
+ from wbportfolio.factories import AssetPositionFactory, PortfolioFactory
11
+ from wbportfolio.models import AssetPosition
12
+
13
+ from ..backends.portfolio_base import DataLoader
14
+
15
+ fake = Faker()
16
+
17
+
18
+ @pytest.mark.django_db
19
+ class TestPortfolioBaseDataloader:
20
+ @pytest.fixture
21
+ def dataloader(self, weekday):
22
+ portfolio = PortfolioFactory.create()
23
+ AssetPositionFactory.create_batch(5, portfolio=portfolio, date=weekday, is_estimated=False)
24
+ dataloader = DataLoader(portfolio, weekday)
25
+ base_df = dataloader.base_df
26
+ columns = dataloader.columns_map.keys()
27
+ dataloader.__dict__["market_capitalization_df"] = pd.DataFrame(
28
+ np.random.randint(1e6, 1e9, size=(base_df.shape[0], len(columns))), index=base_df.index, columns=columns
29
+ )
30
+ dataloader.__dict__["ebit_usd_df"] = pd.DataFrame(
31
+ np.random.randint(100, 100000, size=(base_df.shape[0], len(columns))), index=base_df.index, columns=columns
32
+ )
33
+ dataloader.__dict__["total_assets_usd_df"] = pd.DataFrame(
34
+ np.random.randint(1e6, 1e9, size=(base_df.shape[0], len(columns))), index=base_df.index, columns=columns
35
+ )
36
+ dataloader.__dict__["liabilities_usd_df"] = pd.DataFrame(
37
+ np.random.randint(1e6, 1e9, size=(base_df.shape[0], len(columns))), index=base_df.index, columns=columns
38
+ )
39
+ return dataloader
40
+
41
+ @patch.object(InstrumentDataloaderProxy, "financials")
42
+ def test__get_financial_df(self, mock_fct, dataloader: DataLoader):
43
+ return_value = []
44
+ base_df = dataloader.base_df
45
+ for pivot_date in dataloader.pivot_dates:
46
+ for instrument_id in base_df.index:
47
+ return_value.append({"instrument_id": instrument_id, "year": pivot_date.year, "value": fake.pyfloat()})
48
+ mock_fct.return_value = return_value
49
+ res = dataloader._get_financial_df(Financial.EBIT)
50
+ pd.testing.assert_index_equal(res.index, base_df.index)
51
+ assert set(res.columns.tolist()) == set(dataloader.columns_map.keys())
52
+
53
+ @patch.object(InstrumentDataloaderProxy, "financials")
54
+ def test__get_financial_df_empty(self, mock_fct, dataloader: DataLoader):
55
+ mock_fct.return_value = []
56
+ with pytest.raises(ValueError):
57
+ dataloader._get_financial_df(Financial.EBIT)
58
+
59
+ def test_base_df(self, dataloader):
60
+ base_df = dataloader.base_df
61
+ total_portfolio_weight = AssetPosition.objects.filter(
62
+ portfolio=dataloader.portfolio, date=dataloader.val_date
63
+ ).aggregate(c=Sum("weighting"))["c"]
64
+ for instrument_id, df in base_df.to_dict("index").items():
65
+ for key, value in df.items():
66
+ pos = AssetPosition.objects.get(
67
+ portfolio=dataloader.portfolio, date=dataloader.val_date, underlying_instrument=instrument_id
68
+ )
69
+ if key == "weighting":
70
+ assert float(getattr(pos, key) / total_portfolio_weight) == pytest.approx(value, abs=1e-6)
71
+ else:
72
+ assert float(getattr(pos, key)) == value
73
+
74
+ # Test portfolio metric computation
75
+ def test_portfolio_ownership_df(self, dataloader):
76
+ assert dataloader.portfolio_ownership_df.dropna().empty is False
77
+ pd.testing.assert_frame_equal(
78
+ dataloader.portfolio_ownership_df,
79
+ (1.0 / dataloader.market_capitalization_df).multiply(dataloader.base_df.total_value_fx_usd, axis=0),
80
+ check_exact=True,
81
+ )
82
+
83
+ def test_portfolio_ebit_usd_df(self, dataloader):
84
+ assert dataloader.portfolio_ebit_usd_df.dropna().empty is False
85
+ pd.testing.assert_frame_equal(
86
+ dataloader.portfolio_ebit_usd_df,
87
+ dataloader.portfolio_ownership_df * dataloader.ebit_usd_df,
88
+ check_exact=True,
89
+ )
90
+ pd.testing.assert_series_equal(
91
+ dataloader.portfolio_ebit_usd_df.aggregate, dataloader.portfolio_ebit_usd_df.sum(axis=0), check_exact=True
92
+ )
93
+
94
+ def test_portfolio_total_assets_usd_df(self, dataloader):
95
+ assert dataloader.portfolio_total_assets_usd_df.dropna().empty is False
96
+ pd.testing.assert_frame_equal(
97
+ dataloader.portfolio_total_assets_usd_df,
98
+ dataloader.portfolio_ownership_df * dataloader.total_assets_usd_df,
99
+ check_exact=True,
100
+ )
101
+ pd.testing.assert_series_equal(
102
+ dataloader.portfolio_total_assets_usd_df.aggregate,
103
+ dataloader.portfolio_total_assets_usd_df.sum(axis=0),
104
+ check_exact=True,
105
+ )
106
+
107
+ def test_portfolio_liabilities_usd_df(self, dataloader):
108
+ assert dataloader.portfolio_liabilities_usd_df.dropna().empty is False
109
+ pd.testing.assert_frame_equal(
110
+ dataloader.portfolio_liabilities_usd_df,
111
+ dataloader.portfolio_ownership_df * dataloader.liabilities_usd_df,
112
+ check_exact=True,
113
+ )
114
+ pd.testing.assert_series_equal(
115
+ dataloader.portfolio_liabilities_usd_df.aggregate,
116
+ dataloader.portfolio_liabilities_usd_df.sum(axis=0),
117
+ check_exact=True,
118
+ )
119
+
120
+ def test_portfolio_aggregation_capital_employed(self, dataloader):
121
+ assert dataloader.portfolio_aggregation_capital_employed.dropna().empty is False
122
+ pd.testing.assert_series_equal(
123
+ dataloader.portfolio_aggregation_capital_employed,
124
+ dataloader.portfolio_total_assets_usd_df.aggregate - dataloader.portfolio_liabilities_usd_df.aggregate,
125
+ check_exact=True,
126
+ )
127
+
128
+ def test_portfolio_aggregation_roce(self, dataloader):
129
+ assert dataloader.portfolio_aggregation_roce.dropna().empty is False
130
+ pd.testing.assert_series_equal(
131
+ dataloader.portfolio_aggregation_roce,
132
+ dataloader.portfolio_ebit_usd_df.aggregate
133
+ / dataloader.portfolio_aggregation_capital_employed.rolling(2).mean(),
134
+ check_exact=True,
135
+ )
@@ -0,0 +1,69 @@
1
+ from unittest.mock import patch
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ import pytest
6
+ from django.contrib.contenttypes.models import ContentType
7
+ from faker import Faker
8
+ from wbfdm.analysis.esg.enums import ESGAggregation
9
+ from wbfdm.contrib.metric.models import InstrumentMetric
10
+
11
+ from ..backends.portfolio_esg import PortfolioESGMetricBackend
12
+
13
+ fake = Faker()
14
+
15
+
16
+ @pytest.mark.django_db
17
+ class TestPortfolioESGDataloader:
18
+ @pytest.fixture
19
+ def backend(self, weekday):
20
+ return PortfolioESGMetricBackend(weekday)
21
+
22
+ @patch.object(PortfolioESGMetricBackend, "_get_metrics_for_esg_aggregation")
23
+ def test_compute_metrics(self, mock_fct, backend, portfolio, instrument_factory, asset_position_factory):
24
+ instruments = instrument_factory.create_batch(2)
25
+ asset_position_factory.create(portfolio=portfolio, is_estimated=False, date=backend.val_date)
26
+ esg_aggregation = pd.DataFrame(
27
+ np.random.randint(1, 100, size=[2, 1]), index=[i.id for i in instruments], columns=["score"]
28
+ )
29
+ mock_fct.return_value = esg_aggregation
30
+ for metric in backend.compute_metrics(portfolio):
31
+ InstrumentMetric.update_or_create_from_metric(metric)
32
+
33
+ p_ct = ContentType.objects.get_for_model(portfolio)
34
+
35
+ # check that we indeed created and stored a metrics for each esg aggregation and for each instrument
36
+ for esg in ESGAggregation:
37
+ for instrument in instruments:
38
+ assert (
39
+ InstrumentMetric.objects.get(
40
+ basket_content_type=p_ct.pk,
41
+ basket_id=portfolio.id,
42
+ instrument=instrument,
43
+ date=backend.val_date,
44
+ key=f"portfolio_esg_{esg.name.lower()}",
45
+ ).metrics["score"]
46
+ == esg_aggregation.loc[instrument.id, "score"]
47
+ )
48
+
49
+ # check if the egg aggregated accross the whole portfolio is properly stored
50
+ assert (
51
+ InstrumentMetric.objects.get(
52
+ basket_content_type=p_ct.pk,
53
+ basket_id=portfolio.id,
54
+ instrument__isnull=True,
55
+ date=backend.val_date,
56
+ key=f"portfolio_esg_{esg.name.lower()}",
57
+ ).metrics["score"]
58
+ == esg_aggregation.sum(axis=0)["score"]
59
+ )
60
+
61
+ #
62
+ # # basic checks
63
+ # assert res[0].basket_id == portfolio.id
64
+ # assert res[0].basket_content_type_id ==
65
+ # assert res[0].key ==
66
+ # assert res[0].metrics ==
67
+ # assert res[0].date ==
68
+ # assert res[0].instrument_id ==
69
+ # assert res[0].dependency_metrics ==