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,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}
@@ -0,0 +1,80 @@
1
+ import csv
2
+ from io import StringIO
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+ from wbportfolio.models import Product, Trade
7
+ from xlrd import xldate_as_datetime
8
+
9
+ from .sylk import SYLK
10
+ from .utils import get_portfolio_id
11
+
12
+
13
+ def parse(import_source):
14
+ data = list()
15
+ sylk_handler = SYLK()
16
+ for line in [_line.decode("cp1252") for _line in import_source.file.open("rb").readlines()]:
17
+ sylk_handler.parseline(line)
18
+
19
+ buffer = StringIO()
20
+ csvwriter = csv.writer(buffer, quotechar="'", delimiter=";", lineterminator="\n", quoting=csv.QUOTE_ALL)
21
+ for line in sylk_handler.stream_rows():
22
+ csvwriter.writerow(line)
23
+
24
+ buffer.seek(0)
25
+ content = buffer.read().replace('""', "")
26
+ df = pd.read_csv(StringIO(content), sep=";", quotechar="'", usecols=[1, 3, 4, 7, 10, 15, 16, 17, 18, 20, 21])
27
+ if not df.empty:
28
+ # Filter out all non transaction rows and remove record_desc col
29
+ df = df[df["record_desc"].str.strip() == "TRANSACTION"]
30
+ del df["record_desc"]
31
+
32
+ # Convert timestamps to json conform date strings
33
+ df["trans_date"] = df["trans_date"].apply(lambda x: xldate_as_datetime(x, datemode=0).date())
34
+ df["trans_date"] = df["trans_date"].apply(lambda x: x.strftime("%Y-%m-%d"))
35
+ df["settl_date"] = df["settl_date"].apply(lambda x: xldate_as_datetime(x, datemode=0).date())
36
+ df["settl_date"] = df["settl_date"].apply(lambda x: x.strftime("%Y-%m-%d"))
37
+
38
+ # Replace all nan values with empty str
39
+ df[["register_firstname", "cust_ref"]] = df[["register_firstname", "cust_ref"]].replace(np.nan, "", regex=True)
40
+
41
+ # Merge register_firstname and cust_ref and then remove both cols
42
+ df["note"] = df["register_firstname"] + df["cust_ref"]
43
+ del df["register_firstname"]
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
+ del df["cust_ref"]
53
+
54
+ # Rename Columns
55
+
56
+ df.columns = [
57
+ "register__register_reference",
58
+ "bank",
59
+ "underlying_instrument",
60
+ "transaction_date",
61
+ "value_date",
62
+ "external_id",
63
+ "price",
64
+ "shares",
65
+ "comment",
66
+ ]
67
+ df["transaction_subtype"] = df.shares.apply(
68
+ lambda x: Trade.Type.REDEMPTION.value if x < 0 else Trade.Type.SUBSCRIPTION.value
69
+ )
70
+ df["portfolio"] = df["underlying_instrument"].apply(lambda x: get_portfolio_id(x))
71
+ df["underlying_instrument"] = df["underlying_instrument"].apply(
72
+ lambda x: {"id": x, "instrument_type": "product"}
73
+ )
74
+ df["pending"] = False
75
+ # Convert df to list of dicts
76
+ data = df.to_dict("records")
77
+
78
+ return {
79
+ "data": data,
80
+ }
@@ -0,0 +1,57 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ from wbportfolio.models import Product, Trade
4
+
5
+
6
+ def get_portfolio(x):
7
+ product = Product.objects.get(id=x)
8
+ portfolio = product.primary_portfolio
9
+ return portfolio.id
10
+
11
+
12
+ def parse(import_source):
13
+ data = list()
14
+
15
+ df = pd.read_excel(import_source.file.open(), usecols=[3, 4, 7, 10, 15, 16, 17, 18, 20, 21], engine="openpyxl")
16
+
17
+ # Filter out all non transaction rows and remove RECORD_DESC col
18
+ df = df[df["RECORD_DESC"].str.strip() == "TRANSACTION"]
19
+ del df["RECORD_DESC"]
20
+
21
+ # Convert timestamps to json conform date strings
22
+ df["TRANS_DATE"] = df["TRANS_DATE"].apply(lambda x: x.date().strftime("%Y-%m-%d"))
23
+ df["SETTL_DATE"] = df["SETTL_DATE"].apply(lambda x: x.date().strftime("%Y-%m-%d"))
24
+
25
+ # Replace all nan values with empty str
26
+ df[["REGISTER_FIRSTNAME", "CUST_REF"]] = df[["REGISTER_FIRSTNAME", "CUST_REF"]].replace(np.nan, "", regex=True)
27
+
28
+ # Merge REGISTER_FIRSTNAME and CUST_REF and then remove both cols
29
+ df["note"] = df["REGISTER_FIRSTNAME"] + df["CUST_REF"]
30
+ del df["REGISTER_FIRSTNAME"]
31
+ del df["CUST_REF"]
32
+
33
+ # Create Product Mapping and apply to df
34
+ product_mapping = {
35
+ product["isin"]: product["id"]
36
+ for product in Product.objects.filter(isin__in=df["ISIN"].unique()).values("id", "isin")
37
+ }
38
+ df["ISIN"] = df["ISIN"].apply(lambda x: product_mapping[x])
39
+
40
+ # Rename Columns
41
+ df.columns = [
42
+ "bank",
43
+ "underlying_instrument",
44
+ "transaction_date",
45
+ "value_date",
46
+ "external_id",
47
+ "price",
48
+ "shares",
49
+ "comment",
50
+ ]
51
+ df["transaction_subtype"] = df.shares.apply(lambda x: Trade.Type.REDEMPTION if x < 0 else Trade.Type.SUBSCRIPTION)
52
+ df["portfolio"] = df["underlying_instrument"].apply(lambda x: get_portfolio(x))
53
+ df["underlying_instrument"] = df["underlying_instrument"].apply(lambda x: {"id": x, "instrument_type": "product"})
54
+ # Convert df to list of dicts
55
+ data = list(df.T.to_dict().values())
56
+
57
+ return {"data": data}
@@ -0,0 +1,137 @@
1
+ import datetime
2
+ import re
3
+ from contextlib import suppress
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ from wbportfolio.models import ProductGroup
8
+
9
+
10
+ def file_name_parse(file_name):
11
+ dates = re.findall(r"([0-9]{4}-[0-9]{2}-[0-9]{2})", file_name)
12
+ isin = re.findall(r"\.([a-zA-Z0-9]*)_", file_name)
13
+
14
+ assert len(dates) == 2, "Not 2 dates found in the filename"
15
+ assert len(isin) == 1, "Not exactly 1 isin found in the filename"
16
+
17
+ return {
18
+ "isin": isin[0],
19
+ "valuation_date": datetime.datetime.strptime(dates[0], "%Y-%m-%d").date(),
20
+ "generation_date": datetime.datetime.strptime(dates[1], "%Y-%m-%d").date(),
21
+ }
22
+
23
+
24
+ def get_exchange(row):
25
+ if str(row["Asset type code"]) == "VMOB" and row["Bloom Ticker"]:
26
+ with suppress(ValueError):
27
+ [_, exchange] = str(row["Bloom Ticker"]).replace(" Equity", "").replace(" EQUITY", "").split(" ")
28
+ return {"bbg_exchange_codes": exchange}
29
+ return None
30
+
31
+
32
+ def get_underlying_instrument(row):
33
+ if str(row["Asset type code"]) == "VMOB":
34
+ data = {
35
+ "instrument_type": "equity",
36
+ "currency__key": row["currency__key"],
37
+ "isin": row["Asset Code"] or row["Security code"],
38
+ "name": row["Asset description"],
39
+ }
40
+ if bloomberg_ticker := row.get("Bloom Ticker", None):
41
+ tickers = str(bloomberg_ticker).replace(" Equity", "").replace(" EQUITY", "").split(" ")
42
+ if len(tickers) > 0:
43
+ data["ticker"] = tickers[0]
44
+ if len(tickers) > 1:
45
+ data["exchange"] = {"bbg_exchange_codes": tickers[1]}
46
+ return data
47
+ return None
48
+
49
+
50
+ def parse(import_source):
51
+ # Load file into a CSV DictReader and convert the encoding to latin1 due to hyphonation
52
+ csv_file = import_source.file.open()
53
+ # csv_file = codecs.iterdecode(csv_file, "latin1")
54
+
55
+ cols = [0, 2, 4, 6, 7, 8, 10, 13, 27, 28, 64, 67]
56
+
57
+ df = pd.read_csv(csv_file, encoding="latin1", usecols=cols)
58
+ df = df.replace([np.inf, -np.inf, np.nan], None)
59
+ columns_map = {
60
+ "Code": "portfolio",
61
+ "underlying_instrument": "underlying_instrument",
62
+ "exchange__bbg_exchange_codes": "exchange__bbg_exchange_codes",
63
+ "NAV Date": "date",
64
+ "Quantity / Amount": "initial_shares",
65
+ "Price": "initial_price",
66
+ "Ccy": "currency__key",
67
+ "FX Rate": "initial_currency_fx_rate",
68
+ "asset_valuation_date": "asset_valuation_date",
69
+ }
70
+ df = df.rename(columns=columns_map)
71
+ # Substitute the Code for the ProductGroup ID
72
+ product_group_mapping = {
73
+ product_group.identifier: product_group.id
74
+ for product_group in ProductGroup.objects.filter(identifier__in=df["portfolio"].unique())
75
+ }
76
+
77
+ df["date"] = df["date"].apply(lambda x: x.replace("/", "-"))
78
+ df["initial_currency_fx_rate"] = df["initial_currency_fx_rate"].apply(lambda x: 1 / x if x else 1).round(5)
79
+
80
+ cash_mask = df["Accounting category"].isin(["T111"])
81
+ cash = (
82
+ df.loc[
83
+ cash_mask,
84
+ ["currency__key", "initial_currency_fx_rate", "portfolio", "date", "initial_price", "initial_shares"],
85
+ ]
86
+ .groupby(["currency__key", "initial_currency_fx_rate", "portfolio", "date", "initial_price"])
87
+ .sum()
88
+ .reset_index()
89
+ ).copy()
90
+ cash["underlying_instrument"] = cash["currency__key"].apply(
91
+ lambda x: {"currency__key": x, "instrument_type": "cash"}
92
+ )
93
+
94
+ # cash_equivalents_mask, all asset type code that match TRES and which don't have accounting category T111
95
+ cash_equivalents_mask = df["Asset type code"].str.match("TRES") & ~df["Accounting category"].str.match("T111")
96
+
97
+ cash_equivalents = (
98
+ df.loc[
99
+ cash_equivalents_mask,
100
+ ["currency__key", "initial_currency_fx_rate", "portfolio", "date", "initial_price", "initial_shares"],
101
+ ]
102
+ .groupby(["currency__key", "initial_currency_fx_rate", "portfolio", "date", "initial_price"])
103
+ .sum()
104
+ .reset_index()
105
+ ).copy()
106
+ cash_equivalents["underlying_instrument"] = cash_equivalents["currency__key"].apply(
107
+ lambda x: {"currency__key": x, "instrument_type": "cash_equivalent"}
108
+ )
109
+
110
+ # equities = df.loc[df["Accounting category"].str.match("010"), :].copy() # Historically, we filter out equity base on the "accounting category" that matches "010", we had issue with equity having the code "020". We decided to use the column "Asset type code" and filter out with the code "VCOM"
111
+ equities = df.loc[df["Asset type code"].str.match("VMOB"), :].copy()
112
+ if not equities.empty:
113
+ equities["underlying_instrument"] = equities.apply(lambda x: get_underlying_instrument(x), axis=1)
114
+ equities["exchange"] = equities.apply(lambda x: get_exchange(x), axis=1)
115
+ del equities["Accounting category"]
116
+ del equities["Asset type code"]
117
+ del equities["Bloom Ticker"]
118
+ del equities["Asset description"]
119
+ df = pd.concat([cash, equities, cash_equivalents])
120
+
121
+ df["asset_valuation_date"] = df["date"]
122
+ # Rename the columns
123
+
124
+ df = df.drop(df.columns.difference(columns_map.values()), axis=1)
125
+
126
+ df["is_estimated"] = False
127
+ df = df.replace([np.inf, -np.inf, np.nan], None)
128
+ df = df[df["underlying_instrument"].notnull()]
129
+
130
+ df["weighting"] = df.initial_currency_fx_rate * df.initial_price * df.initial_shares
131
+ portfolio_weights = df.groupby("portfolio")["weighting"].sum()
132
+ df["weighting"] = df.apply(lambda x: x["weighting"] / portfolio_weights.loc[x["portfolio"]], axis=1)
133
+ df["portfolio"] = df["portfolio"].apply(
134
+ lambda x: {"id": product_group_mapping[str(x)], "instrument_type": "product_group"}
135
+ )
136
+ csv_file.close()
137
+ return {"data": df.to_dict("records")}
@@ -0,0 +1,56 @@
1
+ import codecs
2
+ import csv
3
+ import datetime
4
+ import logging
5
+
6
+ from wbportfolio.import_export.utils import convert_string_to_number
7
+ from wbportfolio.models import Fees, Product
8
+
9
+ logger = logging.getLogger("importers.parsers.sglux.fee")
10
+ # Shares class mapping between ticker to identifier
11
+ # {
12
+ # "ATNRFRP": "UNIT/RG",
13
+ # "ATNRFRC": "UNIT/RC",
14
+ # "ATNRFRE": "UNIT/R1",
15
+ # "ATNRFRU": "UNIT/R2",
16
+ # "ATNRFPP": "UNIT/PG",
17
+ # "ATNRFPC": "UNIT/PC",
18
+ # "ATNRFPE": "UNIT/P1",
19
+ # "ATNRFPU": "UNIT/P2",
20
+ # "ATNRFZU": "UNIT/H",
21
+ # "ATNRFAU": "UNIT/A",
22
+ # "ATNRFBU": "UNIT/B",
23
+ # "ATNRFFU": "UNIT/F"
24
+ # }
25
+
26
+
27
+ def parse(import_source):
28
+ fee_file = import_source.file.open()
29
+ fee_file = codecs.iterdecode(fee_file, "latin1")
30
+
31
+ # Read file into a CSV Dict Reader
32
+ fee_reader = csv.DictReader(fee_file, delimiter=",")
33
+
34
+ # Iterate through the CSV File and parse the data into a list
35
+ data = list()
36
+
37
+ for fee_data in fee_reader:
38
+ if (fee_description := fee_data["Fees description"]) and fee_description.startswith("Investment Manager fees"):
39
+ share_class = fee_data["Class"] # .split("\\")[1][0]
40
+ product = Product.objects.get(
41
+ parent__identifier=fee_data["Code"], currency__key=fee_data["Local ccy"], identifier=share_class
42
+ )
43
+ date = datetime.datetime.strptime(fee_data["NAV Date"], "%Y/%m/%d")
44
+ data.append(
45
+ {
46
+ "portfolio": product.primary_portfolio.id,
47
+ "linked_product": product.id,
48
+ "transaction_date": date.strftime("%Y-%m-%d"),
49
+ "calculated": False,
50
+ "transaction_subtype": Fees.Type.MANAGEMENT,
51
+ "total_value": round(convert_string_to_number(fee_data.get("Fees - Local ccy", 0)), 6),
52
+ }
53
+ )
54
+
55
+ fee_file.close()
56
+ return {"data": data}
@@ -0,0 +1,51 @@
1
+ import codecs
2
+ import csv
3
+ import datetime
4
+ import logging
5
+ import re
6
+
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.sglux.perf_fees")
11
+
12
+
13
+ def parse(import_source):
14
+ fee_file = import_source.file.open()
15
+ fee_file = codecs.iterdecode(fee_file, "latin1")
16
+
17
+ # Read file into a CSV Dict Reader
18
+ # fee_reader = csv.DictReader(fee_file, delimiter=',')
19
+ fee_reader = csv.reader(fee_file)
20
+ isin = re.findall("([A-Z]{2}[A-Z0-9]{9}[0-9]{1})", import_source.file.name)[0]
21
+ product = Product.objects.get(isin=isin)
22
+
23
+ # Iterate through the CSV File and parse the data into a list
24
+ data = list()
25
+
26
+ for fee_data in fee_reader:
27
+ date = datetime.datetime.strptime(fee_data[0], "%m/%d/%Y")
28
+ data.append(
29
+ {
30
+ "portfolio": product.primary_portfolio.id,
31
+ "linked_product": product.id,
32
+ "transaction_date": date.strftime("%Y-%m-%d"),
33
+ "calculated": False,
34
+ "transaction_subtype": Fees.Type.PERFORMANCE,
35
+ "total_value": round(convert_string_to_number(fee_data[1]), 6),
36
+ }
37
+ )
38
+ data.append(
39
+ {
40
+ "portfolio": product.primary_portfolio.id,
41
+ "linked_product": product.id,
42
+ "transaction_date": date.strftime("%Y-%m-%d"),
43
+ "calculated": False,
44
+ "transaction_subtype": Fees.Type.PERFORMANCE_CRYSTALIZED,
45
+ "total_value": round(convert_string_to_number(fee_data[2]), 6),
46
+ }
47
+ )
48
+
49
+ fee_file.close()
50
+
51
+ return {"data": data}