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,57 @@
1
+ from .assets import (
2
+ AssetPositionInstrumentModelViewSet,
3
+ AssetPositionModelViewSet,
4
+ AssetPositionPortfolioModelViewSet,
5
+ AssetPositionUnderlyingInstrumentChartViewSet,
6
+ CashPositionPortfolioPandasAPIView,
7
+ CompositionModelPortfolioPandasView,
8
+ ContributorPortfolioChartView,
9
+ )
10
+ from .charts import *
11
+ from .custodians import CustodianModelViewSet, CustodianRepresentationViewSet
12
+ from .portfolio_relationship import InstrumentPreferedClassificationThroughProductModelViewSet
13
+ from .portfolios import (
14
+ ModelPortfolioModelViewSet,
15
+ PortfolioModelViewSet,
16
+ PortfolioPortfolioThroughModelViewSet,
17
+ PortfolioRepresentationViewSet,
18
+ )
19
+ from .positions import (
20
+ AggregatedAssetPositionLiquidityPandasView,
21
+ AssetPositionPandasView,
22
+ )
23
+ from .registers import RegisterModelViewSet, RegisterRepresentationViewSet
24
+ from .roles import PortfolioRoleInstrumentModelViewSet, PortfolioRoleModelViewSet
25
+ from .signals import *
26
+ from .synchronization import (
27
+ PortfolioSynchronizationRepresentationViewSet,
28
+ PriceComputationRepresentationViewSet,
29
+ )
30
+ from .transactions import *
31
+ from .adjustments import AdjustmentEquityModelViewSet, AdjustmentModelViewSet
32
+ from .product_groups import ProductGroupModelViewSet, ProductGroupRepresentationViewSet
33
+ from .product_performance import (
34
+ PerformanceComparisonPandasView,
35
+ PerformancePandasView,
36
+ ProductPerformanceNetNewMoneyListViewSet,
37
+ )
38
+ from .products import (
39
+ ProductCustomerModelViewSet,
40
+ ProductModelViewSet,
41
+ ProductPerformanceFeesModelViewSet,
42
+ ProductRepresentationViewSet,
43
+ NominalProductChartView,
44
+ AUMProductChartView,
45
+ InstrumentPriceAUMDataChartView,
46
+ )
47
+ from .product_groups import ProductGroupRepresentationViewSet, ProductGroupModelViewSet
48
+ from .portfolio_relationship import (
49
+ InstrumentPreferedClassificationThroughProductModelViewSet,
50
+ InstrumentPortfolioThroughPortfolioModelViewSet,
51
+ )
52
+ from .portfolio_cash_flow import DailyPortfolioCashFlowModelViewSet
53
+ from .portfolio_swing_pricing import PortfolioSwingPricingModelViewSet
54
+ from .portfolio_cash_targets import PortfolioCashTargetModelViewSet
55
+ from .assets_and_net_new_money_progression import AssetAndNetNewMoneyProgressionChartViewSet
56
+ from .esg import ESGMetricAggregationPortfolioPandasViewSet
57
+ from .reconciliations import AccountReconciliationModelViewSet, AccountReconciliationLineModelViewSet
@@ -0,0 +1,46 @@
1
+ from wbcore import viewsets
2
+ from wbcore.permissions.permissions import InternalUserPermissionMixin
3
+ from wbportfolio.models import Adjustment
4
+ from wbportfolio.serializers import AdjustmentModelSerializer
5
+
6
+ from .configs import (
7
+ AdjustmentButtonConfig,
8
+ AdjustmentDisplayConfig,
9
+ AdjustmentEndpointConfig,
10
+ AdjustmentTitleConfig,
11
+ )
12
+
13
+
14
+ class AdjustmentModelViewSet(InternalUserPermissionMixin, viewsets.ModelViewSet):
15
+ queryset = Adjustment.objects.select_related(
16
+ "instrument",
17
+ "last_handler",
18
+ )
19
+ serializer_class = AdjustmentModelSerializer
20
+
21
+ filterset_fields = {
22
+ "date": ["gte", "exact", "lte"],
23
+ "instrument": ["exact"],
24
+ "factor": ["gte", "exact", "lte"],
25
+ "status": ["exact"],
26
+ "last_handler": ["exact"],
27
+ }
28
+ ordering = ["-date"]
29
+ ordering_fields = ["date", "instrument", "factor"]
30
+
31
+ display_config_class = AdjustmentDisplayConfig
32
+ title_config_class = AdjustmentTitleConfig
33
+ endpoint_config_class = AdjustmentEndpointConfig
34
+ button_config_class = AdjustmentButtonConfig
35
+
36
+
37
+ class AdjustmentEquityModelViewSet(AdjustmentModelViewSet):
38
+ filterset_fields = {
39
+ "date": ["gte", "exact", "lte"],
40
+ "factor": ["gte", "exact", "lte"],
41
+ "status": ["exact"],
42
+ "last_handler": ["exact"],
43
+ }
44
+
45
+ def get_queryset(self):
46
+ return super().get_queryset().filter(instrument=self.kwargs["instrument_id"])
@@ -0,0 +1,562 @@
1
+ from contextlib import suppress
2
+ from datetime import date, datetime
3
+
4
+ import pandas as pd
5
+ import plotly.graph_objects as go
6
+ from django.db.models import Exists, F, OuterRef, Q, Sum
7
+ from django.utils.dateparse import parse_date
8
+ from django.utils.functional import cached_property
9
+ from plotly.subplots import make_subplots
10
+ from wbcore import viewsets
11
+ from wbcore.contrib.currency.models import Currency, CurrencyFXRates
12
+ from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
13
+ from wbcore.filters import DjangoFilterBackend
14
+ from wbcore.pandas import fields as pf
15
+ from wbcore.permissions.permissions import InternalUserPermissionMixin
16
+ from wbcore.serializers import decorator
17
+ from wbcore.utils.figures import (
18
+ get_default_timeserie_figure,
19
+ get_hovertemplate_timeserie,
20
+ )
21
+ from wbcore.utils.strings import format_number
22
+ from wbfdm.contrib.metric.viewsets.mixins import InstrumentMetricMixin
23
+ from wbfdm.models import Instrument
24
+ from wbportfolio.filters import (
25
+ AssetPositionFilter,
26
+ AssetPositionInstrumentFilter,
27
+ AssetPositionPortfolioFilter,
28
+ AssetPositionUnderlyingInstrumentChartFilter,
29
+ CashPositionPortfolioFilterSet,
30
+ CompositionModelPortfolioPandasFilter,
31
+ ContributionChartFilter,
32
+ )
33
+ from wbportfolio.import_export.resources.assets import AssetPositionResource
34
+ from wbportfolio.metric.backends.portfolio_base import (
35
+ PORTFOLIO_CAPITAL_EMPLOYED,
36
+ PORTFOLIO_EBIT,
37
+ PORTFOLIO_LIABILITIES,
38
+ PORTFOLIO_ROCE,
39
+ PORTFOLIO_TOTAL_ASSETS,
40
+ )
41
+ from wbportfolio.metric.backends.portfolio_esg import PORTFOLIO_ESG_KEYS
42
+ from wbportfolio.models import (
43
+ AssetPosition,
44
+ InstrumentPortfolioThroughModel,
45
+ Portfolio,
46
+ Product,
47
+ )
48
+ from wbportfolio.serializers import (
49
+ AssetPositionAggregatedPortfolioModelSerializer,
50
+ AssetPositionInstrumentModelSerializer,
51
+ AssetPositionModelSerializer,
52
+ AssetPositionPortfolioModelSerializer,
53
+ CashPositionPortfolioModelSerializer,
54
+ )
55
+
56
+ from .configs import (
57
+ AssetPositionButtonConfig,
58
+ AssetPositionDisplayConfig,
59
+ AssetPositionEndpointConfig,
60
+ AssetPositionInstrumentButtonConfig,
61
+ AssetPositionInstrumentDisplayConfig,
62
+ AssetPositionInstrumentEndpointConfig,
63
+ AssetPositionInstrumentTitleConfig,
64
+ AssetPositionPortfolioButtonConfig,
65
+ AssetPositionPortfolioDisplayConfig,
66
+ AssetPositionPortfolioEndpointConfig,
67
+ AssetPositionPortfolioTitleConfig,
68
+ AssetPositionTitleConfig,
69
+ AssetPositionUnderlyingInstrumentChartEndpointConfig,
70
+ AssetPositionUnderlyingInstrumentChartTitleConfig,
71
+ CashPositionPortfolioDisplayConfig,
72
+ CashPositionPortfolioEndpointConfig,
73
+ CashPositionPortfolioTitleConfig,
74
+ CompositionModelPortfolioPandasDisplayConfig,
75
+ CompositionModelPortfolioPandasEndpointConfig,
76
+ CompositionModelPortfolioPandasTitleConfig,
77
+ ContributorPortfolioChartEndpointConfig,
78
+ ContributorPortfolioChartTitleConfig,
79
+ )
80
+ from .mixins import UserPortfolioRequestPermissionMixin
81
+
82
+
83
+ class AssetPositionModelViewSet(
84
+ UserPortfolioRequestPermissionMixin,
85
+ InternalUserPermissionMixin,
86
+ viewsets.ReadOnlyModelViewSet,
87
+ ):
88
+ IDENTIFIER = "wbportfolio:assetposition"
89
+ IMPORT_ALLOWED = False
90
+
91
+ queryset = AssetPosition.objects.all()
92
+ serializer_class = AssetPositionModelSerializer
93
+ filterset_class = AssetPositionFilter
94
+
95
+ ordering_fields = [
96
+ "total_value_fx_portfolio",
97
+ "total_value_fx_usd",
98
+ "total_value",
99
+ "portfolio__name",
100
+ "portfolio_created__name",
101
+ "underlying_instrument",
102
+ "underlying_instrument_name",
103
+ "underlying_instrument_ticker",
104
+ "underlying_instrument_isin",
105
+ "price",
106
+ "currency__key",
107
+ "currency_fx_rate",
108
+ "shares",
109
+ "date",
110
+ "asset_valuation_date",
111
+ "weighting",
112
+ "market_share",
113
+ "liquidity",
114
+ ]
115
+ ordering = ["-weighting"]
116
+ search_fields = ["underlying_instrument_ticker", "underlying_instrument_name", "underlying_instrument_isin"]
117
+
118
+ display_config_class = AssetPositionDisplayConfig
119
+ button_config_class = AssetPositionButtonConfig
120
+ endpoint_config_class = AssetPositionEndpointConfig
121
+ title_config_class = AssetPositionTitleConfig
122
+
123
+ def get_resource_class(self):
124
+ return AssetPositionResource
125
+
126
+ def get_aggregates(self, queryset, paginated_queryset):
127
+ aggregates = super().get_aggregates(queryset, paginated_queryset)
128
+ if queryset.exists():
129
+ total_value_fx_usd = queryset.aggregate(s=Sum(F("total_value_fx_usd")))["s"]
130
+ weighting = queryset.aggregate(s=Sum(F("weighting")))["s"]
131
+ aggregates.update(
132
+ {
133
+ "weighting": {"Σ": format_number(weighting)},
134
+ "total_value_fx_usd": {"Σ": format_number(total_value_fx_usd)},
135
+ }
136
+ )
137
+ return aggregates
138
+
139
+ def get_queryset(self):
140
+ if self.is_analyst:
141
+ return (
142
+ super()
143
+ .get_queryset()
144
+ .annotate(
145
+ underlying_instrument_isin=F("underlying_instrument__isin"),
146
+ underlying_instrument_ticker=F("underlying_instrument__ticker"),
147
+ underlying_instrument_name=F("underlying_instrument__name"),
148
+ )
149
+ .select_related(
150
+ "underlying_instrument",
151
+ "currency",
152
+ "portfolio",
153
+ "exchange",
154
+ "portfolio_created",
155
+ )
156
+ )
157
+ return AssetPosition.objects.none()
158
+
159
+
160
+ # Portfolio Viewsets
161
+
162
+
163
+ class AssetPositionPortfolioModelViewSet(InstrumentMetricMixin, AssetPositionModelViewSet):
164
+ METRIC_KEYS = (
165
+ PORTFOLIO_EBIT,
166
+ PORTFOLIO_TOTAL_ASSETS,
167
+ PORTFOLIO_LIABILITIES,
168
+ PORTFOLIO_CAPITAL_EMPLOYED,
169
+ PORTFOLIO_ROCE,
170
+ *PORTFOLIO_ESG_KEYS,
171
+ )
172
+ METRIC_BASKET_LABEL = "portfolio"
173
+ METRIC_INSTRUMENT_LABEL = "underlying_instrument"
174
+ METRIC_SHOW_BY_DEFAULT = False
175
+ METRIC_SHOW_FILTERS = True
176
+
177
+ @property
178
+ def metric_date(self):
179
+ if date_str := self.request.GET.get("date"):
180
+ return parse_date(date_str)
181
+
182
+ @property
183
+ def metric_basket(self):
184
+ return self.portfolio
185
+
186
+ filterset_class = AssetPositionPortfolioFilter
187
+ display_config_class = AssetPositionPortfolioDisplayConfig
188
+ button_config_class = AssetPositionPortfolioButtonConfig
189
+ title_config_class = AssetPositionPortfolioTitleConfig
190
+ endpoint_config_class = AssetPositionPortfolioEndpointConfig
191
+
192
+ def get_serializer_class(self):
193
+ if self.request.GET.get("aggregate", "false") == "true":
194
+ return AssetPositionAggregatedPortfolioModelSerializer
195
+ return AssetPositionPortfolioModelSerializer
196
+
197
+ def get_aggregates(self, queryset, paginated_queryset):
198
+ if not queryset.exists():
199
+ return {}
200
+ weighting = queryset.aggregate(s=Sum(F("weighting")))["s"]
201
+ total_value_fx_portfolio = queryset.aggregate(s=Sum(F("total_value_fx_portfolio")))["s"]
202
+ aggregates = super().get_aggregates(queryset, paginated_queryset)
203
+ aggregates["total_value_fx_portfolio"] = {"Σ": format_number(total_value_fx_portfolio)}
204
+ aggregates["weighting"] = {"Σ": format_number(weighting)}
205
+ return aggregates
206
+
207
+ def get_queryset(self):
208
+ if self.has_portfolio_access:
209
+ return (
210
+ super()
211
+ .get_queryset()
212
+ .filter(portfolio=self.portfolio)
213
+ .select_related("underlying_instrument", "currency", "exchange", "portfolio_created")
214
+ )
215
+ return AssetPosition.objects.none()
216
+
217
+
218
+ # Underlying Assets viewsets
219
+ class AssetPositionInstrumentModelViewSet(AssetPositionModelViewSet):
220
+ filterset_class = AssetPositionInstrumentFilter
221
+ serializer_class = AssetPositionInstrumentModelSerializer
222
+ display_config_class = AssetPositionInstrumentDisplayConfig
223
+ title_config_class = AssetPositionInstrumentTitleConfig
224
+ button_config_class = AssetPositionInstrumentButtonConfig
225
+ endpoint_config_class = AssetPositionInstrumentEndpointConfig
226
+
227
+ def get_aggregates(self, queryset, paginated_queryset):
228
+ queryset = queryset.filter(is_invested=True)
229
+ if queryset.exists():
230
+ total_value_fx_usd = queryset.aggregate(s=Sum(F("total_value_fx_usd")))["s"]
231
+ total_shares = queryset.aggregate(s=Sum(F("shares")))["s"]
232
+ total_market_share = queryset.aggregate(s=Sum(F("market_share")))["s"]
233
+ return {
234
+ "shares": {"Σ": format_number(total_shares)},
235
+ "total_value_fx_usd": {"Σ": format_number(total_value_fx_usd)},
236
+ "market_share": {"Σ": format_number(total_market_share)},
237
+ }
238
+ return {}
239
+
240
+ def get_queryset(self):
241
+ qs = (
242
+ super().get_queryset().filter(underlying_instrument__in=self.instrument.get_descendants(include_self=True))
243
+ )
244
+ if self.request.GET.get("filter_last_positions", "false") == "true":
245
+ if self.instrument.assets.exists():
246
+ qs = qs.filter(date=self.instrument.assets.latest("date").date)
247
+ return qs
248
+
249
+
250
+ class CashPositionPortfolioPandasAPIView(
251
+ UserPortfolioRequestPermissionMixin, InternalUserPermissionMixin, ExportPandasAPIViewSet
252
+ ):
253
+ queryset = AssetPosition.objects.all()
254
+ display_config_class = CashPositionPortfolioDisplayConfig
255
+ title_config_class = CashPositionPortfolioTitleConfig
256
+ endpoint_config_class = CashPositionPortfolioEndpointConfig
257
+
258
+ serializer_class = CashPositionPortfolioModelSerializer
259
+ filterset_class = CashPositionPortfolioFilterSet
260
+
261
+ search_fields = ["portfolio_name"]
262
+ ordering_fields = ["portfolio_name", "total_value_fx_usd", "portfolio_weight"]
263
+
264
+ pandas_fields = pf.PandasFields(
265
+ fields=(
266
+ pf.PKField(key="portfolio", label="ID"),
267
+ pf.CharField(key="portfolio_name", label="Portfolio"),
268
+ pf.FloatField(
269
+ key="total_value_fx_usd",
270
+ label="Total Value",
271
+ precision=2,
272
+ decorators=(decorator(decorator_type="text", position="right", value="$"),),
273
+ ),
274
+ pf.FloatField(key="portfolio_weight", label="Total portfolio value", precision=2, percent=True),
275
+ )
276
+ )
277
+
278
+ def get_aggregates(self, request, df):
279
+ if not df.empty:
280
+ sum_total_value_fx_usd = df.total_value_fx_usd.sum()
281
+ return {
282
+ "total_value_fx_usd": {"Σ": format_number(sum_total_value_fx_usd)},
283
+ }
284
+ return {}
285
+
286
+ def get_dataframe(self, request, queryset, **kwargs):
287
+ df = pd.DataFrame()
288
+ if queryset.exists():
289
+ df = pd.DataFrame(
290
+ queryset.values(
291
+ "underlying_instrument__is_cash",
292
+ "total_value_fx_portfolio",
293
+ "fx_rate",
294
+ "portfolio__name",
295
+ "portfolio",
296
+ )
297
+ )
298
+ df_name = (
299
+ df[["portfolio", "portfolio__name"]]
300
+ .groupby("portfolio")
301
+ .agg("first")
302
+ .rename(columns={"portfolio__name": "portfolio_name"})
303
+ )
304
+
305
+ df["total_value_fx_usd"] = df.total_value_fx_portfolio * df.fx_rate
306
+ df_total = (
307
+ df[["portfolio", "total_value_fx_usd"]]
308
+ .groupby("portfolio")
309
+ .sum()
310
+ .rename(columns={"total_value_fx_usd": "total_portfolio_fx_usd"})
311
+ )
312
+ df = df[df["underlying_instrument__is_cash"]]
313
+ df = df[["portfolio", "total_value_fx_usd"]].groupby("portfolio").sum()
314
+ if not df.empty:
315
+ df = df[df["total_value_fx_usd"] != 0]
316
+ df = pd.concat([df, df_total, df_name], axis=1).dropna(how="any")
317
+ df["portfolio_weight"] = df.total_value_fx_usd / df.total_portfolio_fx_usd
318
+ return df.reset_index()
319
+ return df
320
+
321
+ def get_queryset(self):
322
+ if self.is_portfolio_manager:
323
+ if date_str := self.request.GET.get("date", None):
324
+ val_date = datetime.strptime(date_str, "%Y-%m-%d").date()
325
+ else:
326
+ val_date = date.today()
327
+ active_products = Product.active_objects.get_queryset_at_date(val_date)
328
+ return (
329
+ super()
330
+ .get_queryset()
331
+ .annotate(
332
+ fx_rate=CurrencyFXRates.get_fx_rates_subquery(
333
+ "date", currency="portfolio__currency", lookup_expr="exact"
334
+ ),
335
+ is_product_portfolio=Exists(
336
+ InstrumentPortfolioThroughModel.objects.filter(
337
+ portfolio=OuterRef("portfolio"), instrument__in=active_products
338
+ )
339
+ ),
340
+ )
341
+ .filter(is_product_portfolio=True)
342
+ )
343
+ return AssetPosition.objects.none()
344
+
345
+
346
+ # ##### CHART VIEWS #####
347
+
348
+
349
+ class ContributorPortfolioChartView(UserPortfolioRequestPermissionMixin, viewsets.ChartViewSet):
350
+ filterset_class = ContributionChartFilter
351
+ filter_backends = (DjangoFilterBackend,)
352
+ IDENTIFIER = "wbportfolio:portfolio-contributor"
353
+ queryset = AssetPosition.objects.all()
354
+
355
+ title_config_class = ContributorPortfolioChartTitleConfig
356
+ endpoint_config_class = ContributorPortfolioChartEndpointConfig
357
+
358
+ def get_plotly(self, queryset):
359
+ fig = go.Figure()
360
+ hedged_currency = (
361
+ Currency.objects.get(id=self.request.GET["hedged_currency"])
362
+ if "hedged_currency" in self.request.GET
363
+ else None
364
+ )
365
+ df = Portfolio.get_contribution_df(queryset, hedged_currency=hedged_currency)
366
+ if not df.empty:
367
+ df = df[["contribution_total", "contribution_forex", "underlying_security"]].sort_values(
368
+ by="contribution_total", ascending=True
369
+ )
370
+ df["instrument_id"] = df.underlying_security.apply(lambda x: Instrument.objects.get(id=x).name_repr)
371
+ df_forex = df[["instrument_id", "contribution_forex"]]
372
+ df_forex = df_forex[df_forex.contribution_forex != 0]
373
+
374
+ contribution_equity = df.contribution_total - df.contribution_forex
375
+
376
+ text_forex = df_forex.contribution_forex.apply(lambda x: f"{x:,.2%}")
377
+ text_equity = contribution_equity.apply(lambda x: f"{x:,.2%}")
378
+ fig.add_trace(
379
+ go.Bar(
380
+ y=df.instrument_id,
381
+ x=contribution_equity,
382
+ name="Contribution Equity",
383
+ orientation="h",
384
+ marker=dict(
385
+ color="rgba(247,110,91,0.6)",
386
+ line=dict(color="rgb(247,110,91,1.0)", width=2),
387
+ ),
388
+ text=text_equity.values,
389
+ textposition="auto",
390
+ )
391
+ )
392
+ fig.add_trace(
393
+ go.Bar(
394
+ y=df_forex.instrument_id,
395
+ x=df_forex.contribution_forex,
396
+ name="Contribution Forex",
397
+ orientation="h",
398
+ marker=dict(
399
+ color="rgba(58, 71, 80, 0.6)",
400
+ line=dict(color="rgba(58, 71, 80, 1.0)", width=2),
401
+ ),
402
+ text=text_forex.values,
403
+ textposition="outside",
404
+ )
405
+ )
406
+ fig.update_layout(
407
+ barmode="relative",
408
+ xaxis=dict(showgrid=False, showline=False, zeroline=False, tickformat=".2%"),
409
+ yaxis=dict(showgrid=False, showline=False, zeroline=False, tickmode="linear"),
410
+ margin=dict(b=0, r=20, l=20, t=0, pad=20),
411
+ paper_bgcolor="rgba(0,0,0,0)",
412
+ plot_bgcolor="rgba(0,0,0,0)",
413
+ font=dict(family="roboto", size=12, color="black"),
414
+ )
415
+ # fig = get_horizontal_barplot(df, x_label="contribution_total", y_label="name")
416
+ return fig
417
+
418
+ def get_queryset(self):
419
+ if self.has_portfolio_access:
420
+ return super().get_queryset().filter(portfolio=self.portfolio)
421
+ return AssetPosition.objects.none()
422
+
423
+
424
+ class AssetPositionUnderlyingInstrumentChartViewSet(UserPortfolioRequestPermissionMixin, viewsets.ChartViewSet):
425
+ IDENTIFIER = "wbportfolio:assetpositionchart"
426
+
427
+ queryset = AssetPosition.objects.all()
428
+
429
+ title_config_class = AssetPositionUnderlyingInstrumentChartTitleConfig
430
+ endpoint_config_class = AssetPositionUnderlyingInstrumentChartEndpointConfig
431
+ filterset_class = AssetPositionUnderlyingInstrumentChartFilter
432
+
433
+ def get_queryset(self):
434
+ return AssetPosition.objects.filter(
435
+ underlying_instrument__in=self.instrument.get_descendants(include_self=True)
436
+ )
437
+
438
+ def get_plotly(self, queryset):
439
+ fig = make_subplots(specs=[[{"secondary_y": True}]])
440
+ fig = get_default_timeserie_figure(fig)
441
+ if queryset.exists():
442
+ df_price = (pd.DataFrame(queryset.values("date", "price_fx_usd"))).groupby("date").first()
443
+
444
+ df_price = df_price.where(pd.notnull(df_price), 0)
445
+ fig.add_trace(
446
+ go.Scatter(
447
+ x=df_price.index, y=df_price.price_fx_usd, mode="lines", marker_color="green", name="Price"
448
+ ),
449
+ secondary_y=False,
450
+ )
451
+
452
+ df_weight = pd.DataFrame(queryset.values("date", "weighting", "portfolio__name"))
453
+ df_weight = df_weight.where(pd.notnull(df_weight), 0)
454
+ df_weight = df_weight.groupby(["date", "portfolio__name"]).sum().reset_index()
455
+ for portfolio_name, df_tmp in df_weight.groupby("portfolio__name"):
456
+ fig.add_trace(
457
+ go.Scatter(
458
+ x=df_tmp.date,
459
+ y=df_tmp.weighting,
460
+ hovertemplate=get_hovertemplate_timeserie(is_percent=True),
461
+ mode="lines",
462
+ name=f"Allocation: {portfolio_name}",
463
+ ),
464
+ secondary_y=True,
465
+ )
466
+
467
+ # Set x-axis title
468
+ fig.update_xaxes(title_text="Date")
469
+ # Set y-axes titles
470
+ fig.update_yaxes(
471
+ title_text="<b>Price</b>",
472
+ secondary_y=False,
473
+ titlefont=dict(color="green"),
474
+ tickfont=dict(color="green"),
475
+ )
476
+ fig.update_yaxes(
477
+ title_text="<b>Portfolio Allocation (%)</b>",
478
+ secondary_y=True,
479
+ titlefont=dict(color="blue"),
480
+ tickfont=dict(color="blue"),
481
+ )
482
+
483
+ return fig
484
+
485
+
486
+ class CompositionModelPortfolioPandasView(
487
+ UserPortfolioRequestPermissionMixin, InternalUserPermissionMixin, ExportPandasAPIViewSet
488
+ ):
489
+ IDENTIFIER = "wbportfolio:compositionmodelportfolio"
490
+ filterset_class = CompositionModelPortfolioPandasFilter
491
+ queryset = AssetPosition.objects.all()
492
+
493
+ display_config_class = CompositionModelPortfolioPandasDisplayConfig
494
+ title_config_class = CompositionModelPortfolioPandasTitleConfig
495
+ endpoint_config_class = CompositionModelPortfolioPandasEndpointConfig
496
+
497
+ @cached_property
498
+ def dependant_portfolios(self):
499
+ return Portfolio.objects.filter(
500
+ Q(depends_on__in=[self.portfolio]) | Q(dependent_portfolios__in=[self.portfolio])
501
+ )
502
+
503
+ @cached_property
504
+ def instrument_map(self) -> dict[int, str]:
505
+ instrument_qs = Instrument.objects.filter(
506
+ id__in=self.get_queryset().values("underlying_instrument__parent")
507
+ ).values("id", "computed_str")
508
+ return {item["id"]: item["computed_str"] for item in instrument_qs}
509
+
510
+ def get_pandas_fields(self, request):
511
+ fields = [
512
+ pf.PKField(key="underlying_instrument__parent", label="ID"),
513
+ pf.CharField(key="underlying_instrument_repr", label="Instrument"),
514
+ ]
515
+ if not self.portfolio.only_weighting:
516
+ fields.append(pf.FloatField(key=f"shares_{self.portfolio.id}", label=str(self.portfolio)))
517
+ fields.append(pf.FloatField(key=f"weighting_{self.portfolio.id}", label=str(self.portfolio), percent=True))
518
+
519
+ for portfolio in self.dependant_portfolios:
520
+ if not portfolio.only_weighting:
521
+ fields.append(pf.FloatField(key=f"shares_{portfolio.id}", label=str(portfolio)))
522
+ fields.append(pf.FloatField(key=f"difference_{portfolio.id}", label=str(portfolio)))
523
+ else:
524
+ fields.append(pf.FloatField(key=f"weighting_{portfolio.id}", label=str(portfolio), percent=True))
525
+ fields.append(pf.FloatField(key=f"difference_{portfolio.id}", label=str(portfolio), percent=True))
526
+
527
+ return pf.PandasFields(fields=fields)
528
+
529
+ search_fields = ["underlying_instrument_repr"]
530
+ ordering_fields = ["underlying_instrument_repr", "model_portfolio"]
531
+
532
+ def get_queryset(self):
533
+ if self.has_portfolio_access:
534
+ return AssetPosition.objects.filter(portfolio__in=[*self.dependant_portfolios, self.portfolio])
535
+ return AssetPosition.objects.none()
536
+
537
+ def get_dataframe(self, request, queryset, **kwargs):
538
+ df = pd.DataFrame()
539
+ if queryset.exists():
540
+ df = pd.DataFrame(
541
+ queryset.values("underlying_instrument__parent", "portfolio", "weighting", "shares")
542
+ ).pivot_table(
543
+ index="underlying_instrument__parent",
544
+ columns=["portfolio"],
545
+ values=["weighting", "shares"],
546
+ aggfunc="sum",
547
+ fill_value=0,
548
+ )
549
+
550
+ for portfolio in self.dependant_portfolios:
551
+ with suppress(KeyError):
552
+ if portfolio.only_weighting:
553
+ df["difference", portfolio.id] = (
554
+ df["weighting"][self.portfolio.id] - df["weighting"][portfolio.id]
555
+ )
556
+ else:
557
+ df["difference", portfolio.id] = df["shares"][self.portfolio.id] - df["shares"][portfolio.id]
558
+ df.columns = ["_".join([str(item) for item in col]) for col in df.columns.to_flat_index()]
559
+ df = df.reset_index()
560
+ df["underlying_instrument_repr"] = df.underlying_instrument__parent.map(self.instrument_map)
561
+
562
+ return df