wbportfolio 1.43.1__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of wbportfolio might be problematic. Click here for more details.

Files changed (506) hide show
  1. wbportfolio/__init__.py +1 -0
  2. wbportfolio/admin/__init__.py +12 -0
  3. wbportfolio/admin/asset.py +47 -0
  4. wbportfolio/admin/custodians.py +9 -0
  5. wbportfolio/admin/portfolio.py +127 -0
  6. wbportfolio/admin/portfolio_relationships.py +22 -0
  7. wbportfolio/admin/product_groups.py +42 -0
  8. wbportfolio/admin/products.py +80 -0
  9. wbportfolio/admin/reconciliations.py +14 -0
  10. wbportfolio/admin/registers.py +17 -0
  11. wbportfolio/admin/roles.py +19 -0
  12. wbportfolio/admin/synchronization/__init__.py +2 -0
  13. wbportfolio/admin/synchronization/admin.py +114 -0
  14. wbportfolio/admin/synchronization/portfolio_synchronization.py +18 -0
  15. wbportfolio/admin/synchronization/price_computation.py +21 -0
  16. wbportfolio/admin/transactions/__init__.py +5 -0
  17. wbportfolio/admin/transactions/claim.py +16 -0
  18. wbportfolio/admin/transactions/dividends.py +14 -0
  19. wbportfolio/admin/transactions/fees.py +35 -0
  20. wbportfolio/admin/transactions/trades.py +49 -0
  21. wbportfolio/admin/transactions/transactions.py +37 -0
  22. wbportfolio/analysis/__init__.py +0 -0
  23. wbportfolio/analysis/claims.py +235 -0
  24. wbportfolio/apps.py +5 -0
  25. wbportfolio/contrib/__init__.py +0 -0
  26. wbportfolio/contrib/company_portfolio/__init__.py +0 -0
  27. wbportfolio/contrib/company_portfolio/admin.py +28 -0
  28. wbportfolio/contrib/company_portfolio/apps.py +29 -0
  29. wbportfolio/contrib/company_portfolio/configs/__init__.py +3 -0
  30. wbportfolio/contrib/company_portfolio/configs/display.py +182 -0
  31. wbportfolio/contrib/company_portfolio/configs/endpoints.py +34 -0
  32. wbportfolio/contrib/company_portfolio/configs/previews.py +37 -0
  33. wbportfolio/contrib/company_portfolio/constants.py +1 -0
  34. wbportfolio/contrib/company_portfolio/dynamic_preferences_registry.py +87 -0
  35. wbportfolio/contrib/company_portfolio/factories.py +32 -0
  36. wbportfolio/contrib/company_portfolio/filters.py +127 -0
  37. wbportfolio/contrib/company_portfolio/management.py +19 -0
  38. wbportfolio/contrib/company_portfolio/migrations/0001_initial.py +214 -0
  39. wbportfolio/contrib/company_portfolio/migrations/__init__.py +0 -0
  40. wbportfolio/contrib/company_portfolio/models.py +334 -0
  41. wbportfolio/contrib/company_portfolio/scripts.py +76 -0
  42. wbportfolio/contrib/company_portfolio/serializers.py +303 -0
  43. wbportfolio/contrib/company_portfolio/tasks.py +19 -0
  44. wbportfolio/contrib/company_portfolio/tests/__init__.py +0 -0
  45. wbportfolio/contrib/company_portfolio/tests/conftest.py +161 -0
  46. wbportfolio/contrib/company_portfolio/tests/test_models.py +161 -0
  47. wbportfolio/contrib/company_portfolio/urls.py +29 -0
  48. wbportfolio/contrib/company_portfolio/viewsets.py +195 -0
  49. wbportfolio/defaults/__init__.py +0 -0
  50. wbportfolio/defaults/fees/__init__.py +0 -0
  51. wbportfolio/defaults/fees/default.py +92 -0
  52. wbportfolio/defaults/portfolio/__init__.py +0 -0
  53. wbportfolio/defaults/portfolio/default_rebalancing.py +45 -0
  54. wbportfolio/dynamic_preferences_registry.py +58 -0
  55. wbportfolio/factories/__init__.py +35 -0
  56. wbportfolio/factories/adjustments.py +17 -0
  57. wbportfolio/factories/assets.py +75 -0
  58. wbportfolio/factories/claim.py +39 -0
  59. wbportfolio/factories/custodians.py +11 -0
  60. wbportfolio/factories/dividends.py +14 -0
  61. wbportfolio/factories/fees.py +15 -0
  62. wbportfolio/factories/indexes.py +17 -0
  63. wbportfolio/factories/portfolio_cash_flow.py +20 -0
  64. wbportfolio/factories/portfolio_cash_targets.py +15 -0
  65. wbportfolio/factories/portfolio_swing_pricings.py +15 -0
  66. wbportfolio/factories/portfolios.py +59 -0
  67. wbportfolio/factories/product_groups.py +28 -0
  68. wbportfolio/factories/products.py +56 -0
  69. wbportfolio/factories/pytest_utils.py +121 -0
  70. wbportfolio/factories/reconciliations.py +23 -0
  71. wbportfolio/factories/roles.py +20 -0
  72. wbportfolio/factories/synchronization.py +40 -0
  73. wbportfolio/factories/trades.py +35 -0
  74. wbportfolio/factories/transactions.py +21 -0
  75. wbportfolio/fdm/__init__.py +0 -0
  76. wbportfolio/fdm/tasks.py +12 -0
  77. wbportfolio/filters/__init__.py +32 -0
  78. wbportfolio/filters/assets.py +485 -0
  79. wbportfolio/filters/assets_and_net_new_money_progression.py +42 -0
  80. wbportfolio/filters/custodians.py +10 -0
  81. wbportfolio/filters/esg.py +22 -0
  82. wbportfolio/filters/performances.py +171 -0
  83. wbportfolio/filters/portfolios.py +24 -0
  84. wbportfolio/filters/positions.py +178 -0
  85. wbportfolio/filters/products.py +153 -0
  86. wbportfolio/filters/roles.py +26 -0
  87. wbportfolio/filters/signals.py +92 -0
  88. wbportfolio/filters/transactions/__init__.py +20 -0
  89. wbportfolio/filters/transactions/claim.py +394 -0
  90. wbportfolio/filters/transactions/fees.py +66 -0
  91. wbportfolio/filters/transactions/trades.py +248 -0
  92. wbportfolio/filters/transactions/transactions.py +98 -0
  93. wbportfolio/fixtures/product_factsheets.yaml +1 -0
  94. wbportfolio/fixtures/wbportfolio.yaml.gz +0 -0
  95. wbportfolio/fixtures/wbrisk_management.yaml.gz +0 -0
  96. wbportfolio/import_export/__init__.py +0 -0
  97. wbportfolio/import_export/backends/__init__.py +2 -0
  98. wbportfolio/import_export/backends/refinitiv/adjustment.py +40 -0
  99. wbportfolio/import_export/backends/ubs/__init__.py +3 -0
  100. wbportfolio/import_export/backends/ubs/asset_position.py +45 -0
  101. wbportfolio/import_export/backends/ubs/fees.py +63 -0
  102. wbportfolio/import_export/backends/ubs/instrument_price.py +44 -0
  103. wbportfolio/import_export/backends/ubs/mixin.py +15 -0
  104. wbportfolio/import_export/backends/utils.py +58 -0
  105. wbportfolio/import_export/backends/wbfdm/__init__.py +2 -0
  106. wbportfolio/import_export/backends/wbfdm/adjustment.py +50 -0
  107. wbportfolio/import_export/backends/wbfdm/dividend.py +16 -0
  108. wbportfolio/import_export/backends/wbfdm/mixin.py +15 -0
  109. wbportfolio/import_export/handlers/__init__.py +0 -0
  110. wbportfolio/import_export/handlers/adjustment.py +39 -0
  111. wbportfolio/import_export/handlers/asset_position.py +167 -0
  112. wbportfolio/import_export/handlers/dividend.py +80 -0
  113. wbportfolio/import_export/handlers/fees.py +58 -0
  114. wbportfolio/import_export/handlers/portfolio_cash_flow.py +57 -0
  115. wbportfolio/import_export/handlers/register.py +43 -0
  116. wbportfolio/import_export/handlers/trade.py +191 -0
  117. wbportfolio/import_export/parsers/__init__.py +0 -0
  118. wbportfolio/import_export/parsers/default_mapping.py +30 -0
  119. wbportfolio/import_export/parsers/jpmorgan/__init__.py +0 -0
  120. wbportfolio/import_export/parsers/jpmorgan/customer_trade.py +63 -0
  121. wbportfolio/import_export/parsers/jpmorgan/fees.py +64 -0
  122. wbportfolio/import_export/parsers/jpmorgan/strategy.py +116 -0
  123. wbportfolio/import_export/parsers/jpmorgan/valuation.py +41 -0
  124. wbportfolio/import_export/parsers/leonteq/__init__.py +0 -0
  125. wbportfolio/import_export/parsers/leonteq/customer_trade.py +47 -0
  126. wbportfolio/import_export/parsers/leonteq/equity.py +81 -0
  127. wbportfolio/import_export/parsers/leonteq/fees.py +70 -0
  128. wbportfolio/import_export/parsers/leonteq/trade.py +94 -0
  129. wbportfolio/import_export/parsers/leonteq/valuation.py +39 -0
  130. wbportfolio/import_export/parsers/natixis/__init__.py +0 -0
  131. wbportfolio/import_export/parsers/natixis/customer_trade.py +62 -0
  132. wbportfolio/import_export/parsers/natixis/d1_customer_trade.py +66 -0
  133. wbportfolio/import_export/parsers/natixis/d1_equity.py +80 -0
  134. wbportfolio/import_export/parsers/natixis/d1_fees.py +58 -0
  135. wbportfolio/import_export/parsers/natixis/d1_trade.py +70 -0
  136. wbportfolio/import_export/parsers/natixis/d1_valuation.py +41 -0
  137. wbportfolio/import_export/parsers/natixis/dividend.py +53 -0
  138. wbportfolio/import_export/parsers/natixis/equity.py +60 -0
  139. wbportfolio/import_export/parsers/natixis/fees.py +53 -0
  140. wbportfolio/import_export/parsers/natixis/trade.py +63 -0
  141. wbportfolio/import_export/parsers/natixis/utils.py +76 -0
  142. wbportfolio/import_export/parsers/natixis/valuation.py +46 -0
  143. wbportfolio/import_export/parsers/refinitiv/__init__.py +0 -0
  144. wbportfolio/import_export/parsers/refinitiv/adjustment.py +24 -0
  145. wbportfolio/import_export/parsers/sg_lux/__init__.py +0 -0
  146. wbportfolio/import_export/parsers/sg_lux/custodian_positions.py +70 -0
  147. wbportfolio/import_export/parsers/sg_lux/customer_trade.py +75 -0
  148. wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py +140 -0
  149. wbportfolio/import_export/parsers/sg_lux/customer_trade_slk.py +80 -0
  150. wbportfolio/import_export/parsers/sg_lux/customer_trade_without_pw.py +57 -0
  151. wbportfolio/import_export/parsers/sg_lux/equity.py +161 -0
  152. wbportfolio/import_export/parsers/sg_lux/fees.py +56 -0
  153. wbportfolio/import_export/parsers/sg_lux/perf_fees.py +51 -0
  154. wbportfolio/import_export/parsers/sg_lux/portfolio_cash_flow.py +29 -0
  155. wbportfolio/import_export/parsers/sg_lux/portfolio_future_cash_flow.py +36 -0
  156. wbportfolio/import_export/parsers/sg_lux/registers.py +210 -0
  157. wbportfolio/import_export/parsers/sg_lux/sylk.py +248 -0
  158. wbportfolio/import_export/parsers/sg_lux/utils.py +36 -0
  159. wbportfolio/import_export/parsers/sg_lux/valuation.py +53 -0
  160. wbportfolio/import_export/parsers/societe_generale/__init__.py +0 -0
  161. wbportfolio/import_export/parsers/societe_generale/customer_trade.py +54 -0
  162. wbportfolio/import_export/parsers/societe_generale/strategy.py +94 -0
  163. wbportfolio/import_export/parsers/societe_generale/valuation.py +37 -0
  164. wbportfolio/import_export/parsers/tellco/__init__.py +0 -0
  165. wbportfolio/import_export/parsers/tellco/customer_trade.py +64 -0
  166. wbportfolio/import_export/parsers/tellco/equity.py +86 -0
  167. wbportfolio/import_export/parsers/tellco/valuation.py +52 -0
  168. wbportfolio/import_export/parsers/ubs/__init__.py +0 -0
  169. wbportfolio/import_export/parsers/ubs/api/__init__.py +0 -0
  170. wbportfolio/import_export/parsers/ubs/api/asset_position.py +106 -0
  171. wbportfolio/import_export/parsers/ubs/api/fees.py +31 -0
  172. wbportfolio/import_export/parsers/ubs/api/instrument_price.py +20 -0
  173. wbportfolio/import_export/parsers/ubs/api/utils.py +0 -0
  174. wbportfolio/import_export/parsers/ubs/customer_trade.py +60 -0
  175. wbportfolio/import_export/parsers/ubs/equity.py +97 -0
  176. wbportfolio/import_export/parsers/ubs/historical_customer_trade.py +67 -0
  177. wbportfolio/import_export/parsers/ubs/valuation.py +52 -0
  178. wbportfolio/import_export/parsers/vontobel/__init__.py +0 -0
  179. wbportfolio/import_export/parsers/vontobel/asset_position.py +97 -0
  180. wbportfolio/import_export/parsers/vontobel/customer_trade.py +54 -0
  181. wbportfolio/import_export/parsers/vontobel/historical_customer_trade.py +40 -0
  182. wbportfolio/import_export/parsers/vontobel/instrument.py +34 -0
  183. wbportfolio/import_export/parsers/vontobel/management_fees.py +86 -0
  184. wbportfolio/import_export/parsers/vontobel/performance_fees.py +35 -0
  185. wbportfolio/import_export/parsers/vontobel/trade.py +38 -0
  186. wbportfolio/import_export/parsers/vontobel/utils.py +17 -0
  187. wbportfolio/import_export/parsers/vontobel/valuation.py +29 -0
  188. wbportfolio/import_export/resources/__init__.py +0 -0
  189. wbportfolio/import_export/resources/assets.py +68 -0
  190. wbportfolio/import_export/resources/trades.py +41 -0
  191. wbportfolio/import_export/utils.py +42 -0
  192. wbportfolio/jinja2/wbportfolio/sql/aum_nnm.sql +119 -0
  193. wbportfolio/kpi_handlers/nnm.py +163 -0
  194. wbportfolio/metric/__init__.py +0 -0
  195. wbportfolio/metric/backends/__init__.py +2 -0
  196. wbportfolio/metric/backends/base.py +86 -0
  197. wbportfolio/metric/backends/constants.py +222 -0
  198. wbportfolio/metric/backends/portfolio_base.py +255 -0
  199. wbportfolio/metric/backends/portfolio_esg.py +66 -0
  200. wbportfolio/metric/tests/__init__.py +0 -0
  201. wbportfolio/metric/tests/conftest.py +4 -0
  202. wbportfolio/metric/tests/test_portfolio_base.py +135 -0
  203. wbportfolio/metric/tests/test_portfolio_esg.py +69 -0
  204. wbportfolio/migrations/0001_initial_squashed.py +13848 -0
  205. wbportfolio/migrations/0002_product_default_sub_account_squashed_0039_alter_assetallocation_company_and_more.py +3836 -0
  206. wbportfolio/migrations/0040_instrument_financial_instrument.py +26 -0
  207. wbportfolio/migrations/0041_remove_listresearch_research_ptr_and_more.py +129 -0
  208. wbportfolio/migrations/0042_instrumentlist_instrumentlistthroughmodel_and_more.py +71 -0
  209. wbportfolio/migrations/0043_alter_instrumentlistthroughmodel_options_and_more.py +238 -0
  210. wbportfolio/migrations/0044_alter_instrumentlist_identifier.py +35 -0
  211. wbportfolio/migrations/0045_alter_instrument_financial_instrument.py +26 -0
  212. wbportfolio/migrations/0046_add_product_default_account.py +166 -0
  213. wbportfolio/migrations/0047_remove_product_default_sub_account.py +14 -0
  214. wbportfolio/migrations/0048_alter_trade_status.py +29 -0
  215. wbportfolio/migrations/0049_trade_claimed_shares.py +25 -0
  216. wbportfolio/migrations/0050_fees_fee_date_fees_wbportfolio_transac_1f7a29_idx.py +44 -0
  217. wbportfolio/migrations/0051_delete_macroreview.py +11 -0
  218. wbportfolio/migrations/0052_remove_cash_instrument_ptr_and_more.py +888 -0
  219. wbportfolio/migrations/0053_remove_product_group.py +132 -0
  220. wbportfolio/migrations/0054_portfolioinstrumentpreferredclassificationthroughmodel_and_more.py +270 -0
  221. wbportfolio/migrations/0055_remove_product__custom_management_rebates_and_more.py +139 -0
  222. wbportfolio/migrations/0056_remove_companyportfoliodata_assets_under_management_currency_and_more.py +56 -0
  223. wbportfolio/migrations/0057_alter_portfolio_preferred_instrument_classifications_and_more.py +36 -0
  224. wbportfolio/migrations/0058_pmsinstrument.py +23 -0
  225. wbportfolio/migrations/0059_fees_unique_fees.py +51 -0
  226. wbportfolio/migrations/0060_alter_portfolioportfoliothroughmodel_type.py +21 -0
  227. wbportfolio/migrations/0061_portfolio_bank_accounts_product_bank_account_and_more.py +175 -0
  228. wbportfolio/migrations/0062_alter_dailyportfoliocashflow_options.py +20 -0
  229. wbportfolio/migrations/0063_accountreconciliation_accountreconciliationline_and_more.py +133 -0
  230. wbportfolio/migrations/0064_alter_portfolio_managers_portfolio_is_tracked_and_more.py +40 -0
  231. wbportfolio/migrations/0065_alter_portfolio_managers_claim_as_shares_and_more.py +73 -0
  232. wbportfolio/migrations/0066_assetposition_initial_shares_at_custodian_and_more.py +108 -0
  233. wbportfolio/migrations/0067_assetposition_unique_asset_position.py +77 -0
  234. wbportfolio/migrations/0068_trade_internal_trade_trade_marked_as_internal_and_more.py +59 -0
  235. wbportfolio/migrations/0069_remove_portfolio_is_invested_and_more.py +56 -0
  236. wbportfolio/migrations/0070_remove_assetposition_unique_asset_position_and_more.py +82 -0
  237. wbportfolio/migrations/0071_alter_trade_options_alter_trade_order.py +22 -0
  238. wbportfolio/migrations/__init__.py +0 -0
  239. wbportfolio/models/__init__.py +26 -0
  240. wbportfolio/models/adjustments.py +246 -0
  241. wbportfolio/models/asset.py +869 -0
  242. wbportfolio/models/custodians.py +101 -0
  243. wbportfolio/models/indexes.py +33 -0
  244. wbportfolio/models/llm/wbcrm/analyze_relationship.py +58 -0
  245. wbportfolio/models/mixins/__init__.py +0 -0
  246. wbportfolio/models/mixins/instruments.py +127 -0
  247. wbportfolio/models/mixins/liquidity_stress_test.py +1307 -0
  248. wbportfolio/models/portfolio.py +1039 -0
  249. wbportfolio/models/portfolio_cash_flow.py +167 -0
  250. wbportfolio/models/portfolio_cash_targets.py +46 -0
  251. wbportfolio/models/portfolio_relationship.py +135 -0
  252. wbportfolio/models/portfolio_swing_pricings.py +51 -0
  253. wbportfolio/models/product_groups.py +230 -0
  254. wbportfolio/models/products.py +569 -0
  255. wbportfolio/models/reconciliations/__init__.py +2 -0
  256. wbportfolio/models/reconciliations/account_reconciliation_lines.py +192 -0
  257. wbportfolio/models/reconciliations/account_reconciliations.py +102 -0
  258. wbportfolio/models/reconciliations/reconciliations.py +25 -0
  259. wbportfolio/models/registers.py +132 -0
  260. wbportfolio/models/roles.py +208 -0
  261. wbportfolio/models/synchronization/__init__.py +3 -0
  262. wbportfolio/models/synchronization/portfolio_synchronization.py +292 -0
  263. wbportfolio/models/synchronization/price_computation.py +200 -0
  264. wbportfolio/models/synchronization/synchronization.py +188 -0
  265. wbportfolio/models/transactions/__init__.py +7 -0
  266. wbportfolio/models/transactions/claim.py +634 -0
  267. wbportfolio/models/transactions/dividends.py +31 -0
  268. wbportfolio/models/transactions/expiry.py +7 -0
  269. wbportfolio/models/transactions/fees.py +153 -0
  270. wbportfolio/models/transactions/trade_proposals.py +502 -0
  271. wbportfolio/models/transactions/trades.py +704 -0
  272. wbportfolio/models/transactions/transactions.py +211 -0
  273. wbportfolio/models/utils.py +12 -0
  274. wbportfolio/permissions.py +13 -0
  275. wbportfolio/pms/__init__.py +0 -0
  276. wbportfolio/pms/statistics/__init__.py +0 -0
  277. wbportfolio/pms/trading/__init__.py +1 -0
  278. wbportfolio/pms/trading/handler.py +164 -0
  279. wbportfolio/pms/typing.py +194 -0
  280. wbportfolio/preferences.py +6 -0
  281. wbportfolio/reports/__init__.py +0 -0
  282. wbportfolio/reports/monthly_position_report.py +74 -0
  283. wbportfolio/risk_management/__init__.py +0 -0
  284. wbportfolio/risk_management/backends/__init__.py +11 -0
  285. wbportfolio/risk_management/backends/accounts.py +166 -0
  286. wbportfolio/risk_management/backends/controversy_portfolio.py +63 -0
  287. wbportfolio/risk_management/backends/exposure_portfolio.py +203 -0
  288. wbportfolio/risk_management/backends/instrument_list_portfolio.py +89 -0
  289. wbportfolio/risk_management/backends/liquidity_risk.py +86 -0
  290. wbportfolio/risk_management/backends/liquidity_stress_instrument.py +86 -0
  291. wbportfolio/risk_management/backends/mixins.py +220 -0
  292. wbportfolio/risk_management/backends/product_integrity.py +111 -0
  293. wbportfolio/risk_management/backends/stop_loss_instrument.py +24 -0
  294. wbportfolio/risk_management/backends/stop_loss_portfolio.py +36 -0
  295. wbportfolio/risk_management/backends/ucits_portfolio.py +63 -0
  296. wbportfolio/risk_management/tests/__init__.py +0 -0
  297. wbportfolio/risk_management/tests/conftest.py +15 -0
  298. wbportfolio/risk_management/tests/test_accounts.py +98 -0
  299. wbportfolio/risk_management/tests/test_controversy_portfolio.py +33 -0
  300. wbportfolio/risk_management/tests/test_exposure_portfolio.py +94 -0
  301. wbportfolio/risk_management/tests/test_instrument_list_portfolio.py +60 -0
  302. wbportfolio/risk_management/tests/test_liquidity_risk.py +47 -0
  303. wbportfolio/risk_management/tests/test_product_integrity.py +55 -0
  304. wbportfolio/risk_management/tests/test_stop_loss_instrument.py +110 -0
  305. wbportfolio/risk_management/tests/test_stop_loss_portfolio.py +119 -0
  306. wbportfolio/risk_management/tests/test_ucits_portfolio.py +39 -0
  307. wbportfolio/serializers/__init__.py +42 -0
  308. wbportfolio/serializers/adjustments.py +24 -0
  309. wbportfolio/serializers/assets.py +166 -0
  310. wbportfolio/serializers/custodians.py +26 -0
  311. wbportfolio/serializers/portfolio_cash_flow.py +48 -0
  312. wbportfolio/serializers/portfolio_cash_targets.py +20 -0
  313. wbportfolio/serializers/portfolio_relationship.py +53 -0
  314. wbportfolio/serializers/portfolio_swing_pricing.py +20 -0
  315. wbportfolio/serializers/portfolios.py +143 -0
  316. wbportfolio/serializers/positions.py +76 -0
  317. wbportfolio/serializers/product_group.py +88 -0
  318. wbportfolio/serializers/products.py +331 -0
  319. wbportfolio/serializers/reconciliations.py +173 -0
  320. wbportfolio/serializers/registers.py +72 -0
  321. wbportfolio/serializers/roles.py +60 -0
  322. wbportfolio/serializers/signals.py +157 -0
  323. wbportfolio/serializers/synchronization.py +18 -0
  324. wbportfolio/serializers/transactions/__init__.py +24 -0
  325. wbportfolio/serializers/transactions/claim.py +310 -0
  326. wbportfolio/serializers/transactions/dividends.py +18 -0
  327. wbportfolio/serializers/transactions/expiry.py +18 -0
  328. wbportfolio/serializers/transactions/fees.py +32 -0
  329. wbportfolio/serializers/transactions/trades.py +315 -0
  330. wbportfolio/serializers/transactions/transactions.py +84 -0
  331. wbportfolio/static/wbportfolio/css/macro_review.css +17 -0
  332. wbportfolio/static/wbportfolio/markdown/documentation/account_holding_reconciliation.md +16 -0
  333. wbportfolio/static/wbportfolio/markdown/documentation/aggregate_asset_position_liquidity.md +25 -0
  334. wbportfolio/static/wbportfolio/markdown/documentation/company.md +78 -0
  335. wbportfolio/static/wbportfolio/markdown/documentation/earnings_instrument.md +14 -0
  336. wbportfolio/static/wbportfolio/markdown/documentation/financial_analysis_instrument_ratios.md +94 -0
  337. wbportfolio/static/wbportfolio/markdown/documentation/financial_statistics.md +44 -0
  338. wbportfolio/static/wbportfolio/markdown/documentation/person.md +70 -0
  339. wbportfolio/tasks.py +125 -0
  340. wbportfolio/templates/portfolio/email/customer_report.html +6 -0
  341. wbportfolio/templates/portfolio/email/customer_trade_notification.html +26 -0
  342. wbportfolio/templates/portfolio/email/email_base_template.html +420 -0
  343. wbportfolio/templates/portfolio/email/rebalancing_report.html +34 -0
  344. wbportfolio/templates/portfolio/macro/macro_review.html +88 -0
  345. wbportfolio/tests/__init__.py +0 -0
  346. wbportfolio/tests/conftest.py +164 -0
  347. wbportfolio/tests/models/__init__.py +0 -0
  348. wbportfolio/tests/models/test_account_reconciliation.py +191 -0
  349. wbportfolio/tests/models/test_assets.py +193 -0
  350. wbportfolio/tests/models/test_custodians.py +12 -0
  351. wbportfolio/tests/models/test_customer_trades.py +113 -0
  352. wbportfolio/tests/models/test_dividends.py +7 -0
  353. wbportfolio/tests/models/test_imports.py +192 -0
  354. wbportfolio/tests/models/test_instrument_mixins.py +48 -0
  355. wbportfolio/tests/models/test_merge.py +133 -0
  356. wbportfolio/tests/models/test_portfolio_cash_flow.py +112 -0
  357. wbportfolio/tests/models/test_portfolio_cash_targets.py +27 -0
  358. wbportfolio/tests/models/test_portfolio_swing_pricings.py +42 -0
  359. wbportfolio/tests/models/test_portfolios.py +676 -0
  360. wbportfolio/tests/models/test_product_groups.py +80 -0
  361. wbportfolio/tests/models/test_products.py +187 -0
  362. wbportfolio/tests/models/test_roles.py +82 -0
  363. wbportfolio/tests/models/test_splits.py +233 -0
  364. wbportfolio/tests/models/test_synchronization.py +617 -0
  365. wbportfolio/tests/models/transactions/__init__.py +0 -0
  366. wbportfolio/tests/models/transactions/test_claim.py +129 -0
  367. wbportfolio/tests/models/transactions/test_fees.py +65 -0
  368. wbportfolio/tests/models/transactions/test_trades.py +204 -0
  369. wbportfolio/tests/models/utils.py +13 -0
  370. wbportfolio/tests/serializers/__init__.py +0 -0
  371. wbportfolio/tests/serializers/test_claims.py +21 -0
  372. wbportfolio/tests/signals.py +151 -0
  373. wbportfolio/tests/tests.py +31 -0
  374. wbportfolio/tests/viewsets/__init__.py +0 -0
  375. wbportfolio/tests/viewsets/test_assets.py +67 -0
  376. wbportfolio/tests/viewsets/test_performances.py +72 -0
  377. wbportfolio/tests/viewsets/test_products.py +92 -0
  378. wbportfolio/tests/viewsets/transactions/__init__.py +0 -0
  379. wbportfolio/tests/viewsets/transactions/test_claims.py +146 -0
  380. wbportfolio/urls.py +247 -0
  381. wbportfolio/utils.py +30 -0
  382. wbportfolio/viewsets/__init__.py +57 -0
  383. wbportfolio/viewsets/adjustments.py +46 -0
  384. wbportfolio/viewsets/assets.py +562 -0
  385. wbportfolio/viewsets/assets_and_net_new_money_progression.py +117 -0
  386. wbportfolio/viewsets/charts/__init__.py +1 -0
  387. wbportfolio/viewsets/charts/assets.py +247 -0
  388. wbportfolio/viewsets/configs/__init__.py +6 -0
  389. wbportfolio/viewsets/configs/buttons/__init__.py +23 -0
  390. wbportfolio/viewsets/configs/buttons/adjustments.py +13 -0
  391. wbportfolio/viewsets/configs/buttons/assets.py +145 -0
  392. wbportfolio/viewsets/configs/buttons/claims.py +83 -0
  393. wbportfolio/viewsets/configs/buttons/custodians.py +76 -0
  394. wbportfolio/viewsets/configs/buttons/fees.py +14 -0
  395. wbportfolio/viewsets/configs/buttons/mixins.py +88 -0
  396. wbportfolio/viewsets/configs/buttons/portfolios.py +115 -0
  397. wbportfolio/viewsets/configs/buttons/products.py +41 -0
  398. wbportfolio/viewsets/configs/buttons/reconciliations.py +65 -0
  399. wbportfolio/viewsets/configs/buttons/registers.py +11 -0
  400. wbportfolio/viewsets/configs/buttons/signals.py +68 -0
  401. wbportfolio/viewsets/configs/buttons/trade_proposals.py +25 -0
  402. wbportfolio/viewsets/configs/buttons/trades.py +144 -0
  403. wbportfolio/viewsets/configs/display/__init__.py +61 -0
  404. wbportfolio/viewsets/configs/display/adjustments.py +81 -0
  405. wbportfolio/viewsets/configs/display/assets.py +265 -0
  406. wbportfolio/viewsets/configs/display/claim.py +299 -0
  407. wbportfolio/viewsets/configs/display/custodians.py +24 -0
  408. wbportfolio/viewsets/configs/display/esg.py +88 -0
  409. wbportfolio/viewsets/configs/display/fees.py +133 -0
  410. wbportfolio/viewsets/configs/display/portfolio_cash_flow.py +103 -0
  411. wbportfolio/viewsets/configs/display/portfolio_relationship.py +38 -0
  412. wbportfolio/viewsets/configs/display/portfolios.py +125 -0
  413. wbportfolio/viewsets/configs/display/positions.py +75 -0
  414. wbportfolio/viewsets/configs/display/product_groups.py +54 -0
  415. wbportfolio/viewsets/configs/display/product_performance.py +241 -0
  416. wbportfolio/viewsets/configs/display/products.py +249 -0
  417. wbportfolio/viewsets/configs/display/reconciliations.py +151 -0
  418. wbportfolio/viewsets/configs/display/registers.py +71 -0
  419. wbportfolio/viewsets/configs/display/roles.py +49 -0
  420. wbportfolio/viewsets/configs/display/trade_proposals.py +97 -0
  421. wbportfolio/viewsets/configs/display/trades.py +359 -0
  422. wbportfolio/viewsets/configs/display/transactions.py +55 -0
  423. wbportfolio/viewsets/configs/endpoints/__init__.py +75 -0
  424. wbportfolio/viewsets/configs/endpoints/adjustments.py +17 -0
  425. wbportfolio/viewsets/configs/endpoints/assets.py +115 -0
  426. wbportfolio/viewsets/configs/endpoints/claim.py +106 -0
  427. wbportfolio/viewsets/configs/endpoints/custodians.py +6 -0
  428. wbportfolio/viewsets/configs/endpoints/esg.py +14 -0
  429. wbportfolio/viewsets/configs/endpoints/fees.py +26 -0
  430. wbportfolio/viewsets/configs/endpoints/portfolio_relationship.py +23 -0
  431. wbportfolio/viewsets/configs/endpoints/portfolios.py +43 -0
  432. wbportfolio/viewsets/configs/endpoints/positions.py +18 -0
  433. wbportfolio/viewsets/configs/endpoints/product_groups.py +11 -0
  434. wbportfolio/viewsets/configs/endpoints/product_performance.py +29 -0
  435. wbportfolio/viewsets/configs/endpoints/products.py +37 -0
  436. wbportfolio/viewsets/configs/endpoints/reconciliations.py +31 -0
  437. wbportfolio/viewsets/configs/endpoints/roles.py +9 -0
  438. wbportfolio/viewsets/configs/endpoints/trade_proposals.py +17 -0
  439. wbportfolio/viewsets/configs/endpoints/trades.py +82 -0
  440. wbportfolio/viewsets/configs/endpoints/transactions.py +17 -0
  441. wbportfolio/viewsets/configs/menu/__init__.py +30 -0
  442. wbportfolio/viewsets/configs/menu/adjustments.py +8 -0
  443. wbportfolio/viewsets/configs/menu/assets.py +8 -0
  444. wbportfolio/viewsets/configs/menu/claim.py +41 -0
  445. wbportfolio/viewsets/configs/menu/custodians.py +11 -0
  446. wbportfolio/viewsets/configs/menu/fees.py +13 -0
  447. wbportfolio/viewsets/configs/menu/instrument_prices.py +10 -0
  448. wbportfolio/viewsets/configs/menu/portfolio_cash_flow.py +8 -0
  449. wbportfolio/viewsets/configs/menu/portfolios.py +15 -0
  450. wbportfolio/viewsets/configs/menu/positions.py +14 -0
  451. wbportfolio/viewsets/configs/menu/product_groups.py +10 -0
  452. wbportfolio/viewsets/configs/menu/product_performance.py +25 -0
  453. wbportfolio/viewsets/configs/menu/products.py +15 -0
  454. wbportfolio/viewsets/configs/menu/reconciliations.py +7 -0
  455. wbportfolio/viewsets/configs/menu/registers.py +10 -0
  456. wbportfolio/viewsets/configs/menu/roles.py +16 -0
  457. wbportfolio/viewsets/configs/menu/trades.py +18 -0
  458. wbportfolio/viewsets/configs/menu/transactions.py +8 -0
  459. wbportfolio/viewsets/configs/previews/__init__.py +1 -0
  460. wbportfolio/viewsets/configs/previews/portfolios.py +21 -0
  461. wbportfolio/viewsets/configs/titles/__init__.py +65 -0
  462. wbportfolio/viewsets/configs/titles/adjustments.py +19 -0
  463. wbportfolio/viewsets/configs/titles/assets.py +57 -0
  464. wbportfolio/viewsets/configs/titles/assets_and_net_new_money_progression.py +6 -0
  465. wbportfolio/viewsets/configs/titles/claim.py +81 -0
  466. wbportfolio/viewsets/configs/titles/custodians.py +12 -0
  467. wbportfolio/viewsets/configs/titles/esg.py +10 -0
  468. wbportfolio/viewsets/configs/titles/fees.py +25 -0
  469. wbportfolio/viewsets/configs/titles/instrument_prices.py +20 -0
  470. wbportfolio/viewsets/configs/titles/portfolios.py +32 -0
  471. wbportfolio/viewsets/configs/titles/positions.py +11 -0
  472. wbportfolio/viewsets/configs/titles/product_groups.py +12 -0
  473. wbportfolio/viewsets/configs/titles/product_performance.py +16 -0
  474. wbportfolio/viewsets/configs/titles/products.py +6 -0
  475. wbportfolio/viewsets/configs/titles/registers.py +12 -0
  476. wbportfolio/viewsets/configs/titles/roles.py +23 -0
  477. wbportfolio/viewsets/configs/titles/trades.py +51 -0
  478. wbportfolio/viewsets/configs/titles/transactions.py +8 -0
  479. wbportfolio/viewsets/custodians.py +66 -0
  480. wbportfolio/viewsets/esg.py +165 -0
  481. wbportfolio/viewsets/mixins.py +48 -0
  482. wbportfolio/viewsets/portfolio_cash_flow.py +31 -0
  483. wbportfolio/viewsets/portfolio_cash_targets.py +8 -0
  484. wbportfolio/viewsets/portfolio_relationship.py +46 -0
  485. wbportfolio/viewsets/portfolio_swing_pricing.py +8 -0
  486. wbportfolio/viewsets/portfolios.py +154 -0
  487. wbportfolio/viewsets/positions.py +292 -0
  488. wbportfolio/viewsets/product_groups.py +84 -0
  489. wbportfolio/viewsets/product_performance.py +646 -0
  490. wbportfolio/viewsets/products.py +529 -0
  491. wbportfolio/viewsets/reconciliations.py +160 -0
  492. wbportfolio/viewsets/registers.py +75 -0
  493. wbportfolio/viewsets/roles.py +44 -0
  494. wbportfolio/viewsets/signals.py +42 -0
  495. wbportfolio/viewsets/synchronization.py +25 -0
  496. wbportfolio/viewsets/transactions/__init__.py +40 -0
  497. wbportfolio/viewsets/transactions/claim.py +933 -0
  498. wbportfolio/viewsets/transactions/fees.py +190 -0
  499. wbportfolio/viewsets/transactions/mixins.py +19 -0
  500. wbportfolio/viewsets/transactions/trade_proposals.py +93 -0
  501. wbportfolio/viewsets/transactions/trades.py +395 -0
  502. wbportfolio/viewsets/transactions/transactions.py +123 -0
  503. wbportfolio-1.43.1.dist-info/METADATA +22 -0
  504. wbportfolio-1.43.1.dist-info/RECORD +506 -0
  505. wbportfolio-1.43.1.dist-info/WHEEL +5 -0
  506. wbportfolio-1.43.1.dist-info/licenses/LICENSE +4 -0
@@ -0,0 +1,60 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ from .utils import _get_underlying_instrument, file_name_parse
5
+
6
+ FIELD_MAP = {
7
+ "Close": "initial_price",
8
+ "Nb of shares": "initial_shares",
9
+ "Fx Rate": "initial_currency_fx_rate",
10
+ "Valuation Date": "asset_valuation_date",
11
+ "Quoted Crncy": "currency__key",
12
+ "exchange": "exchange",
13
+ "underlying_instrument": "underlying_instrument",
14
+ }
15
+
16
+
17
+ def _apply_adjusting_factor(row):
18
+ """
19
+ If the position is a product position, then the adjusting factor adjusts the shares otherwise, it adjusts the price.
20
+
21
+ No idea why though
22
+ """
23
+ if row["underlying_instrument"]["instrument_type"] == "product":
24
+ return pd.Series([row["initial_price"], row["initial_shares"] * row["Quotity/Adj. factor"]])
25
+ else:
26
+ return pd.Series([row["initial_price"] * row["Quotity/Adj. factor"], row["initial_shares"]])
27
+
28
+
29
+ def parse(import_source):
30
+ # Parse the Parts of the filename into the different parts
31
+ parts = file_name_parse(import_source.file.name)
32
+
33
+ # Get the valuation date and investment from the parts list
34
+ valuation_date = parts["valuation_date"]
35
+ product = parts["product"]
36
+
37
+ # Load file into a CSV DictReader
38
+ df = pd.read_csv(import_source.file, encoding="utf-16", delimiter=";")
39
+ df = df.rename(columns=FIELD_MAP)
40
+ df = df.dropna(subset=["initial_price"])
41
+ df["initial_price"] = df["initial_price"].astype("str").str.replace(" ", "").astype("float")
42
+ df["underlying_instrument"] = df[["Ticker", "Name", "currency__key"]].apply(
43
+ lambda x: _get_underlying_instrument(*x), axis=1
44
+ )
45
+ df["initial_price"] = df["initial_price"].replace(0, np.nan).fillna(1.0)
46
+ df[["initial_price", "initial_shares"]] = df[
47
+ ["initial_price", "initial_shares", "Quotity/Adj. factor", "underlying_instrument"]
48
+ ].apply(lambda x: _apply_adjusting_factor(x), axis=1)
49
+ df["exchange"] = df.underlying_instrument.apply(lambda x: x.get("exchange", None))
50
+ df = df.drop(columns=df.columns.difference(FIELD_MAP.values()))
51
+
52
+ df["portfolio__instrument_type"] = "product"
53
+ df["portfolio__id"] = product.id
54
+ df["is_estimated"] = False
55
+ df["date"] = valuation_date.strftime("%Y-%m-%d")
56
+ df["asset_valuation_date"] = pd.to_datetime(df["asset_valuation_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
57
+
58
+ df["weighting"] = df.initial_currency_fx_rate * df.initial_price * df.initial_shares
59
+ df["weighting"] = df.weighting / df.weighting.sum()
60
+ return {"data": df.to_dict("records")}
@@ -0,0 +1,53 @@
1
+ import pandas as pd
2
+ from pandas.tseries.offsets import BDay
3
+
4
+ from .utils import file_name_parse
5
+
6
+ FIELD_MAP = {
7
+ "Date": "transaction_date",
8
+ "Manag. Fees Natixis": "ISSUER",
9
+ "Manag. Fees Client": "MANAGEMENT",
10
+ "Perf fees amount": "PERFORMANCE"
11
+ # "Currency": "currency"
12
+ }
13
+
14
+
15
+ def parse(import_source):
16
+ # Parse the Parts of the filename into the different parts
17
+ parts = file_name_parse(import_source.file.name)
18
+ # Get the valuation date and investment from the parts list
19
+ parts["valuation_date"]
20
+ product = parts["product"]
21
+
22
+ df = pd.read_csv(import_source.file, encoding="utf-8", delimiter=";")
23
+ df = df.rename(columns=FIELD_MAP)
24
+ df = df.drop(columns=df.columns.difference(FIELD_MAP.values()))
25
+ df["transaction_date"] = pd.to_datetime(df["transaction_date"], dayfirst=True)
26
+
27
+ # Switch the weeekend day fees to the next monday
28
+ df["transaction_date"] = df["transaction_date"].apply(lambda x: x + BDay(1) if x.weekday() in [5, 6] else x)
29
+
30
+ # Ensure float columns are number
31
+ for col in ["MANAGEMENT", "ISSUER", "PERFORMANCE"]:
32
+ df[col] = df[col].astype("str").str.replace(" ", "").astype("float")
33
+
34
+ # Groupby and sum similar fees (e.g. Monday)
35
+ df = df.groupby("transaction_date").sum().reset_index()
36
+ df = pd.melt(
37
+ df,
38
+ id_vars=["transaction_date"],
39
+ value_vars=["MANAGEMENT", "ISSUER", "PERFORMANCE"],
40
+ var_name="transaction_subtype",
41
+ value_name="total_value",
42
+ )
43
+ df = df[df["total_value"] != 0]
44
+
45
+ df["portfolio"] = product.primary_portfolio.id
46
+ df["linked_product"] = product.id
47
+ df["transaction_date"] = df["transaction_date"].dt.strftime("%Y-%m-%d")
48
+ df["calculated"] = False
49
+ df["total_value_gross"] = df["total_value"]
50
+ df["total_value_fx_portfolio"] = df["total_value"]
51
+ df["total_value_gross_fx_portfolio"] = df["total_value"]
52
+
53
+ return {"data": df.to_dict("records")}
@@ -0,0 +1,63 @@
1
+ import pandas as pd
2
+ from wbportfolio.models import Trade
3
+
4
+ from .utils import _get_underlying_instrument, file_name_parse
5
+
6
+
7
+ def _parse_trade_type(type):
8
+ choices = {
9
+ "Decrease": Trade.Type.DECREASE,
10
+ "Increase": Trade.Type.INCREASE,
11
+ "Rebalancing": Trade.Type.REBALANCE,
12
+ "Buy": Trade.Type.BUY,
13
+ "Sell": Trade.Type.SELL,
14
+ }
15
+
16
+ return choices[type]
17
+
18
+
19
+ def parse(import_source):
20
+ data = list()
21
+ df = pd.read_csv(import_source.file, sep=";")
22
+ if not df.empty:
23
+ # Parse the Parts of the filename into the different parts
24
+ parts = file_name_parse(import_source.file.name)
25
+
26
+ # Get the valuation date and investment from the parts list
27
+ product = parts["product"]
28
+
29
+ # Iterate through the CSV File and parse the data into a list
30
+ df["underlying_instrument"] = df[["BLOOMBERG CODE", "NAME", "QUOTED_CRNCY"]].apply(
31
+ lambda x: _get_underlying_instrument(*x), axis=1
32
+ )
33
+ df["exchange"] = df.underlying_instrument.apply(lambda x: x.get("exchange", None))
34
+ columns_map = {
35
+ "underlying_instrument": "underlying_instrument",
36
+ "TRADE DATE": "transaction_date",
37
+ "EXECUTED QTY": "shares",
38
+ "EXECUTED PRICE": "price",
39
+ "FX RATE": "currency_fx_rate",
40
+ "NET PRICE(Stock crncy)": "price",
41
+ "PRICE(USD)": "price_gross",
42
+ "TRADE TYPE": "transaction_subtype",
43
+ "QUOTED_CRNCY": "currency__key",
44
+ }
45
+ df = df.rename(columns=columns_map)
46
+ float_fields = ["shares", "price", "currency_fx_rate", "price", "price_gross"]
47
+ for field in float_fields:
48
+ if field in df:
49
+ df[field] = df[field].apply(
50
+ lambda x: float(x.replace("-", "").replace(" ", "") if isinstance(x, str) else x)
51
+ )
52
+
53
+ df["price"] = df.apply(
54
+ lambda x: x["price"] * x["QUOTITY/ADJ. FACTOR"] if "QUOTITY/ADJ. FACTOR" in x else x["price"], axis=1
55
+ )
56
+ df.transaction_date = pd.to_datetime(df.transaction_date, dayfirst=True)
57
+ df.transaction_date = df.transaction_date.apply(lambda x: x.strftime("%Y-%m-%d"))
58
+ df["transaction_subtype"] = df["transaction_subtype"].apply(lambda x: _parse_trade_type(x))
59
+ df = df.drop(columns=df.columns.difference(columns_map.values()))
60
+ df["portfolio"] = product.primary_portfolio.id
61
+ df["bank"] = "Natixis Cash Transfer"
62
+ data = df.to_dict("records")
63
+ return {"data": data}
@@ -0,0 +1,76 @@
1
+ import datetime
2
+ import re
3
+
4
+ from django.db.models import Q
5
+ from wbportfolio.models import Product
6
+
7
+ INSTRUMENT_MAP_NAME = {"EDA23_AtonRa Z class": Product.objects.get(isin="LU2170995018")}
8
+
9
+
10
+ def _get_exchange_from_ticker(ticker):
11
+ try:
12
+ ticker, exchange = ticker.split(" ")
13
+ except Exception:
14
+ ticker, exchange = ticker, None
15
+ return exchange
16
+
17
+
18
+ def _get_ticker(ticker):
19
+ return ticker.split(" ")[0]
20
+
21
+
22
+ def _get_underlying_instrument(bbg_code, name, currency, instrument_type="equity", isin=None, cash_position=False):
23
+ if product := INSTRUMENT_MAP_NAME.get(bbg_code, None):
24
+ return {"id": product.id}
25
+
26
+ exchange = _get_exchange_from_ticker(bbg_code)
27
+ ticker = _get_ticker(bbg_code)
28
+
29
+ cash_position = cash_position or "CASH" == bbg_code or len(re.findall("(CASH [A-Z]{3})", bbg_code)) > 0
30
+ if cash_position:
31
+ underlying_instrument = {"instrument_type": "cash", "currency__key": currency}
32
+ else:
33
+ underlying_instrument = {
34
+ "exchange": {"bbg_exchange_codes": exchange},
35
+ "currency__key": currency,
36
+ "name": name.split(" @")[0].split("/")[
37
+ 0
38
+ ], # we remove anything after @ and also any trailing symbol (which represents some country information at natixis)
39
+ "ticker": ticker,
40
+ "instrument_type": instrument_type,
41
+ }
42
+ if not isin:
43
+ isin_re = re.findall("([A-Z]{2}[A-Z0-9]{9}[0-9]{1})", ticker)
44
+ if len(isin_re) > 0:
45
+ isin = isin_re[0]
46
+ # Natixis gives us ISIN as ticker for product. in that case, we registered the isin but we remove the ticker
47
+ underlying_instrument["instrument_type"] = "product"
48
+ del underlying_instrument["ticker"]
49
+ elif Product.objects.filter(ticker=ticker).exists():
50
+ underlying_instrument["instrument_type"] = "product"
51
+ if isin:
52
+ underlying_instrument["isin"] = isin
53
+ return underlying_instrument
54
+
55
+
56
+ def file_name_parse(file_name):
57
+ dates = re.findall(r"_([0-9]{4}-?[0-9]{2}-?[0-9]{2})", file_name)
58
+ identifier = re.findall(r"([A-Z]{2}(?![A-Z]{10}\b)[A-Z0-9]{10})_", file_name)
59
+ assert len(dates) == 2, "Not 2 dates found in the filename"
60
+ assert len(identifier) == 1, "Not exactly 1 identifier found in the filename"
61
+ try:
62
+ valuation_date = datetime.datetime.strptime(dates[0], "%Y-%m-%d").date()
63
+ except ValueError:
64
+ valuation_date = datetime.datetime.strptime(dates[0], "%Y%m%d").date()
65
+ try:
66
+ generation_date = datetime.datetime.strptime(dates[1], "%Y-%m-%d").date()
67
+ except ValueError:
68
+ generation_date = datetime.datetime.strptime(dates[1], "%Y%m%d").date()
69
+
70
+ product = Product.objects.get(Q(ticker=identifier[0]) | Q(isin=identifier[0]))
71
+
72
+ return {
73
+ "product": product,
74
+ "valuation_date": valuation_date,
75
+ "generation_date": generation_date,
76
+ }
@@ -0,0 +1,46 @@
1
+ import codecs
2
+ import csv
3
+ import datetime
4
+
5
+ from wbportfolio.import_export.utils import convert_string_to_number
6
+
7
+ from .utils import file_name_parse
8
+
9
+
10
+ def parse(import_source):
11
+ # Load file into a CSV DictReader
12
+ csv_file = import_source.file.open(mode="rt")
13
+ csv_file_type = type(csv_file.read())
14
+
15
+ # If the file is a byte string, then we need to convert it.
16
+ if csv_file_type is bytes:
17
+ csv_file = codecs.iterdecode(csv_file, "utf-8")
18
+
19
+ # Read file into a CSV Dict Reader
20
+ csv_reader = csv.DictReader(csv_file, delimiter=";")
21
+
22
+ # Parse the Parts of the filename into the different parts
23
+ parts = file_name_parse(import_source.file.name)
24
+
25
+ # Get the valuation date and investment from the parts list
26
+ parts["valuation_date"]
27
+ product = parts["product"]
28
+
29
+ # Iterate through the CSV File and parse the data into a list
30
+ data = list()
31
+
32
+ for valuation in csv_reader:
33
+ date = datetime.datetime.strptime(valuation["Date"], "%d/%m/%Y").date()
34
+ if date.weekday() not in [5, 6]:
35
+ data.append(
36
+ {
37
+ "instrument": {"instrument_type": "product", "id": product.id},
38
+ "date": date.strftime("%Y-%m-%d"),
39
+ "net_value": round(convert_string_to_number(valuation["Index Value in%"]), 6),
40
+ "gross_value": round(convert_string_to_number(valuation["Index gross in%"]), 6),
41
+ "calculated": False,
42
+ }
43
+ )
44
+
45
+ csv_file.close()
46
+ return {"data": data}
@@ -0,0 +1,24 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ from wbfdm.import_export.parsers.refinitiv.utils import _clean_and_return_dict
4
+
5
+ DEFAULT_MAPPING = {
6
+ "AX": "factor",
7
+ "Dates": "date",
8
+ "Instrument": "instrument",
9
+ }
10
+
11
+
12
+ def parse(import_source):
13
+ df = pd.read_json(import_source.file, orient="records")
14
+ df = df.replace([np.inf, -np.inf, np.nan], None)
15
+ df = df.rename(columns=DEFAULT_MAPPING).dropna(how="all", axis=1)
16
+ df = df.dropna(how="all", subset=df.columns.difference(["instrument"]))
17
+ df["date"] = pd.to_datetime(df["date"], utc=True, unit="ms")
18
+ df.date = df.date.dt.strftime("%Y-%m-%d")
19
+ df = df.dropna(how="any")
20
+ data = list()
21
+ if not df.empty:
22
+ df = df.groupby(["instrument", "date"]).first().reset_index()
23
+ data = _clean_and_return_dict(df)
24
+ return {"data": data}
File without changes
@@ -0,0 +1,70 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ import pandas as pd
4
+ from pandas.tseries.offsets import BDay
5
+ from schwifty import IBAN
6
+ from wbportfolio.models.portfolio import Portfolio
7
+
8
+ if TYPE_CHECKING:
9
+ from wbcore.contrib.io.models import ImportSource
10
+
11
+ # Bank specific Information for Societe Generale Luxembourg
12
+ BANK_COUNTRY_CODE = "LU"
13
+ BANK_CODE = "060"
14
+
15
+
16
+ def get_underlying_instrument(row: pd.Series) -> dict[str, str]:
17
+ """Get the underlying instrument from the row"""
18
+ return {
19
+ "instrument_type": "equity",
20
+ "isin": str(row["isin"]),
21
+ "name": str(row["name"]),
22
+ }
23
+
24
+
25
+ def get_portfolio_id(row: pd.Series) -> int:
26
+ """Get the portfolio id from the bank account number
27
+
28
+ Raises: Portfolio.DoesNotExist: We raise an error intentionally if the portfolio does not exist to make the import fail
29
+ """
30
+ iban = str(IBAN.generate(BANK_COUNTRY_CODE, bank_code=BANK_CODE, account_code=str(row["account_number"])))
31
+ return Portfolio.objects.get(bank_accounts__iban=iban).id
32
+
33
+
34
+ def parse(import_source: "ImportSource") -> dict:
35
+ """Parse the custodian positions file into a dictionary"""
36
+
37
+ # Get df from csv and rename columns and group by account number, isin, and date to sum the shares per date, account, and isin
38
+ df = pd.read_csv(import_source.file.open(), encoding="latin1").rename( # type: ignore
39
+ columns={
40
+ "Account number": "account_number",
41
+ "ISIN code": "isin",
42
+ "Quantity": "initial_shares",
43
+ "Position date": "date",
44
+ "Security name": "name",
45
+ }
46
+ )
47
+
48
+ df = pd.DataFrame(
49
+ df[["account_number", "isin", "initial_shares", "date", "name"]]
50
+ .groupby(["account_number", "isin", "date", "name"])
51
+ .sum()
52
+ )
53
+
54
+ # add weight per account number
55
+ df["weighting"] = df.groupby(["account_number", "date"])["initial_shares"].transform(lambda x: x / x.sum())
56
+
57
+ # reset index to remove the multi index from the groupby
58
+ df = df.reset_index()
59
+
60
+ # parse datetime string to date string and set to 1 day earlier
61
+ df["date"] = pd.to_datetime(df["date"], format="%m/%d/%Y %I:%M:%S %p") - BDay(1)
62
+ df["date"] = df["date"].dt.strftime("%Y-%m-%d")
63
+
64
+ df["underlying_instrument"] = df.apply(get_underlying_instrument, axis=1)
65
+ df["portfolio"] = df.apply(get_portfolio_id, axis=1)
66
+ df["estimated"] = True
67
+
68
+ # remove name column
69
+ df = df.drop(columns=["name", "account_number"])
70
+ return {"data": df.to_dict("records")}
@@ -0,0 +1,75 @@
1
+ from datetime import date
2
+ from io import BytesIO
3
+
4
+ import msoffcrypto
5
+ import numpy as np
6
+ import pandas as pd
7
+ from wbportfolio.models import Product, Trade
8
+
9
+
10
+ def get_portfolio(x):
11
+ product = Product.objects.get(id=x)
12
+ portfolio = product.primary_portfolio
13
+ return portfolio.id
14
+
15
+
16
+ def parse(import_source):
17
+ if credential := import_source.source.get_valid_credential(date.today()):
18
+ password = credential.password
19
+ buffer = BytesIO()
20
+
21
+ encrypted_file = msoffcrypto.OfficeFile(import_source.file)
22
+ encrypted_file.load_key(password=password)
23
+ encrypted_file.decrypt(buffer)
24
+
25
+ buffer.seek(0)
26
+
27
+ df = pd.read_excel(buffer.read(), usecols=[3, 4, 7, 10, 15, 16, 17, 18, 20, 21], engine="openpyxl")
28
+
29
+ # Filter out all non transaction rows and remove RECORD_DESC col
30
+ df = df[df["RECORD_DESC"].str.strip() == "TRANSACTION"]
31
+ del df["RECORD_DESC"]
32
+
33
+ # Convert timestamps to json conform date strings
34
+ df["TRANS_DATE"] = df["TRANS_DATE"].apply(lambda x: x.date().strftime("%Y-%m-%d"))
35
+ df["SETTL_DATE"] = df["SETTL_DATE"].apply(lambda x: x.date().strftime("%Y-%m-%d"))
36
+
37
+ # Replace all nan values with empty str
38
+ df[["REGISTER_FIRSTNAME", "CUST_REF"]] = df[["REGISTER_FIRSTNAME", "CUST_REF"]].replace(np.nan, "", regex=True)
39
+
40
+ # Merge REGISTER_FIRSTNAME and CUST_REF and then remove both cols
41
+ df["note"] = df["REGISTER_FIRSTNAME"] + df["CUST_REF"]
42
+ del df["REGISTER_FIRSTNAME"]
43
+ del df["CUST_REF"]
44
+
45
+ # Create Product Mapping and apply to df
46
+ product_mapping = {
47
+ product["isin"]: product["id"]
48
+ for product in Product.objects.filter(isin__in=df["ISIN"].unique()).values("id", "isin")
49
+ }
50
+ df["ISIN"] = df["ISIN"].apply(lambda x: product_mapping[x])
51
+
52
+ # Rename Columns
53
+ df.columns = [
54
+ "bank",
55
+ "underlying_instrument",
56
+ "transaction_date",
57
+ "value_date",
58
+ "external_id",
59
+ "price",
60
+ "shares",
61
+ "comment",
62
+ ]
63
+ df["transaction_subtype"] = df.shares.apply(
64
+ lambda x: Trade.Type.REDEMPTION if x < 0 else Trade.Type.SUBSCRIPTION
65
+ )
66
+ df["portfolio"] = df["underlying_instrument"].apply(lambda x: get_portfolio(x))
67
+ df["underlying_instrument"] = df["underlying_instrument"].apply(
68
+ lambda x: {"id": x, "instrument_type": "product"}
69
+ )
70
+
71
+ # Convert df to list of dicts
72
+ data = list(df.T.to_dict().values())
73
+
74
+ return {"data": data}
75
+ return {"data": list()}
@@ -0,0 +1,140 @@
1
+ import csv
2
+ import math
3
+ from io import StringIO
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ from wbfdm.models import InstrumentPrice
8
+ from wbportfolio.models import Product, Register, Trade
9
+ from xlrd import xldate_as_datetime
10
+
11
+ from .sylk import SYLK
12
+ from .utils import assemble_transaction_reference, get_portfolio_id
13
+
14
+
15
+ def convert_string_to_number(string):
16
+ try:
17
+ return float(string.replace(" ", "").replace(",", ""))
18
+ except ValueError:
19
+ return 0.0
20
+
21
+
22
+ def remove_not_needed_columns(df: pd.DataFrame):
23
+ del df["NORDER"]
24
+ del df["NORDER_EXTERN"]
25
+ del df["REGISTER_ID1"]
26
+ del df["ISIN1"]
27
+ del df["AMOUNT"]
28
+ del df["QUANTITY"]
29
+ del df["AMOUNT_EST_EUR"]
30
+ del df["CODE_OPERATION"]
31
+ del df["TRADE_DATE"]
32
+ del df["VALUE_DATE"]
33
+ del df["REGISTER_DEAL_NAME"]
34
+ del df["REGISTER_ID2"]
35
+
36
+
37
+ def convert_dates(df: pd.DataFrame):
38
+ df["trade_date"] = df["TRADE_DATE"].apply(lambda x: xldate_as_datetime(x, datemode=0).date())
39
+ df["trade_date"] = df["trade_date"].apply(lambda x: x.strftime("%Y-%m-%d"))
40
+ df["value_date"] = df["VALUE_DATE"].apply(lambda x: xldate_as_datetime(x, datemode=0).date())
41
+ df["value_date"] = df["value_date"].apply(lambda x: x.strftime("%Y-%m-%d"))
42
+
43
+
44
+ def map_products_by_isin(df: pd.DataFrame):
45
+ product_mapping = {
46
+ product["isin"]: product["id"]
47
+ for product in Product.objects.filter(isin__in=df["ISIN1"].unique()).values("id", "isin")
48
+ }
49
+ df["underlying_instrument"] = df["ISIN1"].apply(lambda x: product_mapping[x])
50
+ df["portfolio"] = df["underlying_instrument"].apply(lambda x: get_portfolio_id(x))
51
+
52
+
53
+ def map_registers_by_references(df: pd.DataFrame):
54
+ register_mapping = {
55
+ register["register_reference"]: register["id"]
56
+ for register in Register.objects.filter(register_reference__in=df["REGISTER_ID1"].unique()).values(
57
+ "id", "register_reference"
58
+ )
59
+ }
60
+ df["register"] = df["REGISTER_ID1"].apply(lambda x: register_mapping[str(x)])
61
+
62
+
63
+ def get_price(x):
64
+ try:
65
+ estimated_price = float(
66
+ InstrumentPrice.objects.filter(
67
+ instrument=x["underlying_instrument"], calculated=False, date__lte=x["trade_date"]
68
+ )
69
+ .latest("date")
70
+ .net_value
71
+ )
72
+ except InstrumentPrice.DoesNotExist:
73
+ try:
74
+ estimated_price = Product.objects.get(id=x["underlying_instrument"]).issue_price
75
+ except Product.DoesNotExist:
76
+ estimated_price = 100
77
+ return estimated_price
78
+
79
+
80
+ def get_shares(x):
81
+ multiplier = 1 if x["CODE_OPERATION"] == 1 else -1
82
+ if x["QUANTITY"] and not math.isnan(x["QUANTITY"]):
83
+ return round(x["QUANTITY"] * multiplier, 4)
84
+
85
+ return round((x["AMOUNT"] * multiplier) / x["price"], 4)
86
+
87
+
88
+ def parse(import_source):
89
+ data = list()
90
+
91
+ sylk_handler = SYLK()
92
+ for line in [_line.decode("cp1252") for _line in import_source.file.open("rb").readlines()]:
93
+ sylk_handler.parseline(line)
94
+
95
+ buffer = StringIO()
96
+ csvwriter = csv.writer(buffer, quotechar="'", delimiter=";", lineterminator="\n", quoting=csv.QUOTE_ALL)
97
+ for line in sylk_handler.stream_rows():
98
+ csvwriter.writerow(line)
99
+
100
+ buffer.seek(0)
101
+ content = buffer.read().replace('""', "")
102
+ df = pd.read_csv(StringIO(content), sep=";", quotechar="'", usecols=[0, 4, 5, 6, 7, 12, 13, 15, 16, 17, 18, 22])
103
+ if not df.empty:
104
+ df["pending"] = True
105
+
106
+ df.loc[df["CODE_OPERATION"] == 6, "pending"] = False
107
+ transfers = df.loc[df["CODE_OPERATION"] == 6, :].copy()
108
+ transfers["CODE_OPERATION"] = 2
109
+ transfers["QUANTITY"] = -transfers["QUANTITY"]
110
+ transfers["TRANSFER_REGISTER"] = transfers["REGISTER_ID1"].astype("int").astype("str")
111
+ transfers["REGISTER_ID1"] = transfers["REGISTER_ID2"].astype("int").astype("str")
112
+ transfers["REGISTER_DEAL_NAME"] = transfers["REGISTER_ID1"].apply(
113
+ lambda x: Register.objects.get(register_reference=x).register_name_1
114
+ )
115
+
116
+ df = pd.concat([df, transfers], axis=0)
117
+
118
+ if "TRANSFER_REGISTER" in df:
119
+ df["TRANSFER_REGISTER"] = df["TRANSFER_REGISTER"].replace(np.nan, "", regex=True)
120
+
121
+ convert_dates(df)
122
+ df["NORDER_EXTERN"] = df["NORDER_EXTERN"].replace(np.nan, "", regex=True)
123
+ map_products_by_isin(df)
124
+ df["price"] = df.apply(get_price, axis=1)
125
+ df["shares"] = df.apply(get_shares, axis=1)
126
+ df["transaction_subtype"] = df.shares.apply(
127
+ lambda x: Trade.Type.REDEMPTION.value if x < 0 else Trade.Type.SUBSCRIPTION.value
128
+ )
129
+
130
+ df["bank"] = df["REGISTER_DEAL_NAME"]
131
+ df["external_identifier2"] = df["NORDER"]
132
+ df["register__register_reference"] = df["REGISTER_ID1"]
133
+ df["external_id"] = df.apply(assemble_transaction_reference, axis=1)
134
+
135
+ if "TRANSFER_REGISTER" in df:
136
+ del df["TRANSFER_REGISTER"]
137
+ remove_not_needed_columns(df)
138
+ # Convert df to list of dicts
139
+ data = df.rename(columns={"trade_date": "transaction_date"}).to_dict("records")
140
+ return {"data": data}