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,190 @@
1
+ import pandas as pd
2
+ from django.db.models import F, Sum
3
+ from rest_framework import filters
4
+ from wbcore.contrib.currency.models import CurrencyFXRates
5
+ from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
6
+ from wbcore.filters import DjangoFilterBackend
7
+ from wbcore.pandas import fields as pf
8
+ from wbcore.permissions.permissions import InternalUserPermissionMixin
9
+ from wbcore.serializers import decorator
10
+ from wbcore.utils.strings import format_number
11
+ from wbportfolio.filters import FeesAggregatedFilter, FeesFilter, FeesPortfolioFilterSet
12
+ from wbportfolio.models import Fees
13
+ from wbportfolio.serializers import FeesModelSerializer
14
+
15
+ from ..configs import (
16
+ FeeEndpointConfig,
17
+ FeesAggregatedPortfolioPandasDisplayConfig,
18
+ FeesAggregatedPortfolioPandasEndpointConfig,
19
+ FeesAggregatedPortfolioTitleConfig,
20
+ FeesButtonConfig,
21
+ FeesDisplayConfig,
22
+ FeesPortfolioDisplayConfig,
23
+ FeesPortfolioEndpointConfig,
24
+ FeesPortfolioTitleConfig,
25
+ FeesTitleConfig,
26
+ )
27
+ from ..mixins import UserPortfolioRequestPermissionMixin
28
+ from .transactions import TransactionModelViewSet
29
+
30
+
31
+ class FeesModelViewSet(TransactionModelViewSet):
32
+ filter_backends = (
33
+ DjangoFilterBackend,
34
+ filters.OrderingFilter,
35
+ )
36
+ queryset = Fees.valid_objects.all()
37
+ serializer_class = FeesModelSerializer
38
+ filterset_class = FeesFilter
39
+
40
+ display_config_class = FeesDisplayConfig
41
+ title_config_class = FeesTitleConfig
42
+ button_config_class = FeesButtonConfig
43
+ endpoint_config_class = FeeEndpointConfig
44
+ ordering = ["-fee_date", "id"] # ordering by id because otherwise there is duplicates in the paginated view
45
+
46
+ def get_aggregates(self, queryset, paginated_queryset):
47
+ return {
48
+ "total_value_fx_portfolio": {
49
+ "Σ": format_number(queryset.aggregate(s=Sum("total_value_fx_portfolio"))["s"])
50
+ },
51
+ "total_value_gross_fx_portfolio": {
52
+ "Σ": format_number(queryset.aggregate(s=Sum("total_value_gross_fx_portfolio"))["s"])
53
+ },
54
+ }
55
+
56
+ def get_queryset(self):
57
+ if self.is_manager:
58
+ return super().get_queryset().select_related("import_source", "linked_product")
59
+ return Fees.objects.none()
60
+
61
+
62
+ class FeesPortfolioModelViewSet(FeesModelViewSet):
63
+ IDENTIFIER = "wbportfolio:portfolio-fees"
64
+
65
+ filterset_class = FeesPortfolioFilterSet
66
+
67
+ display_config_class = FeesPortfolioDisplayConfig
68
+ title_config_class = FeesPortfolioTitleConfig
69
+ endpoint_config_class = FeesPortfolioEndpointConfig
70
+
71
+ def get_queryset(self):
72
+ if self.is_portfolio_manager:
73
+ return super().get_queryset().filter(portfolio=self.portfolio)
74
+
75
+ return Fees.objects.none()
76
+
77
+
78
+ class FeesAggregatedPortfolioPandasView(
79
+ UserPortfolioRequestPermissionMixin, InternalUserPermissionMixin, ExportPandasAPIViewSet
80
+ ):
81
+ IDENTIFIER = "wbportfolio:aggregetedfees"
82
+
83
+ filterset_class = FeesAggregatedFilter
84
+
85
+ queryset = Fees.valid_objects.all()
86
+
87
+ display_config_class = FeesAggregatedPortfolioPandasDisplayConfig
88
+ title_config_class = FeesAggregatedPortfolioTitleConfig
89
+ endpoint_config_class = FeesAggregatedPortfolioPandasEndpointConfig
90
+
91
+ pandas_fields = pf.PandasFields(
92
+ fields=(
93
+ pf.PKField(key="id", label="ID"),
94
+ pf.CharField(key="fee_date", label="Instrument"),
95
+ pf.FloatField(
96
+ key="TRANSACTION",
97
+ label="Transactions",
98
+ decorators=(decorator(decorator_type="text", position="left", value="$"),),
99
+ ),
100
+ pf.FloatField(
101
+ key="PERFORMANCE_CRYSTALIZED",
102
+ label="Performance",
103
+ decorators=(decorator(decorator_type="text", position="left", value="$"),),
104
+ ),
105
+ pf.FloatField(
106
+ key="PERFORMANCE",
107
+ label="Performance Crystalized",
108
+ decorators=(decorator(decorator_type="text", position="left", value="$"),),
109
+ ),
110
+ pf.FloatField(
111
+ key="MANAGEMENT",
112
+ label="Management",
113
+ decorators=(decorator(decorator_type="text", position="left", value="$"),),
114
+ ),
115
+ pf.FloatField(
116
+ key="ISSUER",
117
+ label="Issuer",
118
+ decorators=(decorator(decorator_type="text", position="left", value="$"),),
119
+ ),
120
+ pf.FloatField(
121
+ key="OTHER", label="Other", decorators=(decorator(decorator_type="text", position="left", value="$"),)
122
+ ),
123
+ pf.FloatField(
124
+ key="total", label="Total", decorators=(decorator(decorator_type="text", position="left", value="$"),)
125
+ ),
126
+ )
127
+ )
128
+ ordering = ["-fee_date"]
129
+ ordering_fields = [
130
+ "fee_date",
131
+ "TRANSACTION",
132
+ "PERFORMANCE_CRYSTALIZED",
133
+ "PERFORMANCE",
134
+ "MANAGEMENT",
135
+ "ISSUER",
136
+ "OTHER",
137
+ "total",
138
+ ]
139
+
140
+ def get_aggregates(self, request, df):
141
+ if df.empty:
142
+ return {}
143
+ return {
144
+ "TRANSACTION": {"Σ": format_number(df["TRANSACTION"].sum())},
145
+ "PERFORMANCE_CRYSTALIZED": {"Σ": format_number(df["PERFORMANCE_CRYSTALIZED"].sum())},
146
+ "PERFORMANCE": {"Σ": format_number(df["PERFORMANCE"].sum())},
147
+ "MANAGEMENT": {"Σ": format_number(df["MANAGEMENT"].sum())},
148
+ "ISSUER": {"Σ": format_number(df["ISSUER"].sum())},
149
+ "OTHER": {"Σ": format_number(df["OTHER"].sum())},
150
+ "total": {"Σ": format_number(df["total"].sum())},
151
+ }
152
+
153
+ def get_queryset(self):
154
+ if self.is_portfolio_manager:
155
+ qs = super().get_queryset().filter(portfolio=self.portfolio)
156
+ return qs.annotate(
157
+ currency_fx_rate_usd=CurrencyFXRates.get_fx_rates_subquery(
158
+ "fee_date", currency="currency", lookup_expr="exact"
159
+ ),
160
+ total_value_fx_usd=F("total_value") * F("currency_fx_rate_usd"),
161
+ )
162
+ return Fees.objects.none()
163
+
164
+ def get_dataframe(self, request, queryset, **kwargs):
165
+ df = pd.DataFrame(queryset.values("fee_date", "transaction_subtype", "total_value_fx_usd"))
166
+ if not df.empty:
167
+ df = (
168
+ df.pivot_table(
169
+ index="fee_date",
170
+ columns=["transaction_subtype"],
171
+ values="total_value_fx_usd",
172
+ aggfunc="sum",
173
+ )
174
+ .rename_axis(None, axis=1)
175
+ .reset_index()
176
+ )
177
+ for type in Fees.Type.values:
178
+ if type not in df:
179
+ df[type] = 0
180
+ df["total"] = (
181
+ df["TRANSACTION"]
182
+ + df["PERFORMANCE_CRYSTALIZED"]
183
+ + df["PERFORMANCE"]
184
+ + df["MANAGEMENT"]
185
+ + df["ISSUER"]
186
+ + df["OTHER"]
187
+ )
188
+ df["id"] = df["fee_date"]
189
+ df = df.fillna(0)
190
+ return df
@@ -0,0 +1,19 @@
1
+ from datetime import date, datetime
2
+
3
+ from django.http import HttpRequest
4
+ from django.utils.functional import cached_property
5
+ from wbportfolio.models.transactions.claim import Claim
6
+
7
+
8
+ class ClaimPermissionMixin:
9
+ queryset = Claim.objects.all()
10
+ request: HttpRequest
11
+
12
+ @cached_property
13
+ def validity_date(self) -> date:
14
+ if validity_date_repr := self.request.GET.get("validity_date"):
15
+ return datetime.strptime(validity_date_repr, "%Y-%m-%d")
16
+ return date.today()
17
+
18
+ def get_queryset(self):
19
+ return super().get_queryset().filter_for_user(self.request.user, validity_date=self.validity_date)
@@ -0,0 +1,93 @@
1
+ from django.shortcuts import get_object_or_404
2
+ from rest_framework.decorators import action
3
+ from rest_framework.response import Response
4
+ from wbcompliance.viewsets.risk_management.mixins import RiskCheckViewSetMixin
5
+ from wbcore import viewsets
6
+ from wbcore.metadata.configs.display.instance_display import (
7
+ Display,
8
+ create_simple_display,
9
+ )
10
+ from wbcore.permissions.permissions import InternalUserPermissionMixin
11
+ from wbcore.utils.views import CloneMixin
12
+ from wbportfolio.models import TradeProposal
13
+ from wbportfolio.models.transactions.trade_proposals import (
14
+ apply_trades_proposal_as_task,
15
+ )
16
+ from wbportfolio.serializers import (
17
+ TradeProposalModelSerializer,
18
+ TradeProposalRepresentationSerializer,
19
+ )
20
+
21
+ from ..configs import (
22
+ TradeProposalButtonConfig,
23
+ TradeProposalDisplayConfig,
24
+ TradeProposalEndpointConfig,
25
+ TradeProposalPortfolioEndpointConfig,
26
+ )
27
+
28
+
29
+ class TradeProposalRepresentationViewSet(InternalUserPermissionMixin, viewsets.RepresentationViewSet):
30
+ IDENTIFIER = "wbportfolio:trade"
31
+ queryset = TradeProposal.objects.all()
32
+ serializer_class = TradeProposalRepresentationSerializer
33
+
34
+
35
+ class TradeProposalModelViewSet(CloneMixin, RiskCheckViewSetMixin, InternalUserPermissionMixin, viewsets.ModelViewSet):
36
+ ordering_fields = ("trade_date",)
37
+ ordering = ("-trade_date",)
38
+ search_fields = ("comment",)
39
+ filterset_fields = {"trade_date": ["exact", "gte", "lte"], "status": ["exact"]}
40
+
41
+ queryset = TradeProposal.objects.select_related("model_portfolio", "portfolio")
42
+ serializer_class = TradeProposalModelSerializer
43
+ display_config_class = TradeProposalDisplayConfig
44
+ button_config_class = TradeProposalButtonConfig
45
+ endpoint_config_class = TradeProposalEndpointConfig
46
+
47
+ @classmethod
48
+ def get_clone_instance_display(cls) -> Display:
49
+ return create_simple_display(
50
+ [
51
+ ["comment"],
52
+ ["trade_date"],
53
+ ]
54
+ )
55
+
56
+ @classmethod
57
+ def _get_risk_checks_button_title(cls) -> str:
58
+ return "Pre-Trade Checks"
59
+
60
+ @action(detail=True, methods=["PATCH"])
61
+ def reset(self, request, pk=None):
62
+ trade_proposal = get_object_or_404(TradeProposal, pk=pk)
63
+ if trade_proposal.status == TradeProposal.Status.DRAFT:
64
+ trade_proposal.reset_trades()
65
+ return Response({"send": True})
66
+
67
+ @action(detail=True, methods=["PATCH"])
68
+ def normalize(self, request, pk=None):
69
+ trade_proposal = get_object_or_404(TradeProposal, pk=pk)
70
+ if trade_proposal.status == TradeProposal.Status.DRAFT:
71
+ trade_proposal.normalize_trades()
72
+ return Response({"send": True})
73
+
74
+ @action(detail=True, methods=["PATCH"])
75
+ def replay(self, request, pk=None):
76
+ trade_proposal = get_object_or_404(TradeProposal, pk=pk)
77
+ if trade_proposal.portfolio.is_manageable:
78
+ apply_trades_proposal_as_task.delay(trade_proposal.id)
79
+ return Response({"send": True})
80
+
81
+ @action(detail=True, methods=["PATCH"])
82
+ def deleteall(self, request, pk=None):
83
+ trade_proposal = get_object_or_404(TradeProposal, pk=pk)
84
+ if trade_proposal.status == TradeProposal.Status.DRAFT:
85
+ trade_proposal.trades.all().delete()
86
+ return Response({"send": True})
87
+
88
+
89
+ class TradeProposalPortfolioModelViewSet(TradeProposalModelViewSet):
90
+ endpoint_config_class = TradeProposalPortfolioEndpointConfig
91
+
92
+ def get_queryset(self):
93
+ return TradeProposal.objects.filter(portfolio=self.kwargs["portfolio_id"])
@@ -0,0 +1,395 @@
1
+ from decimal import Decimal
2
+
3
+ import pandas as pd
4
+ import plotly.graph_objects as go
5
+ from django.contrib.messages import warning
6
+ from django.db.models import BooleanField, Case, F, OuterRef, Subquery, Sum, Value, When
7
+ from django.db.models.functions import Coalesce
8
+ from django.shortcuts import get_object_or_404
9
+ from django.utils.functional import cached_property
10
+ from django_filters.rest_framework import DjangoFilterBackend
11
+ from wbcore import viewsets
12
+ from wbcore.permissions.permissions import InternalUserPermissionMixin
13
+ from wbcore.utils.strings import format_number
14
+ from wbcrm.models import Account
15
+ from wbcore.viewsets.mixins import OrderableMixin
16
+ from wbportfolio.filters import (
17
+ SubscriptionRedemptionFilterSet,
18
+ SubscriptionRedemptionPortfolioFilterSet,
19
+ TradeFilter,
20
+ TradeInstrumentFilterSet,
21
+ TradePortfolioFilter,
22
+ )
23
+ from wbportfolio.import_export.resources.trades import TradeProposalTradeResource
24
+ from wbportfolio.models import Trade, TradeProposal
25
+ from wbportfolio.models.transactions.claim import Claim
26
+ from wbportfolio.serializers import (
27
+ ReadOnlyTradeTradeProposalModelSerializer,
28
+ TradeModelSerializer,
29
+ TradeProposalRepresentationSerializer,
30
+ TradeRepresentationSerializer,
31
+ TradeTradeProposalModelSerializer,
32
+ )
33
+ from wbportfolio.viewsets.configs.titles.trades import (
34
+ CustomerDistributionInstrumentTitleConfig,
35
+ )
36
+
37
+ from ..configs import (
38
+ CustodianDistributionInstrumentEndpointConfig,
39
+ CustodianDistributionInstrumentTitleConfig,
40
+ SubscriptionRedemptionDisplayConfig,
41
+ SubscriptionRedemptionEndpointConfig,
42
+ SubscriptionRedemptionTitleConfig,
43
+ TradeButtonConfig,
44
+ TradeDisplayConfig,
45
+ TradeEndpointConfig,
46
+ TradeInstrumentButtonConfig,
47
+ TradeInstrumentEndpointConfig,
48
+ TradeInstrumentTitleConfig,
49
+ TradePortfolioDisplayConfig,
50
+ TradePortfolioEndpointConfig,
51
+ TradePortfolioTitleConfig,
52
+ TradeTitleConfig,
53
+ TradeTradeProposalButtonConfig,
54
+ TradeTradeProposalDisplayConfig,
55
+ TradeTradeProposalEndpointConfig,
56
+ )
57
+ from ..mixins import UserPortfolioRequestPermissionMixin
58
+ from .transactions import TransactionModelViewSet
59
+
60
+
61
+ class TradeProposalRepresentationViewSet(InternalUserPermissionMixin, viewsets.RepresentationViewSet):
62
+ IDENTIFIER = "wbportfolio:trade"
63
+ queryset = TradeProposal.objects.all()
64
+ serializer_class = TradeProposalRepresentationSerializer
65
+
66
+
67
+ class TradeRepresentationViewSet(InternalUserPermissionMixin, viewsets.RepresentationViewSet):
68
+ filterset_class = TradeFilter
69
+
70
+ ordering_fields = ("transaction_date", "shares")
71
+ ordering = ["-transaction_date"]
72
+ search_fields = (
73
+ "underlying_instrument__name",
74
+ "bank",
75
+ "shares",
76
+ "comment",
77
+ "external_id",
78
+ "register__register_name_1",
79
+ "register__register_name_2",
80
+ "register__custodian_name_1",
81
+ "register__custodian_name_2",
82
+ )
83
+
84
+ queryset = Trade.objects.select_related("underlying_instrument")
85
+ serializer_class = TradeRepresentationSerializer
86
+
87
+ def get_queryset(self):
88
+ return (
89
+ Trade.objects.filter(marked_for_deletion=False, pending=False)
90
+ .select_related("portfolio")
91
+ .select_related("underlying_instrument")
92
+ .prefetch_related("claims")
93
+ )
94
+
95
+
96
+ class TradeModelViewSet(TransactionModelViewSet):
97
+ IDENTIFIER = "wbportfolio:trade"
98
+
99
+ ordering_fields = (
100
+ "id",
101
+ "transaction_subtype",
102
+ "transaction_date",
103
+ "underlying_instrument__name",
104
+ "shares",
105
+ "approved_claimed_shares",
106
+ "pending_claimed_shares",
107
+ "price",
108
+ "total_value",
109
+ "total_value_fx_portfolio",
110
+ "total_value_usd",
111
+ "currency_fx_rate",
112
+ "currency__key",
113
+ "bank",
114
+ "register__computed_str",
115
+ "comment",
116
+ )
117
+ search_fields = (
118
+ "portfolio__name",
119
+ "underlying_instrument__name",
120
+ "bank",
121
+ "comment",
122
+ "external_id",
123
+ "register__computed_str",
124
+ "register__register_name_1",
125
+ "register__register_name_2",
126
+ "register__custodian_name_1",
127
+ "register__custodian_name_2",
128
+ )
129
+
130
+ filterset_class = TradeFilter
131
+ queryset = Trade.objects.all()
132
+ serializer_class = TradeModelSerializer
133
+
134
+ display_config_class = TradeDisplayConfig
135
+ endpoint_config_class = TradeEndpointConfig
136
+ title_config_class = TradeTitleConfig
137
+ button_config_class = TradeButtonConfig
138
+
139
+ def get_aggregates(self, queryset, paginated_queryset):
140
+ return {
141
+ "shares": {
142
+ "Σ": format_number(queryset.aggregate(s=Sum("shares"))["s"] or Decimal(0)),
143
+ },
144
+ **super().get_aggregates(queryset, paginated_queryset),
145
+ }
146
+
147
+ def get_queryset(self):
148
+ qs = super().get_queryset()
149
+ if not self.is_portfolio_manager:
150
+ qs = qs.filter(transaction_subtype__in=[Trade.Type.REDEMPTION, Trade.Type.SUBSCRIPTION])
151
+ qs = (
152
+ qs.annotate(
153
+ approved_claimed_shares=Coalesce(
154
+ Subquery(
155
+ Claim.objects.filter(status=Claim.Status.APPROVED, trade=OuterRef("pk"))
156
+ .values("trade")
157
+ .annotate(sum_shares=Sum("shares"))
158
+ .values("sum_shares")[:1]
159
+ ),
160
+ Decimal(0.0),
161
+ ),
162
+ pending_claimed_shares=Coalesce(
163
+ Subquery(
164
+ Claim.objects.filter(
165
+ status__in=[Claim.Status.PENDING, Claim.Status.DRAFT], trade=OuterRef("pk")
166
+ )
167
+ .values("trade")
168
+ .annotate(sum_shares=Sum("shares"))
169
+ .values("sum_shares")[:1]
170
+ ),
171
+ Decimal(0.0),
172
+ ),
173
+ completely_claimed=Case(
174
+ When(shares=F("approved_claimed_shares"), then=Value(True)),
175
+ default=Value(False),
176
+ output_field=BooleanField(),
177
+ ),
178
+ completely_claimed_if_approved=Case(
179
+ When(completely_claimed=True, then=Value(False)),
180
+ When(shares=F("approved_claimed_shares") + F("pending_claimed_shares"), then=Value(True)),
181
+ default=Value(False),
182
+ output_field=BooleanField(),
183
+ ),
184
+ )
185
+ .select_related("underlying_instrument")
186
+ .select_related("register")
187
+ .select_related("portfolio")
188
+ .select_related("currency")
189
+ .select_related("custodian")
190
+ .select_related("import_source")
191
+ .prefetch_related("claims")
192
+ .prefetch_related("claims__account")
193
+ .prefetch_related("claims__claimant")
194
+ .prefetch_related("claims__product")
195
+ )
196
+ return qs
197
+
198
+
199
+ class SubscriptionRedemptionModelViewSet(TradeModelViewSet):
200
+ IDENTIFIER = "wbportoflio:subscriptionredemption"
201
+ filterset_class = SubscriptionRedemptionFilterSet
202
+ display_config_class = SubscriptionRedemptionDisplayConfig
203
+ title_config_class = SubscriptionRedemptionTitleConfig
204
+ endpoint_config_class = SubscriptionRedemptionEndpointConfig
205
+
206
+ def get_queryset(self):
207
+ return super().get_queryset().filter(transaction_subtype__in=[Trade.Type.REDEMPTION, Trade.Type.SUBSCRIPTION])
208
+
209
+
210
+ class TradePortfolioModelViewSet(TradeModelViewSet):
211
+ IDENTIFIER = "wbportfolio:trade"
212
+ filterset_class = TradePortfolioFilter
213
+ title_config_class = TradePortfolioTitleConfig
214
+ endpoint_config_class = TradePortfolioEndpointConfig
215
+ display_config_class = TradePortfolioDisplayConfig
216
+
217
+ def get_aggregates(self, queryset, paginated_queryset):
218
+ if queryset.exists():
219
+ return {
220
+ "total_value_fx_portfolio": {
221
+ "Σ": format_number(queryset.aggregate(s=Sum(F("total_value_fx_portfolio")))["s"])
222
+ },
223
+ **super().get_aggregates(queryset, paginated_queryset),
224
+ }
225
+ return dict()
226
+
227
+ def get_queryset(self):
228
+ return super().get_queryset().filter(portfolio=self.portfolio)
229
+
230
+
231
+ class TradeInstrumentModelViewSet(TradeModelViewSet):
232
+ IDENTIFIER = "wbportfolio:trade"
233
+
234
+ filterset_class = TradeInstrumentFilterSet
235
+
236
+ title_config_class = TradeInstrumentTitleConfig
237
+ endpoint_config_class = TradeInstrumentEndpointConfig
238
+ button_config_class = TradeInstrumentButtonConfig
239
+
240
+ def get_aggregates(self, queryset, paginated_queryset):
241
+ if queryset.exists():
242
+ return {
243
+ "total_value": {"Σ": format_number(queryset.aggregate(s=Sum(F("total_value")))["s"])},
244
+ **super().get_aggregates(queryset, paginated_queryset),
245
+ }
246
+ return dict()
247
+
248
+ def get_queryset(self):
249
+ return (
250
+ super().get_queryset().filter(underlying_instrument__in=self.instrument.get_descendants(include_self=True))
251
+ )
252
+
253
+
254
+ class SubscriptionRedemptionInstrumentModelViewSet(SubscriptionRedemptionModelViewSet):
255
+ filterset_class = SubscriptionRedemptionPortfolioFilterSet
256
+
257
+ def get_queryset(self):
258
+ return (
259
+ super()
260
+ .get_queryset()
261
+ .filter(
262
+ underlying_instrument__in=self.instrument.get_descendants(include_self=True),
263
+ transaction_subtype__in=[Trade.Type.REDEMPTION, Trade.Type.SUBSCRIPTION],
264
+ )
265
+ )
266
+
267
+
268
+ class CustodianDistributionInstrumentChartViewSet(UserPortfolioRequestPermissionMixin, viewsets.ChartViewSet):
269
+ IDENTIFIER = "wbportfolio:custodiandistribution"
270
+ filterset_fields = {
271
+ "transaction_date": ["lte"],
272
+ }
273
+ filter_backends = (DjangoFilterBackend,)
274
+ queryset = Trade.objects.all()
275
+
276
+ title_config_class = CustodianDistributionInstrumentTitleConfig
277
+ endpoint_config_class = CustodianDistributionInstrumentEndpointConfig
278
+
279
+ def get_plotly(self, queryset):
280
+ fig = go.Figure()
281
+ if queryset.exists():
282
+ df = pd.DataFrame(queryset.values("custodian__name", "custodian__id", "shares"))
283
+ df = df.groupby("custodian__id").agg({"custodian__name": "first", "shares": "sum"})
284
+ fig = go.Figure(data=[go.Pie(labels=df.custodian__name, values=df.shares)])
285
+ return fig
286
+
287
+ def get_queryset(self):
288
+ return Trade.objects.filter(underlying_instrument=self.instrument)
289
+
290
+
291
+ class CustomerDistributionInstrumentChartViewSet(UserPortfolioRequestPermissionMixin, viewsets.ChartViewSet):
292
+ IDENTIFIER = "wbportfolio:custodiandistribution"
293
+ filterset_fields = {
294
+ "transaction_date": ["lte"],
295
+ }
296
+ filter_backends = (DjangoFilterBackend,)
297
+ queryset = Trade.objects.all()
298
+
299
+ title_config_class = CustomerDistributionInstrumentTitleConfig
300
+ endpoint_config_class = CustodianDistributionInstrumentEndpointConfig
301
+
302
+ # TODO: Consider moving into a helper class?
303
+ def group_smaller_items(self, df, threshold=0.01, text="Others"):
304
+ df["weight"] = df.sum_shares / df.sum_shares.sum()
305
+ group_sum = df.loc[df["weight"] < threshold].sum().to_frame().T
306
+ group_sum["account_name"] = text
307
+ rest = df.loc[df["weight"] >= threshold]
308
+ return pd.concat([group_sum, rest])
309
+
310
+ def get_plotly(self, queryset):
311
+ fig = go.Figure()
312
+ if queryset.exists():
313
+ df = self.group_smaller_items(
314
+ pd.DataFrame(queryset.values("account_name", "sum_shares")),
315
+ )
316
+
317
+ fig = go.Figure(data=[go.Pie(labels=df.account_name, values=df.sum_shares)])
318
+ return fig
319
+
320
+ def get_queryset(self):
321
+ return (
322
+ Trade.objects.filter(
323
+ underlying_instrument=self.instrument, transaction_subtype__in=["SUBSCRIPTION", "REDEMPTION"]
324
+ )
325
+ .values("claims")
326
+ .annotate(
327
+ shares=F("claims__shares"),
328
+ account_tree_id=F("claims__account__tree_id"),
329
+ status=F("claims__status"),
330
+ )
331
+ .filter(status="APPROVED")
332
+ .values("account_tree_id")
333
+ .annotate(
334
+ account_name=Subquery(
335
+ Account.objects.filter(tree_id=OuterRef("account_tree_id"), level=0).values("title")[:1]
336
+ ),
337
+ sum_shares=Sum("shares"),
338
+ )
339
+ .filter(account_name__isnull=False)
340
+ )
341
+
342
+
343
+ class TradeTradeProposalModelViewSet(
344
+ UserPortfolioRequestPermissionMixin, InternalUserPermissionMixin, OrderableMixin, viewsets.ModelViewSet
345
+ ):
346
+ ordering = (
347
+ "trade_proposal",
348
+ "order",
349
+ )
350
+ IDENTIFIER = "wbportfolio:tradeproposal"
351
+ search_fields = ("underlying_instrument__name",)
352
+ filterset_fields = {}
353
+ queryset = Trade.objects.all()
354
+
355
+ display_config_class = TradeTradeProposalDisplayConfig
356
+ endpoint_config_class = TradeTradeProposalEndpointConfig
357
+ serializer_class = TradeTradeProposalModelSerializer
358
+ button_config_class = TradeTradeProposalButtonConfig
359
+
360
+ @cached_property
361
+ def trade_proposal_status(self):
362
+ return get_object_or_404(TradeProposal, pk=self.kwargs["trade_proposal_id"]).status
363
+
364
+ def get_resource_class(self):
365
+ return TradeProposalTradeResource
366
+
367
+ def get_aggregates(self, queryset, paginated_queryset):
368
+ if queryset.exists():
369
+ aggregates = queryset.aggregate(
370
+ sum_target_weight=Sum(F("target_weight")),
371
+ sum_effective_weight=Sum(F("effective_weight")),
372
+ sum_weighting=Sum(F("weighting")),
373
+ )
374
+ return {
375
+ "target_weight": {"Σ": format_number(aggregates["sum_target_weight"], decimal=5)},
376
+ "effective_weight": {"Σ": format_number(aggregates["sum_effective_weight"], decimal=5)},
377
+ "weighting": {"Σ": format_number(aggregates["sum_weighting"], decimal=5)},
378
+ }
379
+ return {}
380
+
381
+ def get_serializer_class(self):
382
+ if self.trade_proposal_status != TradeProposal.Status.DRAFT:
383
+ return ReadOnlyTradeTradeProposalModelSerializer
384
+ return TradeTradeProposalModelSerializer
385
+
386
+ def add_messages(self, request, queryset=None, paginated_queryset=None, instance=None, initial=False):
387
+ if queryset:
388
+ total_target_weight = float(queryset.aggregate(c=Sum(F("target_weight")))["c"]) or 0
389
+ if round(total_target_weight, 3) != 1:
390
+ warning(request, "The total target weight does not equals to 1")
391
+
392
+ def get_queryset(self):
393
+ if self.is_portfolio_manager:
394
+ return super().get_queryset().filter(trade_proposal=self.kwargs["trade_proposal_id"])
395
+ return TradeProposal.objects.none()