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,117 @@
1
+ from datetime import date, datetime
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ import plotly.graph_objects as go
6
+ from wbcore import viewsets
7
+ from wbcrm.models.accounts import Account
8
+ from wbportfolio.analysis.claims import get_assets_and_net_new_money_progression
9
+ from wbportfolio.filters import AssetsAndNetNewMoneyProgressionFilterSet
10
+ from wbportfolio.models.products import Product
11
+ from wbportfolio.models.transactions.claim import Claim
12
+
13
+ from .configs import AssetAndNetNewMoneyProgressionChartTitleConfig
14
+
15
+
16
+ class AssetAndNetNewMoneyProgressionChartViewSet(viewsets.ChartViewSet):
17
+ queryset = Claim.objects.all()
18
+
19
+ filterset_class = AssetsAndNetNewMoneyProgressionFilterSet
20
+
21
+ title_config_class = AssetAndNetNewMoneyProgressionChartTitleConfig
22
+
23
+ def get_plotly(self, queryset) -> "go.Figure":
24
+ if "period" not in self.request.GET:
25
+ from_date = date(2023, 12, 31)
26
+ to_date = date(2024, 6, 30)
27
+ else:
28
+ from_date, to_date = self.request.GET["period"].split(",")
29
+ from_date = datetime.strptime(from_date, "%Y-%m-%d").date()
30
+ to_date = datetime.strptime(to_date, "%Y-%m-%d").date()
31
+
32
+ account_id = self.request.GET.get("account", None)
33
+ product_id = self.request.GET.get("product", None)
34
+
35
+ name = f"Progression: {from_date:%d.%m.%Y}-{to_date:%d.%m.%Y}"
36
+
37
+ account_tree_id = None
38
+ el = list()
39
+ if account_id:
40
+ account = Account.objects.get(id=account_id)
41
+ account_tree_id = account.tree_id # type: ignore
42
+ el.append(str(account))
43
+
44
+ if product_id:
45
+ el.append(str(Product.objects.get(id=product_id)))
46
+
47
+ if len(el) > 0:
48
+ name += f" ({", ".join(el)})"
49
+
50
+ data = get_assets_and_net_new_money_progression(
51
+ from_date=from_date, to_date=to_date, account_tree_id=account_tree_id, product_id=product_id
52
+ )
53
+ df = pd.DataFrame(data)
54
+
55
+ # Compute performance of each month
56
+ df["performance"] = df["total_assets"] - df["total_assets"].shift(1) - df["net_new_money"]
57
+
58
+ # Compute the x label
59
+ df["x"] = df[["year", "month"]].apply(lambda row: "-".join(row.values.astype(str)), axis=1)
60
+
61
+ # Implode the pivoted table to have a list of values
62
+ df = df.melt(id_vars=["x", "year", "month"], value_vars=["net_new_money", "performance", "total_assets"])
63
+
64
+ # Sort by year, then month, then variable to have everything in the right order
65
+ df = df.sort_values(["year", "month", "variable"])
66
+
67
+ # Remove the first two rows, as they are the NNM and Performance of the previous month, which we do not want to display
68
+ df = df.iloc[2:]
69
+
70
+ # Create a pattern to have the measure for the waterfall chart
71
+ df["measure"] = np.tile(["absolute", "relative", "relative"], len(df) // 3 + 1)[: len(df)]
72
+
73
+ # We reset the index and drop all columns we don't need
74
+ # IMPORTANT: Do not drop year and month, even though we
75
+ # don't need it - we will loose the ordering
76
+ df = df.reset_index()[["x", "year", "month", "variable", "value", "measure"]]
77
+
78
+ # Rename things, to have it pretty in the chart
79
+ df["variable"] = (
80
+ df["variable"]
81
+ .str.replace("net_new_money", "NNM")
82
+ .replace("total_assets", "AuM")
83
+ .replace("performance", "Performance")
84
+ )
85
+ df.iloc[0, 0] = ""
86
+ df.iloc[0, 3] = "Initial AuM"
87
+ df.iloc[-1, 3] = "Final AuM"
88
+
89
+ # Create the figure and show it
90
+ fig = go.Figure()
91
+ fig.add_trace(
92
+ go.Waterfall(
93
+ name=name,
94
+ orientation="v",
95
+ measure=df["measure"],
96
+ x=[df["x"], df["variable"]],
97
+ y=df["value"],
98
+ text=list(map(lambda x: f"{x/1_000_000:,.0f}" if x is not None else "0", df["value"])),
99
+ decreasing={"marker": {"color": "#FF6961"}},
100
+ increasing={"marker": {"color": "#77DD77"}},
101
+ totals={"marker": {"color": "#D3D3D3"}},
102
+ )
103
+ )
104
+ fig.update_layout(
105
+ template="plotly_white",
106
+ margin=dict(l=10, r=10, t=0, b=40),
107
+ showlegend=True,
108
+ legend={
109
+ "orientation": "h",
110
+ "yanchor": "bottom",
111
+ "y": 1,
112
+ "xanchor": "center",
113
+ "x": 0.5,
114
+ },
115
+ )
116
+
117
+ return fig
@@ -0,0 +1 @@
1
+ from .assets import DistributionChartViewSet, DistributionTableViewSet
@@ -0,0 +1,247 @@
1
+ import datetime as dt
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ import plotly.express as px
6
+ import plotly.graph_objects as go
7
+ from django.db.models import QuerySet
8
+ from django.shortcuts import get_object_or_404
9
+ from django.utils.functional import cached_property
10
+ from wbcore import viewsets
11
+ from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
12
+ from wbcore.filters import DjangoFilterBackend
13
+ from wbcore.pandas import fields as pf
14
+ from wbfdm.models import (
15
+ ClassificationGroup,
16
+ Instrument,
17
+ InstrumentClassificationThroughModel,
18
+ )
19
+ from wbportfolio.filters.assets import DistributionFilter
20
+ from wbportfolio.models import (
21
+ AssetPosition,
22
+ AssetPositionGroupBy,
23
+ Portfolio,
24
+ PortfolioRole,
25
+ )
26
+
27
+ from ..configs.buttons.assets import (
28
+ DistributionChartButtonConfig,
29
+ DistributionTableButtonConfig,
30
+ )
31
+ from ..configs.display.assets import DistributionTableDisplayConfig
32
+ from ..configs.endpoints.assets import (
33
+ DistributionChartEndpointConfig,
34
+ DistributionTableEndpointConfig,
35
+ )
36
+ from ..configs.titles.assets import (
37
+ DistributionChartTitleConfig,
38
+ DistributionTableTitleConfig,
39
+ )
40
+
41
+
42
+ class AbstractDistributionMixin:
43
+ AUTORESIZE = False
44
+ queryset = AssetPosition.objects.all()
45
+ filterset_class = DistributionFilter
46
+ filter_backends = (DjangoFilterBackend,)
47
+
48
+ @cached_property
49
+ def classification_group(self):
50
+ try:
51
+ return ClassificationGroup.objects.get(id=self.request.GET.get("group_by_classification_group"))
52
+ except ClassificationGroup.DoesNotExist:
53
+ return ClassificationGroup.objects.get(is_primary=True)
54
+
55
+ @cached_property
56
+ def classification_field_names(self):
57
+ return [f"classification__{field_name}__name" for field_name in self.classification_group.get_fields_names()]
58
+
59
+ @cached_property
60
+ def classification_levels_representation(self):
61
+ return self.classification_group.get_levels_representation()
62
+
63
+ @cached_property
64
+ def classification_columns_map(self):
65
+ return dict(
66
+ zip(["classification__name", *self.classification_field_names], self.classification_levels_representation)
67
+ )
68
+
69
+ def _generate_classification_df(self, queryset):
70
+ df = pd.DataFrame(
71
+ queryset.filter(underlying_security_instrument_type_key="equity").values(
72
+ "weighting", "underlying_instrument"
73
+ ),
74
+ columns=["weighting", "underlying_instrument"],
75
+ )
76
+ df.underlying_instrument = df.underlying_instrument.map(
77
+ dict(
78
+ Instrument.objects.filter(id__in=df.underlying_instrument)
79
+ .annotate_base_data()
80
+ .values_list("id", "root")
81
+ )
82
+ )
83
+ df = df.groupby("underlying_instrument").sum()
84
+ classifications = InstrumentClassificationThroughModel.objects.filter(
85
+ classification__group=self.classification_group, instrument__in=df.index
86
+ )
87
+ df_classification = pd.DataFrame(
88
+ classifications.values(
89
+ "instrument",
90
+ "classification__name",
91
+ *self.classification_field_names,
92
+ )
93
+ )
94
+ if df_classification.empty:
95
+ return pd.DataFrame()
96
+ return pd.concat([df, df_classification.groupby("instrument").first()], axis=1).replace(
97
+ [np.inf, -np.inf, np.nan], "N/A"
98
+ )
99
+
100
+ def get_queryset(self):
101
+ portfolio = get_object_or_404(Portfolio, id=self.kwargs["portfolio_id"])
102
+ if (
103
+ PortfolioRole.is_analyst(self.request.user.profile, portfolio=portfolio)
104
+ or self.request.user.profile.is_internal
105
+ ):
106
+ return super().get_queryset().filter(portfolio=portfolio)
107
+ return AssetPosition.objects.none()
108
+
109
+ @staticmethod
110
+ def dataframe_group_by_instrument(df: pd.DataFrame) -> pd.DataFrame:
111
+ if df.empty:
112
+ return pd.DataFrame()
113
+ return df.groupby("aggregated_title").sum().sort_values(by="weighting", ascending=False)
114
+
115
+ def dataframe_groupby_with_class_method(self, qs: QuerySet, class_method: classmethod):
116
+ df = pd.DataFrame()
117
+ if qs.exists():
118
+ df = self.dataframe_group_by_instrument(
119
+ pd.DataFrame(class_method(qs).values("weighting", "aggregated_title"))
120
+ )
121
+ return df
122
+
123
+
124
+ class DistributionChartViewSet(AbstractDistributionMixin, viewsets.ChartViewSet):
125
+ title_config_class = DistributionChartTitleConfig
126
+ endpoint_config_class = DistributionChartEndpointConfig
127
+ button_config_class = DistributionChartButtonConfig
128
+
129
+ @staticmethod
130
+ def pie_chart(df: pd.DataFrame) -> go.Figure:
131
+ fig = go.Figure()
132
+ if not df.empty:
133
+ fig.add_pie(
134
+ labels=df.index,
135
+ values=df.weighting.mul(100),
136
+ marker=dict(colors=px.colors.qualitative.Plotly[: df.shape[0]], line=dict(color="#000000", width=2)),
137
+ hovertemplate="<b>%{label}</b><extra></extra>",
138
+ )
139
+ return fig
140
+
141
+ def get_plotly(self, queryset):
142
+ fig = go.Figure()
143
+ group_by = self.request.GET.get("group_by", "COUNTRY")
144
+ class_method = AssetPositionGroupBy.get_class_method_group_by(name=group_by)
145
+ queryset_without_cash = queryset.exclude(underlying_instrument__is_cash=True)
146
+ if group_by not in ["INDUSTRY", "CURRENCY"]:
147
+ df = self.dataframe_groupby_with_class_method(qs=queryset_without_cash, class_method=class_method)
148
+ fig = self.pie_chart(df=df)
149
+ elif group_by == "CURRENCY":
150
+ df = self.dataframe_groupby_with_class_method(qs=queryset, class_method=class_method)
151
+ fig = self.pie_chart(df=df)
152
+ else:
153
+ df = self._generate_classification_df(queryset_without_cash)
154
+ if not df.empty:
155
+ df["weighting"] = df.weighting / df.weighting.sum()
156
+ df.weighting = df.weighting.astype("float")
157
+ df = df.reset_index().rename(
158
+ columns={**self.classification_columns_map, "weighting": "weight", "index": "Equity"}
159
+ )
160
+
161
+ levels = [*self.classification_levels_representation[::-1], "Equity"]
162
+ df["Equity"] = df["Equity"].map(
163
+ dict(Instrument.objects.filter(id__in=df["Equity"]).values_list("id", "name_repr"))
164
+ )
165
+ portfolio = Portfolio.objects.get(id=self.kwargs["portfolio_id"])
166
+ if not PortfolioRole.is_analyst(self.request.user.profile, portfolio=portfolio):
167
+ del df["Equity"]
168
+ levels.remove("Equity")
169
+ fig = px.sunburst(
170
+ df,
171
+ path=levels,
172
+ values="weight",
173
+ hover_data={"weight": ":.2%"},
174
+ )
175
+ fig.update_traces(hovertemplate="<b>%{label}</b><br>Weight = %{customdata:.3p}")
176
+ return fig
177
+
178
+
179
+ class DistributionTableViewSet(AbstractDistributionMixin, ExportPandasAPIViewSet):
180
+ endpoint_config_class = DistributionTableEndpointConfig
181
+ display_config_class = DistributionTableDisplayConfig
182
+ title_config_class = DistributionTableTitleConfig
183
+ button_config_class = DistributionTableButtonConfig
184
+
185
+ def get_pandas_fields(self, request):
186
+ if self.request.GET.get("group_by") != "INDUSTRY":
187
+ fields = [
188
+ pf.PKField(key="aggregate_field", label=""),
189
+ ]
190
+ else:
191
+ fields = [
192
+ pf.PKField(key="id", label="IDS"),
193
+ pf.CharField(key="equity", label="Equity"),
194
+ ]
195
+ for level_rep in self.classification_levels_representation:
196
+ fields.append(pf.CharField(key=level_rep, label=level_rep))
197
+ fields.extend([pf.FloatField(key="weighting", label="Weight", precision=2, percent=True)])
198
+ return pf.PandasFields(fields=tuple(fields))
199
+
200
+ def get_date_filter(self):
201
+ if date_str := self.request.GET.get("date", None):
202
+ val_date = dt.datetime.strptime(date_str, "%Y-%m-%d").date()
203
+ elif super().get_queryset().exists():
204
+ val_date = AssetPosition.objects.latest("date").date
205
+ else:
206
+ val_date = dt.date.today()
207
+ return val_date
208
+
209
+ def get_queryset(self):
210
+ val_date = self.get_date_filter()
211
+ queryset = super().get_queryset().filter(date=val_date)
212
+ return queryset
213
+
214
+ def get_dataframe(self, request, queryset, **kwargs):
215
+ group_by = self.request.GET.get("group_by", "COUNTRY")
216
+ class_method = AssetPositionGroupBy.get_class_method_group_by(name=group_by)
217
+ queryset_without_cash = queryset.exclude(underlying_instrument__is_cash=True)
218
+ if group_by not in ["INDUSTRY", "CURRENCY"]:
219
+ df = self.dataframe_groupby_with_class_method(qs=queryset_without_cash, class_method=class_method)
220
+ elif group_by == "CURRENCY":
221
+ df = self.dataframe_groupby_with_class_method(qs=queryset, class_method=class_method)
222
+ else: # group_by == "INDUSTRY"
223
+ df = self._generate_classification_df(queryset_without_cash)
224
+
225
+ if not df.empty:
226
+ df.weighting /= df.weighting.sum()
227
+ df = df.reset_index().rename(columns={**self.classification_columns_map, "index": "equity"})
228
+ df["equity"] = df["equity"].map(
229
+ dict(Instrument.objects.filter(id__in=df["equity"]).values_list("id", "name_repr"))
230
+ )
231
+ for level in self.classification_levels_representation:
232
+ tmp = df.groupby(by=level).weighting.sum().astype(float).mul(100).round(1)
233
+ df = df.join(tmp, on=level, rsuffix=f"_{level}")
234
+ df[level] += " (" + df[f"weighting_{level}"].astype(str) + "%)"
235
+ portfolio = Portfolio.objects.get(id=self.request.GET.get("portfolio"))
236
+ if not PortfolioRole.is_analyst(self.request.user.profile, portfolio=portfolio):
237
+ df[["weighting", "equity"]] = None
238
+ df.drop_duplicates(inplace=True)
239
+ return df
240
+
241
+ def manipulate_dataframe(self, df):
242
+ if not df.empty:
243
+ df.sort_values(by="weighting", ascending=False, inplace=True)
244
+ if df.weighting.sum() != 1: # normalize
245
+ df.weighting /= df.weighting.sum()
246
+ df = df.reset_index(names="aggregate_field" if self.request.GET.get("group_by") != "INDUSTRY" else "id")
247
+ return df
@@ -0,0 +1,6 @@
1
+ from .buttons import *
2
+ from .display import *
3
+ from .endpoints import *
4
+ from .menu import *
5
+ from .previews import *
6
+ from .titles import *
@@ -0,0 +1,23 @@
1
+ from .adjustments import AdjustmentButtonConfig
2
+ from .assets import (
3
+ AssetPositionButtonConfig,
4
+ AssetPositionInstrumentButtonConfig,
5
+ AssetPositionPortfolioButtonConfig,
6
+ DistributionChartButtonConfig,
7
+ DistributionTableButtonConfig,
8
+ )
9
+ from .claims import ClaimTradeButtonConfig, ConsolidatedTradeSummaryButtonConfig
10
+
11
+ from .custodians import CustodianButtonConfig
12
+ from .fees import FeesButtonConfig
13
+ from .portfolios import ModelPortfolioButtonConfig, PortfolioButtonConfig
14
+ from .products import ProductButtonConfig, ProductCustomerButtonConfig
15
+ from .registers import RegisterButtonConfig
16
+ from .trades import (
17
+ TradeButtonConfig,
18
+ TradeInstrumentButtonConfig,
19
+ TradeProposalButtonConfig,
20
+ TradeTradeProposalButtonConfig,
21
+ )
22
+ from .reconciliations import AccountReconciliationButtonViewConfig, AccountReconciliationLineButtonViewConfig
23
+ from .signals import *
@@ -0,0 +1,13 @@
1
+ from wbcore.contrib.icons import WBIcon
2
+ from wbcore.metadata.configs import buttons as bt
3
+ from wbcore.metadata.configs.buttons.view_config import ButtonViewConfig
4
+
5
+
6
+ class AdjustmentButtonConfig(ButtonViewConfig):
7
+ def get_custom_list_instance_buttons(self):
8
+ return {
9
+ bt.WidgetButton(key="detection_table", label="Detection Table", icon=WBIcon.DATA_GRID.icon),
10
+ }
11
+
12
+ def get_custom_instance_buttons(self):
13
+ return self.get_custom_list_instance_buttons()
@@ -0,0 +1,145 @@
1
+ from contextlib import suppress
2
+
3
+ from rest_framework.reverse import reverse
4
+ from wbcore.metadata.configs import buttons as bt
5
+ from wbcore.metadata.configs.buttons.view_config import ButtonViewConfig
6
+ from wbportfolio.models.portfolio import Portfolio, PortfolioPortfolioThroughModel
7
+
8
+
9
+ class AssetPositionButtonConfig(ButtonViewConfig):
10
+ def get_custom_list_instance_buttons(self):
11
+ return set()
12
+
13
+ def get_custom_instance_buttons(self):
14
+ return set()
15
+
16
+
17
+ class AssetPositionPortfolioButtonConfig(AssetPositionButtonConfig):
18
+ def get_custom_buttons(self):
19
+ btns = []
20
+ with suppress(Portfolio.DoesNotExist):
21
+ portfolio = Portfolio.objects.get(id=self.view.kwargs.get("portfolio_id", None))
22
+ btns.append(
23
+ bt.WidgetButton(
24
+ endpoint=reverse(
25
+ "wbportfolio:portfolio-contributor-list", args=[portfolio.id], request=self.request
26
+ ),
27
+ label="Contributor",
28
+ )
29
+ )
30
+ btns.append(
31
+ bt.WidgetButton(
32
+ endpoint=(
33
+ reverse(
34
+ "wbportfolio:portfolio-distributionchart-list",
35
+ args=[portfolio.id],
36
+ request=self.request,
37
+ )
38
+ ),
39
+ label="Distribution Charts",
40
+ )
41
+ )
42
+ btns.append(
43
+ bt.WidgetButton(
44
+ endpoint=(
45
+ reverse(
46
+ "wbportfolio:portfolio-esgaggregation-list",
47
+ args=[portfolio.id],
48
+ request=self.request,
49
+ )
50
+ ),
51
+ label="ESG Aggregation",
52
+ )
53
+ )
54
+ cash_management_btns = []
55
+
56
+ if portfolio.daily_cashflows.all().exists():
57
+ cash_management_btns.append(
58
+ bt.WidgetButton(
59
+ label="Cash Flow",
60
+ endpoint=reverse(
61
+ "wbportfolio:portfolio-portfoliocashflow-list",
62
+ args=[portfolio.id],
63
+ request=self.request,
64
+ ),
65
+ )
66
+ )
67
+
68
+ if portfolio.bank_accounts.all().exists():
69
+ bank_account_ids = map(lambda i: str(i), portfolio.bank_accounts.all().values_list("id", flat=True))
70
+ base_url = reverse("wbaccounting:futurecashflow-list", request=self.request)
71
+ cash_flow_url = f"{base_url}?banking_contact={", ".join(bank_account_ids)}"
72
+ cash_management_btns.append(
73
+ bt.WidgetButton(
74
+ label="Bank Accounts",
75
+ endpoint=cash_flow_url,
76
+ )
77
+ )
78
+
79
+ if len(cash_management_btns) > 0:
80
+ btns.append(
81
+ bt.DropDownButton(
82
+ label="Cash Management",
83
+ buttons=cash_management_btns,
84
+ )
85
+ )
86
+ for rel in PortfolioPortfolioThroughModel.objects.filter(portfolio=portfolio):
87
+ dependency_portfolio = rel.dependency_portfolio
88
+ if dependency_portfolio.assets.exists():
89
+ btns.append(
90
+ bt.WidgetButton(
91
+ endpoint=(
92
+ reverse(
93
+ "wbportfolio:portfolio-asset-list",
94
+ args=[dependency_portfolio.id],
95
+ request=self.request,
96
+ )
97
+ ),
98
+ label=f"{PortfolioPortfolioThroughModel.Type[rel.type].label} Portfolio",
99
+ )
100
+ )
101
+ return set(btns)
102
+
103
+
104
+ class AssetPositionInstrumentButtonConfig(AssetPositionButtonConfig):
105
+ def get_custom_buttons(self):
106
+ if instrument_id := self.view.kwargs.get("instrument_id", None):
107
+ return {
108
+ bt.WidgetButton(
109
+ endpoint=reverse(
110
+ "wbportfolio:instrument-assetpositionchart-list", args=[instrument_id], request=self.request
111
+ ),
112
+ label="Portfolio Allocation ",
113
+ ),
114
+ }
115
+ return set()
116
+
117
+
118
+ class DistributionChartButtonConfig(ButtonViewConfig):
119
+ def get_custom_buttons(self) -> set:
120
+ if portfolio_id := self.view.kwargs.get("portfolio_id", None):
121
+ return {
122
+ bt.WidgetButton(
123
+ endpoint=(
124
+ f"{reverse('wbportfolio:portfolio-distributiontable-list', args=[portfolio_id], request=self.request)}"
125
+ f"?group_by={self.request.GET.get('group_by')}"
126
+ ),
127
+ label="Table Form",
128
+ ),
129
+ }
130
+ return set()
131
+
132
+
133
+ class DistributionTableButtonConfig(ButtonViewConfig):
134
+ def get_custom_buttons(self) -> set:
135
+ if portfolio_id := self.view.kwargs.get("portfolio_id", None):
136
+ return {
137
+ bt.WidgetButton(
138
+ endpoint=(
139
+ f"{reverse('wbportfolio:portfolio-distributionchart-list', args=[portfolio_id], request=self.request)}"
140
+ f"?group_by={self.request.GET.get('group_by')}"
141
+ ),
142
+ label="Chart Form",
143
+ ),
144
+ }
145
+ return set()
@@ -0,0 +1,83 @@
1
+ from rest_framework.reverse import reverse
2
+ from wbcore import serializers
3
+ from wbcore.contrib.icons import WBIcon
4
+ from wbcore.enums import RequestType
5
+ from wbcore.metadata.configs import buttons as bt
6
+ from wbcore.metadata.configs.buttons.view_config import ButtonViewConfig
7
+ from wbcore.metadata.configs.display.instance_display.shortcuts import (
8
+ create_simple_display,
9
+ )
10
+ from wbcore.utils.urls import get_urlencode_endpoint
11
+ from wbcrm.models.accounts import Account
12
+ from wbcrm.serializers.accounts import TerminalAccountRepresentationSerializer
13
+
14
+
15
+ class TransferTradeSerializer(serializers.Serializer):
16
+ transfer_date = serializers.DateField(label="Transfer Date", required=True)
17
+ from_account = serializers.PrimaryKeyRelatedField(
18
+ queryset=Account.objects.all(), label="From Account", required=True
19
+ )
20
+ _from_account = TerminalAccountRepresentationSerializer(source="from_account")
21
+
22
+ to_account = serializers.PrimaryKeyRelatedField(queryset=Account.objects.all(), label="To Account", required=True)
23
+ _to_account = TerminalAccountRepresentationSerializer(source="to_account")
24
+
25
+
26
+ class QuickClaimSerializer(serializers.Serializer):
27
+ account = serializers.PrimaryKeyRelatedField(queryset=Account.objects.all(), label="Account", required=True)
28
+ _account = TerminalAccountRepresentationSerializer(source="account")
29
+
30
+
31
+ class ConsolidatedTradeSummaryButtonConfig(ButtonViewConfig):
32
+ def get_custom_buttons(self) -> set:
33
+ return {
34
+ bt.WidgetButton(
35
+ endpoint=get_urlencode_endpoint(
36
+ reverse(
37
+ "wbportfolio:consolidatedtradesummarydistributionchart-list", args=[], request=self.request
38
+ ),
39
+ self.request.GET,
40
+ ),
41
+ label="Distribution NNM Chart",
42
+ ),
43
+ bt.WidgetButton(
44
+ endpoint=get_urlencode_endpoint(
45
+ reverse("wbportfolio:cumulativennmchart-list", args=[], request=self.request), self.request.GET
46
+ ),
47
+ label="Cumulative NNM Chart",
48
+ ),
49
+ }
50
+
51
+
52
+ class ClaimTradeButtonConfig(ButtonViewConfig):
53
+ def get_custom_buttons(self):
54
+ return {
55
+ bt.ActionButton(
56
+ method=RequestType.POST,
57
+ action_label="Trade transferred.",
58
+ endpoint=reverse(
59
+ "wbportfolio:trade-claim-transfer-trade", request=self.request, args=[self.view.kwargs["trade_id"]]
60
+ ),
61
+ description_fields="Do you want to transfer this trade?",
62
+ label="Transfer Trade",
63
+ icon=WBIcon.DEAL.icon,
64
+ confirm_config=bt.ButtonConfig(label="Transfer"),
65
+ cancel_config=bt.ButtonConfig(label="Cancel"),
66
+ serializer=TransferTradeSerializer,
67
+ instance_display=create_simple_display([["transfer_date"], ["from_account"], ["to_account"]]),
68
+ ),
69
+ bt.ActionButton(
70
+ method=RequestType.POST,
71
+ action_label="Trade claimed.",
72
+ endpoint=reverse(
73
+ "wbportfolio:trade-claim-quick-claim", request=self.request, args=[self.view.kwargs["trade_id"]]
74
+ ),
75
+ description_fields="Do you want to quick claim this trade?",
76
+ label="Quick Claim",
77
+ icon=WBIcon.EURO.icon,
78
+ confirm_config=bt.ButtonConfig(label="Claim"),
79
+ cancel_config=bt.ButtonConfig(label="Cancel"),
80
+ serializer=QuickClaimSerializer,
81
+ instance_display=create_simple_display([["account"]]),
82
+ ),
83
+ }