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,80 @@
1
+ import math
2
+ from datetime import datetime
3
+ from decimal import Decimal
4
+ from typing import Any, Dict
5
+
6
+ from django.db import models
7
+ from wbcore.contrib.currency.import_export.handlers import CurrencyImportHandler
8
+ from wbcore.contrib.io.exceptions import DeserializationError
9
+ from wbcore.contrib.io.imports import ImportExportHandler
10
+ from wbfdm.import_export.handlers.instrument import InstrumentImportHandler
11
+
12
+
13
+ class DividendImportHandler(ImportExportHandler):
14
+ MODEL_APP_LABEL = "wbportfolio.DividendTransaction"
15
+
16
+ def __init__(self, *args, **kwargs):
17
+ super().__init__(*args, **kwargs)
18
+ self.instrument_handler = InstrumentImportHandler(self.import_source)
19
+ self.currency_handler = CurrencyImportHandler(self.import_source)
20
+
21
+ def _deserialize(self, data):
22
+ data["transaction_date"] = datetime.strptime(data["transaction_date"], "%Y-%m-%d").date()
23
+ data["value_date"] = datetime.strptime(data["value_date"], "%Y-%m-%d").date()
24
+ from wbportfolio.models import Portfolio
25
+
26
+ data["portfolio"] = Portfolio.objects.get(id=data["portfolio"])
27
+ instrument = self.instrument_handler.process_object(
28
+ data["underlying_instrument"], only_security=False, read_only=True
29
+ )[0]
30
+ if not instrument:
31
+ raise DeserializationError("Can't process this data: underlying instrument not found")
32
+ data["underlying_instrument"] = instrument
33
+ if "currency" not in data:
34
+ data["currency"] = data["portfolio"].currency
35
+ else:
36
+ data["currency"] = self.currency_handler.process_object(data["currency"], read_only=True)[0]
37
+
38
+ for field in self.model._meta.get_fields():
39
+ if not (value := data.get(field.name, None)) is None and isinstance(field, models.DecimalField):
40
+ q = 1 / (math.pow(10, 4))
41
+ data[field.name] = Decimal(value).quantize(Decimal(str(q)))
42
+
43
+ def _get_instance(self, data, history=None, **kwargs):
44
+ self.import_source.log += "\nGet DividendTransaction Instance."
45
+ self.import_source.log += f"\nParameter: Portfolio={data['portfolio']} Underlying={data['underlying_instrument']} Date={data['transaction_date']}"
46
+ dividends = history if history is not None else self.model.objects
47
+
48
+ dividends = dividends.filter(
49
+ portfolio=data["portfolio"],
50
+ transaction_date=data["transaction_date"],
51
+ value_date=data["value_date"],
52
+ underlying_instrument=data["underlying_instrument"],
53
+ price_gross=data["price_gross"],
54
+ )
55
+ if dividends.count() == 1:
56
+ self.import_source.log += "\nDividendTransaction Instance Found." ""
57
+ return dividends.first()
58
+
59
+ def _create_instance(self, data, **kwargs):
60
+ self.import_source.log += "\nCreate DividendTransaction."
61
+ return self.model.objects.create(**data, import_source=self.import_source)
62
+
63
+ def _get_history(self: models.Model, history: Dict[str, Any]) -> models.QuerySet:
64
+ val_date = datetime.strptime(history["transaction_date"], "%Y-%m-%d")
65
+ if portfolio_id := history.get("portfolio", None):
66
+ dividends = self.model.objects.filter(transaction_date__lte=val_date, portfolio=portfolio_id)
67
+ if underlying_instrument_id := history.get("underlying_instrument", None):
68
+ dividends = dividends.filter(underlying_instrument=underlying_instrument_id)
69
+ return dividends
70
+
71
+ def _post_processing_history(self: models.Model, history: models.QuerySet):
72
+ self.import_source.log += "===================="
73
+ self.import_source.log += (
74
+ "It was a historical import and the following DividendTransaction have to be deleted:"
75
+ )
76
+ for dividend in history.order_by("transaction_date"):
77
+ self.import_source.log += (
78
+ f"\n{dividend.transaction_date:%d.%m.%Y}: {dividend.shares} {dividend.price} ==> Deleted"
79
+ )
80
+ dividend.delete()
@@ -0,0 +1,58 @@
1
+ from datetime import datetime
2
+
3
+ from wbcore.contrib.currency.import_export.handlers import CurrencyImportHandler
4
+ from wbcore.contrib.io.imports import ImportExportHandler
5
+ from wbfdm.models.instruments import Cash
6
+ from wbportfolio.models.products import Product
7
+
8
+
9
+ class FeesImportHandler(ImportExportHandler):
10
+ MODEL_APP_LABEL: str = "wbportfolio.Fees"
11
+
12
+ def __init__(self, *args, **kwargs):
13
+ super().__init__(*args, **kwargs)
14
+ self.currency_handler = CurrencyImportHandler(self.import_source)
15
+
16
+ def _deserialize(self, data):
17
+ data["transaction_date"] = datetime.strptime(data["transaction_date"], "%Y-%m-%d").date()
18
+ data["fee_date"] = data["transaction_date"]
19
+ if value_date_str := data.get("value_date", None):
20
+ data["value_date"] = datetime.strptime(value_date_str, "%Y-%m-%d").date()
21
+ if book_date_str := data.get("book_date", None):
22
+ data["book_date"] = datetime.strptime(book_date_str, "%Y-%m-%d").date()
23
+
24
+ from wbportfolio.models import Portfolio
25
+
26
+ data["linked_product"] = Product.objects.get(id=data["linked_product"])
27
+ if "porfolio" in data:
28
+ data["portfolio"] = Portfolio.objects.get(id=data["portfolio"])
29
+ else:
30
+ data["portfolio"] = data["linked_product"].primary_portfolio
31
+ data["underlying_instrument"] = Cash.objects.filter(currency=data["portfolio"].currency).first()
32
+ if "currency" not in data:
33
+ data["currency"] = data["portfolio"].currency
34
+ else:
35
+ data["currency"] = self.currency_handler.process_object(data["currency"], read_only=True)[0]
36
+ data["currency_fx_rate"] = 1.0
37
+ data["total_value"] = data.get("total_value", data.get("total_value_gross", None))
38
+ data["total_value_gross"] = data.get("total_value_gross", data["total_value"])
39
+ data["calculated"] = data.get("calculated", False)
40
+
41
+ def _get_instance(self, data, history=None, **kwargs):
42
+ self.import_source.log += "\nGet Fees Instance."
43
+ self.import_source.log += f"\nParameter: Portfolio={data['portfolio']} Date={data['transaction_date']}"
44
+ fees = self.model.objects.filter(
45
+ linked_product=data["linked_product"],
46
+ fee_date=data["fee_date"],
47
+ transaction_subtype=data["transaction_subtype"],
48
+ calculated=data["calculated"],
49
+ )
50
+ if fees.exists():
51
+ if fees.count() > 1:
52
+ raise ValueError(f'Number of similar fees found > 1: {fees.values_list("id", flat=True)}')
53
+ self.import_source.log += "\nFees Instance Found." ""
54
+ return fees.first()
55
+
56
+ def _create_instance(self, data, **kwargs):
57
+ self.import_source.log += "\nCreate Fees."
58
+ return self.model.objects.create(**data, import_source=self.import_source)
@@ -0,0 +1,57 @@
1
+ from contextlib import suppress
2
+ from datetime import datetime
3
+ from decimal import Decimal
4
+ from typing import TYPE_CHECKING
5
+
6
+ from django.contrib.auth import get_user_model
7
+ from django.db.models import Q
8
+ from wbcore.contrib.io.imports import ImportExportHandler
9
+ from wbcore.contrib.notifications.dispatch import send_notification
10
+ from wbportfolio.models import Portfolio
11
+
12
+ if TYPE_CHECKING:
13
+ from wbportfolio.models.portfolio_cash_flow import DailyPortfolioCashFlow
14
+
15
+
16
+ class DailyPortfolioCashFlowImportHandler(ImportExportHandler):
17
+ MODEL_APP_LABEL = "wbportfolio.DailyPortfolioCashFlow"
18
+
19
+ def _deserialize(self, data):
20
+ data["value_date"] = datetime.strptime(data["value_date"], "%Y-%m-%d").date()
21
+ data["portfolio"] = Portfolio.objects.get(id=data["portfolio"])
22
+ if "cash" in data:
23
+ data["cash"] = Decimal(data["cash"])
24
+
25
+ if "total_assets" in data:
26
+ data["total_assets"] = Decimal(data["total_assets"])
27
+
28
+ if "cash_flow_forecast" in data:
29
+ data["cash_flow_forecast"] = Decimal(data["cash_flow_forecast"])
30
+
31
+ def _get_instance(self, data, history=None, **kwargs):
32
+ self.import_source.log += "\nGet Daily Portfolio Cash Flow Instance."
33
+ self.import_source.log += f"\nParameter: Portfolio={data['portfolio']} Value Date={data['value_date']}"
34
+
35
+ with suppress(self.model.DoesNotExist):
36
+ return self.model.objects.get(portfolio=data["portfolio"], value_date=data["value_date"])
37
+
38
+ def _post_processing_created_object(self, _object: "DailyPortfolioCashFlow"):
39
+ if not _object.portfolio.daily_cashflows.filter(value_date__gt=_object.value_date).exists():
40
+ if _object.proposed_rebalancing != Decimal(0):
41
+ color = "red" if _object.proposed_rebalancing < 0 else "green"
42
+ for user in (
43
+ get_user_model()
44
+ .objects.filter(
45
+ Q(user_permissions__codename="view_dailyportfoliocashflow")
46
+ | Q(groups__permissions__codename="view_dailyportfoliocashflow")
47
+ )
48
+ .distinct()
49
+ ):
50
+ send_notification(
51
+ code="wbportfolio.dailyportfoliocashflow.notify_rebalance",
52
+ title=f"Proposed Rebalancing in the portfolio: {_object.portfolio}",
53
+ body=f"The workbench proposes to rebalance the portfolio {_object.portfolio} by <span style='color:{color}'>{_object.proposed_rebalancing:+,.2f}</span>",
54
+ user=user,
55
+ reverse_name="wbportfolio:portfoliocashflow-detail",
56
+ reverse_args=[_object.pk],
57
+ )
@@ -0,0 +1,43 @@
1
+ from datetime import datetime
2
+ from typing import Any, Dict, Optional
3
+
4
+ from django.db import models
5
+ from wbcore.contrib.geography.models import Geography
6
+ from wbcore.contrib.io.imports import ImportExportHandler
7
+
8
+
9
+ class RegisterImportHandler(ImportExportHandler):
10
+ MODEL_APP_LABEL = "wbportfolio.Register"
11
+
12
+ def _deserialize(self, data: Dict[str, Any]):
13
+ if "opened" in data:
14
+ data["opened"] = datetime.strptime(data["opened"], "%Y-%m-%d").date()
15
+ if residence_id := data.get("residence"):
16
+ data["residence"] = Geography.countries.get(id=residence_id)
17
+ if citizenship_id := data.get("citizenship"):
18
+ data["citizenship"] = Geography.countries.get(id=citizenship_id)
19
+ if outlet_country_id := data.get("outlet_country"):
20
+ data["outlet_country"] = Geography.countries.get(id=outlet_country_id)
21
+ if custodian_country_id := data.get("custodian_country"):
22
+ data["custodian_country"] = Geography.countries.get(id=custodian_country_id)
23
+
24
+ outlet_city = data.pop("outlet_city", None)
25
+ if (outlet_country := data.get("outlet_country", None)) and outlet_city:
26
+ if res := outlet_country.lookup_descendants(outlet_city, level=Geography.Level.CITY.value):
27
+ data["outlet_city"] = res
28
+
29
+ custodian_city = data.pop("custodian_city", None)
30
+ if (custodian_country := data.get("custodian_country", None)) and custodian_city:
31
+ if res := custodian_country.lookup_descendants(custodian_city, level=Geography.Level.CITY.value):
32
+ data["custodian_city"] = res
33
+
34
+ def _get_instance(self, data: Dict[str, Any], history: Optional[models.QuerySet] = None, **kwargs) -> models.Model:
35
+ self.import_source.log += "Get Register Instance."
36
+ self.import_source.log += f"Parameter: Reference={data['register_reference']}"
37
+ if isinstance(data, int):
38
+ return self.model.objects.get(id=data)
39
+ return self.model.objects.filter(register_reference=data["register_reference"]).first()
40
+
41
+ def _create_instance(self, data: Dict[str, Any], **kwargs) -> models.Model:
42
+ self.import_source.log += "Create Register."
43
+ return self.model.objects.create(import_source=self.import_source, **data)
@@ -0,0 +1,191 @@
1
+ import math
2
+ from datetime import datetime
3
+ from decimal import Decimal
4
+ from typing import Any, Dict, Optional
5
+
6
+ from django.db import models
7
+ from wbcore.contrib.currency.import_export.handlers import CurrencyImportHandler
8
+ from wbcore.contrib.io.exceptions import DeserializationError
9
+ from wbcore.contrib.io.imports import ImportExportHandler
10
+ from wbfdm.import_export.handlers.instrument import InstrumentImportHandler
11
+ from wbfdm.models import InstrumentType
12
+ from wbportfolio.models.portfolio import Portfolio
13
+ from wbportfolio.models.products import update_outstanding_shares_as_task
14
+ from wbportfolio.utils import string_matching
15
+
16
+ from .register import RegisterImportHandler
17
+
18
+
19
+ class TradeImportHandler(ImportExportHandler):
20
+ MODEL_APP_LABEL: str = "wbportfolio.Trade"
21
+
22
+ def __init__(self, *args, **kwargs):
23
+ super().__init__(*args, **kwargs)
24
+ self.instrument_handler = InstrumentImportHandler(self.import_source)
25
+ self.register_handler = RegisterImportHandler(self.import_source)
26
+ self.currency_handler = CurrencyImportHandler(self.import_source)
27
+
28
+ def _data_changed(self, _object, change_data: Dict[str, Any], initial_data: Dict[str, Any], **kwargs):
29
+ if (new_register := change_data.get("register")) and (current_register := _object.register):
30
+ # we remplace the register only if the new one gives us more information
31
+ if new_register.register_reference == current_register.global_register_reference:
32
+ del change_data["register"]
33
+ return super()._data_changed(_object, change_data, initial_data, **kwargs)
34
+
35
+ def _deserialize(self, data: Dict[str, Any]):
36
+ if external_identifier2 := data.get("external_identifier2", None):
37
+ data["external_identifier2"] = str(external_identifier2)
38
+ if transaction_date_str := data.get("transaction_date", None):
39
+ data["transaction_date"] = datetime.strptime(transaction_date_str, "%Y-%m-%d").date()
40
+ if value_date_str := data.get("value_date", None):
41
+ data["value_date"] = datetime.strptime(value_date_str, "%Y-%m-%d").date()
42
+ if book_date_str := data.get("book_date", None):
43
+ data["book_date"] = datetime.strptime(book_date_str, "%Y-%m-%d").date()
44
+ if underlying_instrument := data.get("underlying_instrument", None):
45
+ data["underlying_instrument"] = self.instrument_handler.process_object(
46
+ underlying_instrument, only_security=False, read_only=True
47
+ )[0]
48
+ data["portfolio"] = Portfolio._get_or_create_portfolio(
49
+ self.instrument_handler, data.get("portfolio", data["underlying_instrument"])
50
+ )
51
+ if currency_data := data.get("currency", None):
52
+ data["currency"] = self.currency_handler.process_object(currency_data, read_only=True)[0]
53
+
54
+ if register_data := data.get("register", None):
55
+ data["register"] = self.register_handler.process_object(register_data)[0]
56
+
57
+ for field in self.model._meta.get_fields():
58
+ if not (value := data.get(field.name, None)) is None and isinstance(field, models.DecimalField):
59
+ q = 1 / (math.pow(10, 4))
60
+ data[field.name] = Decimal(value).quantize(Decimal(str(q)))
61
+
62
+ if "total_value" in data:
63
+ data["total_value"] = data["price"] * data["shares"]
64
+ if "total_value_fx_portfolio" in data:
65
+ data["total_value_fx_portfolio"] = data["price"] * data["shares"] * data["currency_fx_rate"]
66
+ data["marked_for_deletion"] = data.get("marked_for_deletion", False)
67
+
68
+ def _create_instance(self, data: Dict[str, Any], **kwargs) -> models.Model:
69
+ if "transaction_date" not in data: # we might get only book date and not transaction date
70
+ data["transaction_date"] = data["book_date"]
71
+
72
+ return self.model.objects.create(**data, import_source=self.import_source)
73
+
74
+ def _get_instance(self, data: Dict[str, Any], history: Optional[models.QuerySet] = None, **kwargs) -> models.Model:
75
+ self.import_source.log += "\nGet Trade Instance."
76
+
77
+ if transaction_date := data.get("transaction_date"):
78
+ dates_lookup = {"transaction_date": transaction_date}
79
+ elif book_date := data.get("book_date"):
80
+ dates_lookup = {"book_date": book_date}
81
+ else:
82
+ raise DeserializationError("date lookup is missing from data")
83
+ self.import_source.log += f"\nParameter: Product={data['underlying_instrument']} Trade-Date={transaction_date} Shares={data['shares']}"
84
+
85
+ if history.exists():
86
+ queryset = history
87
+ else:
88
+ queryset = self.model.objects.filter(marked_for_deletion=False)
89
+
90
+ queryset = queryset.filter(
91
+ models.Q(underlying_instrument=data["underlying_instrument"])
92
+ & models.Q(**dates_lookup)
93
+ & models.Q(shares=data["shares"])
94
+ )
95
+
96
+ if _id := data.get("id", None):
97
+ self.import_source.log += f"ID {_id} provided -> Load CustomerTrade"
98
+ return self.model.objects.get(id=_id)
99
+ # We need to check for external identifiers
100
+ if external_id := data.get("external_id"):
101
+ self.import_source.log += f"\nExternal Identifier used: {external_id}"
102
+ queryset = queryset.filter(external_id=external_id)
103
+ if queryset.count() == 1:
104
+ self.import_source.log += f"External ID {external_id} provided -> Load CustomerTrade"
105
+ return queryset.first()
106
+
107
+ if portfolio := data.get("portfolio", None):
108
+ queryset = queryset.filter(portfolio=portfolio)
109
+ if queryset.exists():
110
+ if bank := data["bank"]:
111
+ self.import_source.log += (
112
+ f"\n{queryset.count()} Trades found. The bank will tried to be matched against {bank}"
113
+ )
114
+ if queryset.filter(bank=bank).count() >= 1: # exact match
115
+ queryset = queryset.filter(bank=bank)
116
+ else:
117
+ best_result = string_matching(bank, queryset.values_list("bank", flat=True))
118
+ if best_result[1] >= 80:
119
+ possible_trades = queryset.filter(bank=best_result[0])
120
+ if possible_trades.count() > 1 and possible_trades.filter(claims__isnull=False).exists():
121
+ possible_trades = possible_trades.filter(claims__isnull=False)
122
+ if (
123
+ possible_trades.count() >= 1
124
+ ): # If count is greater than 1, we get exact match so we return either trades
125
+ queryset = possible_trades
126
+ if (queryset.count() > 1) and (price := data.get("price", None)):
127
+ if queryset.filter(price=price).count() == 1:
128
+ queryset = queryset.filter(price=price)
129
+ if queryset.exists():
130
+ # We try to filter by price as well
131
+ trade = queryset.first()
132
+ if queryset.count() == 1:
133
+ self.import_source.log += f"\nOne Trade found: {trade}"
134
+ if queryset.count() > 1:
135
+ self.import_source.log += f"\nMultiple similar Trades found (returning first trade): {trade}"
136
+ return trade
137
+ self.import_source.log += "\nNo trade was successfully matched."
138
+
139
+ def _get_history(self, history: Dict[str, Any]) -> models.QuerySet:
140
+ trades = self.model.objects.filter(
141
+ exclude_from_history=False,
142
+ pending=False,
143
+ transaction_subtype__in=[
144
+ self.model.Type.SUBSCRIPTION,
145
+ self.model.Type.REDEMPTION,
146
+ ], # we cannot exclude marked for deleted trade because otherwise they are never consider in the history
147
+ )
148
+ if transaction_date := history.get("transaction_date"):
149
+ trades = trades.filter(transaction_date__lte=transaction_date)
150
+ elif book_date := history.get("book_date"):
151
+ trades = trades.filter(book_date__lte=book_date)
152
+ if "underlying_instrument" in history:
153
+ trades = trades.filter(underlying_instrument__id=history["underlying_instrument"])
154
+ elif "underlying_instruments" in history:
155
+ trades = trades.filter(underlying_instrument__id__in=history["underlying_instruments"])
156
+ else:
157
+ raise ValueError("We cannot estimate history without at least the underlying instrument")
158
+ return trades
159
+
160
+ def _post_processing_objects(
161
+ self,
162
+ created_objs: list[models.Model],
163
+ modified_objs: list[models.Model],
164
+ unmodified_objs: list[models.Model],
165
+ ):
166
+ for instrument in set(
167
+ map(lambda x: x.underlying_instrument, filter(lambda t: t.is_customer_trade, created_objs + modified_objs))
168
+ ):
169
+ if instrument.instrument_type.key == "product":
170
+ update_outstanding_shares_as_task.delay(instrument.id)
171
+
172
+ def _post_processing_updated_object(self, _object):
173
+ if _object.marked_for_deletion:
174
+ _object.marked_for_deletion = False
175
+ _object.save()
176
+ self.import_source.log += "\nMarked for deletion reverted"
177
+ if _object.underlying_instrument.instrument_type == InstrumentType.PRODUCT:
178
+ _object.link_to_internal_trade()
179
+
180
+ def _post_processing_created_object(self, _object):
181
+ self._post_processing_updated_object(_object)
182
+
183
+ def _post_processing_history(self, history: models.QuerySet):
184
+ self.import_source.log += "===================="
185
+ self.import_source.log += "It was a historical import and the following Trades have to be deleted:"
186
+ for trade in history.order_by("transaction_date"):
187
+ self.import_source.log += (
188
+ f"{trade.transaction_date:%d.%m.%Y}: {trade.shares} {trade.bank} ==> Marked for deletion"
189
+ )
190
+ trade.marked_for_deletion = True
191
+ trade.save()
File without changes
@@ -0,0 +1,30 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+
5
+ def parse(import_source):
6
+ data = []
7
+ if (
8
+ (data_backend := import_source.source.data_backend)
9
+ and (backend_class := data_backend.backend_class)
10
+ and (default_mapping := getattr(backend_class, "DEFAULT_MAPPING"))
11
+ ):
12
+ df = pd.read_json(import_source.file, orient="records")
13
+ if not df.empty:
14
+ if date_label := getattr(backend_class, "DATE_LABEL", None):
15
+ df[date_label] = pd.to_datetime(df[date_label])
16
+ df = df.sort_values(by=date_label, ascending=True)
17
+ if reindex_method := getattr(backend_class, "REINDEX_METHOD", None):
18
+ df = df.set_index(date_label)
19
+ timeline = pd.date_range(start=df.index.min(), end=df.index.max(), freq="B")
20
+ df = df.reindex(timeline, method=reindex_method)
21
+ df = df.reset_index(names=date_label)
22
+ df[date_label] = df[date_label].dt.strftime("%Y-%m-%d")
23
+ df = df.replace([np.inf, -np.inf, np.nan], None).rename(columns=default_mapping)
24
+ df = df.drop(columns=df.columns.difference([*default_mapping.values()]))
25
+
26
+ if none_nullable_fields := getattr(backend_class, "NONE_NULLABLE_FIELDS", None):
27
+ df = df.dropna(subset=none_nullable_fields, how="any")
28
+
29
+ data = df.to_dict("records")
30
+ return {"data": data}
File without changes
@@ -0,0 +1,63 @@
1
+ import datetime
2
+ import re
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+ from wbportfolio.import_export.utils import convert_string_to_number
7
+ from wbportfolio.models import Product, Trade
8
+
9
+
10
+ def file_name_parse(file_name):
11
+ dates = re.findall("([0-9]{8})", file_name)
12
+
13
+ assert len(dates) > 0
14
+
15
+ parts_dict = {"valuation_date": datetime.datetime.strptime(dates[0], "%Y%m%d").date()}
16
+
17
+ isin = re.findall("([A-Z]{2}[A-Z0-9]{9}[0-9]{1})", file_name)
18
+
19
+ if len(isin) > 0:
20
+ parts_dict["isin"] = isin[0]
21
+
22
+ return parts_dict
23
+
24
+
25
+ def parse(import_source):
26
+ # Load files into a CSV DictReader
27
+ df = pd.read_csv(import_source.file, encoding="latin1", delimiter=",")
28
+ df = df.replace([np.inf, -np.inf, np.nan], None)
29
+ # Parse the Parts of the filename into the different parts
30
+ parts = file_name_parse(import_source.file.name)
31
+ df["Trade Date"] = pd.to_datetime(df["Trade Date"], format="%Y%m%d")
32
+ # Get the valuation date from the parts list
33
+ valuation_date = parts["valuation_date"]
34
+
35
+ # Iterate through the CSV File and parse the data into a list
36
+ data = list()
37
+ for nominal_data in df.to_dict("records"):
38
+ product = Product.objects.get(isin=nominal_data["ISIN"])
39
+ shares = convert_string_to_number(nominal_data["Quantity"])
40
+
41
+ # Check whether it is a buy or a sell and convert the value correspondely
42
+ shares = shares if nominal_data["Side"] == "S" else shares * -1
43
+ portfolio = product.primary_portfolio
44
+ data.append(
45
+ {
46
+ "underlying_instrument": {"id": product.id, "instrument_type": "product"},
47
+ "transaction_date": nominal_data["Trade Date"].strftime("%Y-%m-%d"),
48
+ "shares": shares,
49
+ "portfolio": portfolio.id,
50
+ # 'currency': product.currency.key,
51
+ "transaction_subtype": Trade.Type.REDEMPTION if shares < 0 else Trade.Type.SUBSCRIPTION,
52
+ "bank": nominal_data["CounterParty Name"],
53
+ "price": convert_string_to_number(nominal_data["Price"]),
54
+ }
55
+ )
56
+ import_data = {"data": data}
57
+ if "isin" in parts:
58
+ product = Product.objects.get(isin=parts["isin"])
59
+ import_data["history"] = {
60
+ "underlying_instrument": product.id,
61
+ "transaction_date": valuation_date.strftime("%Y-%m-%d"),
62
+ }
63
+ return import_data
@@ -0,0 +1,64 @@
1
+ import datetime
2
+ import logging
3
+ import re
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ from wbportfolio.import_export.utils import convert_string_to_number
8
+ from wbportfolio.models import Fees, Product
9
+
10
+ logger = logging.getLogger("importers.parsers.jpmorgan.fee")
11
+
12
+
13
+ def file_name_parse(file_name):
14
+ dates = re.findall("([0-9]{8})", file_name)
15
+
16
+ assert len(dates) == 1, "Not exactly 1 date found in the filename"
17
+
18
+ return {"valuation_date": datetime.datetime.strptime(dates[0], "%Y%m%d").date()}
19
+
20
+
21
+ def parse(import_source):
22
+ df = pd.read_csv(import_source.file, encoding="utf-16", delimiter=",")
23
+ df["Date"] = pd.to_datetime(df["Date"])
24
+
25
+ columns_fees = df.columns.difference(["ISIN", "CCY"])
26
+ df[columns_fees] = df[columns_fees].where(pd.notnull(df[columns_fees]), 0)
27
+ df = df.replace([np.inf, -np.inf, np.nan], None)
28
+
29
+ # Iterate through the CSV File and parse the data into a list
30
+ data = list()
31
+
32
+ for fee_data in df.to_dict("records"):
33
+ product = Product.objects.get(isin=fee_data["ISIN"])
34
+ fee_date = fee_data["Date"]
35
+ base_data = {
36
+ "portfolio": product.primary_portfolio.id,
37
+ "linked_product": product.id,
38
+ "transaction_date": fee_date.strftime("%Y-%m-%d"),
39
+ "calculated": False,
40
+ }
41
+ data.append(
42
+ {
43
+ "transaction_subtype": Fees.Type.PERFORMANCE,
44
+ "total_value": round(convert_string_to_number(fee_data.get("Perf_Fee * Units", 0)), 4),
45
+ "total_value_gross": round(convert_string_to_number(fee_data.get("Perf_Fee * Units", 0)), 4),
46
+ **base_data,
47
+ }
48
+ )
49
+ data.append(
50
+ {
51
+ "transaction_subtype": Fees.Type.MANAGEMENT,
52
+ "total_value": round(convert_string_to_number(fee_data.get("Mgmt_Fee * Units", 0)), 4),
53
+ **base_data,
54
+ }
55
+ )
56
+ data.append(
57
+ {
58
+ "transaction_subtype": Fees.Type.ISSUER,
59
+ "total_value": round(convert_string_to_number(fee_data.get("JPM_Fee * Units", 0)), 4),
60
+ **base_data,
61
+ }
62
+ )
63
+
64
+ return {"data": data}