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,569 @@
1
+ from contextlib import suppress
2
+ from datetime import date, datetime, time
3
+ from decimal import Decimal
4
+
5
+ from celery import shared_task
6
+ from django.contrib import admin
7
+ from django.contrib.postgres.constraints import ExclusionConstraint
8
+ from django.contrib.postgres.fields import DateRangeField, RangeOperators
9
+ from django.core.validators import MaxValueValidator, MinValueValidator
10
+ from django.db import models
11
+ from django.db.models import (
12
+ BooleanField,
13
+ Case,
14
+ DecimalField,
15
+ ExpressionWrapper,
16
+ F,
17
+ OuterRef,
18
+ Subquery,
19
+ Value,
20
+ When,
21
+ )
22
+ from django.db.models.functions import Coalesce
23
+ from django.db.models.signals import pre_save
24
+ from django.dispatch import receiver
25
+ from pandas.tseries.offsets import BDay
26
+ from wbcore.contrib.ai.llm.config import add_llm_prompt
27
+ from wbcore.contrib.currency.models import CurrencyFXRates
28
+ from wbcore.contrib.directory.models import Entry
29
+ from wbcore.signals import pre_merge
30
+ from wbcore.utils.enum import ChoiceEnum
31
+ from wbcrm.models.accounts import Account
32
+ from wbfdm.models.instruments.instrument_prices import InstrumentPrice
33
+ from wbfdm.models.instruments.instruments import InstrumentManager, InstrumentType
34
+ from wbportfolio.models.llm.wbcrm.analyze_relationship import get_holding_prompt
35
+ from wbportfolio.models.portfolio_relationship import InstrumentPortfolioThroughModel
36
+
37
+ from .mixins.instruments import PMSInstrument
38
+
39
+
40
+ class TypeOfReturn(ChoiceEnum):
41
+ TOTAL_RETURN = "Total return"
42
+ YIELD = "Yield"
43
+ PRICE_RETURN = "Price return"
44
+ ABSOLUT_RETURN = "Absolut return"
45
+
46
+
47
+ class AssetClass(ChoiceEnum):
48
+ EQUITY = "Equity"
49
+ FIXED_INCOME = "Fixed income"
50
+ PRIVATE_EQUITY = "Private equity"
51
+ DERIVATIVE = "Derivative"
52
+ CASH = "Cash"
53
+ CRYPTOCURRENCY = "Cryptocurrency"
54
+ COMMODITY = "Commodity"
55
+ HEDGE_FUND = "Hedge Fund"
56
+ MULTI_ASSET = "Multi Asset"
57
+
58
+
59
+ class LegalStructure(ChoiceEnum):
60
+ AMC = "Actively Managed Certificate"
61
+ SICAV_UCITS = "SICAV (UCITS)"
62
+ PLC_UCITS = "PLC (UCITS)"
63
+ SICAV_AIF = "SICAV (AIFs)"
64
+ SCF_UCITS = "Swiss Contractual Fund (UCITS)"
65
+ FCP_AIF = "FCP (AIFs)"
66
+ PLC_AIF = "PLC (AIFs)"
67
+ SCF_AIF = "Swiss Contractual Fund (AIF) "
68
+
69
+
70
+ class InvestmentIndex(ChoiceEnum):
71
+ LONG = "Long"
72
+ SHORT = "Short"
73
+ LONG_SHORT = "Long/Short"
74
+
75
+
76
+ class Liquidy(ChoiceEnum):
77
+ DAILY = "Daily"
78
+ WEEKLY = "Weekly"
79
+ MONTHLY = "Monthly"
80
+ QUATERLY = "Quaterly"
81
+ YEARLY = "Yearly"
82
+
83
+
84
+ class DefaultProductManager(InstrumentManager):
85
+ def get_queryset(self):
86
+ today = date.today()
87
+ return (
88
+ super()
89
+ .get_queryset()
90
+ .annotate(
91
+ value_date=Case(
92
+ When(last_valuation_date__isnull=False, then=F("last_valuation_date")),
93
+ default=Value(today),
94
+ output_field=models.DateField(),
95
+ ),
96
+ is_invested=models.Exists(
97
+ InstrumentPortfolioThroughModel.objects.filter(
98
+ instrument=models.OuterRef("pk"),
99
+ portfolio__invested_timespan__startswith__lte=OuterRef("value_date"),
100
+ portfolio__invested_timespan__endswith__gt=OuterRef("value_date"),
101
+ )
102
+ ),
103
+ current_bank_fees=Coalesce(
104
+ Subquery(
105
+ FeeProductPercentage.objects.filter(
106
+ product=OuterRef("pk"),
107
+ type=FeeProductPercentage.Type.BANK,
108
+ timespan__startswith__lte=OuterRef("value_date"),
109
+ timespan__endswith__gt=OuterRef("value_date"),
110
+ ).values("percent")[:1]
111
+ ),
112
+ Decimal(0),
113
+ ),
114
+ current_management_fees=Coalesce(
115
+ Subquery(
116
+ FeeProductPercentage.objects.filter(
117
+ product=OuterRef("pk"),
118
+ type=FeeProductPercentage.Type.MANAGEMENT,
119
+ timespan__startswith__lte=OuterRef("value_date"),
120
+ timespan__endswith__gt=OuterRef("value_date"),
121
+ ).values("percent")[:1]
122
+ ),
123
+ Decimal(0),
124
+ ),
125
+ current_performance_fees=Coalesce(
126
+ Subquery(
127
+ FeeProductPercentage.objects.filter(
128
+ product=OuterRef("pk"),
129
+ type=FeeProductPercentage.Type.PERFORMANCE,
130
+ timespan__startswith__lte=OuterRef("value_date"),
131
+ timespan__endswith__gt=OuterRef("value_date"),
132
+ ).values("percent")[:1]
133
+ ),
134
+ Decimal(0),
135
+ ),
136
+ current_performance_fees_vat_deduction=Coalesce(
137
+ Subquery(
138
+ FeeProductPercentage.objects.filter(
139
+ product=OuterRef("pk"),
140
+ type=FeeProductPercentage.Type.PERFORMANCE,
141
+ timespan__startswith__lte=OuterRef("value_date"),
142
+ timespan__endswith__gt=OuterRef("value_date"),
143
+ ).values("vat_deduction")[:1]
144
+ ),
145
+ Decimal(0),
146
+ ),
147
+ current_gross_performance_fees_percent=F("current_performance_fees")
148
+ + F("current_performance_fees_vat_deduction"),
149
+ current_total_issuer_fees=F("current_bank_fees") + F("current_management_fees"),
150
+ )
151
+ )
152
+
153
+
154
+ class FeeProductPercentage(models.Model):
155
+ class Type(models.TextChoices):
156
+ MANAGEMENT = "MANAGEMENT", "Management"
157
+ PERFORMANCE = "PERFORMANCE", "Performance"
158
+ BANK = "BANK", "Bank"
159
+
160
+ product = models.ForeignKey("wbportfolio.Product", related_name="fees_percentages", on_delete=models.CASCADE)
161
+ type = models.CharField(max_length=16, default=Type.MANAGEMENT, choices=Type.choices)
162
+ vat_deduction = models.DecimalField(
163
+ max_digits=6,
164
+ decimal_places=6,
165
+ default=Decimal(0),
166
+ verbose_name="VAT Deduction",
167
+ help_text="The VAT deducted from this fees percentage",
168
+ )
169
+ percent = models.DecimalField(max_digits=6, decimal_places=6)
170
+ timespan = DateRangeField()
171
+
172
+ class Meta:
173
+ verbose_name = "Fees Percentage"
174
+ verbose_name_plural = "Fees Percentage"
175
+ constraints = [
176
+ ExclusionConstraint(
177
+ name="exclude_overlapping_product_fees_type",
178
+ expressions=[
179
+ ("timespan", RangeOperators.OVERLAPS),
180
+ ("product", RangeOperators.EQUAL),
181
+ ("type", RangeOperators.EQUAL),
182
+ ],
183
+ ),
184
+ ]
185
+
186
+ @property
187
+ def net_percent(self) -> Decimal:
188
+ return self.percent
189
+
190
+ @property
191
+ def gross_percent(self) -> Decimal:
192
+ return self.percent + self.vat_deduction
193
+
194
+
195
+ class Product(PMSInstrument):
196
+ price_computation = models.ForeignKey(
197
+ "wbportfolio.PriceComputation",
198
+ null=True,
199
+ blank=True,
200
+ on_delete=models.SET_NULL,
201
+ related_name="products",
202
+ verbose_name="Price Computation Method",
203
+ )
204
+
205
+ share_price = models.PositiveIntegerField(
206
+ default=100,
207
+ verbose_name="Share Price",
208
+ help_text="The initial share price that is used to calculate the nominal value of a product.",
209
+ )
210
+ initial_high_water_mark = models.PositiveIntegerField(
211
+ default=100,
212
+ verbose_name="Initial High Water Mark",
213
+ help_text="Initial High Water Mark",
214
+ )
215
+
216
+ bank = models.ForeignKey(
217
+ "directory.Company",
218
+ related_name="issues_products",
219
+ on_delete=models.PROTECT,
220
+ verbose_name="Bank",
221
+ help_text="The Bank that holds the product. A company from the CRM.",
222
+ )
223
+
224
+ white_label_customers = models.ManyToManyField(
225
+ "directory.Entry",
226
+ related_name="white_label_customer_products",
227
+ blank=True,
228
+ verbose_name="White Label Customers",
229
+ help_text="Specifies whether a product is a white label product or not. If at least one customer is specified, this product becomes a white label product and can only be seen by the respected customers.",
230
+ )
231
+
232
+ default_account = models.ForeignKey(
233
+ "wbcrm.Account",
234
+ related_name="default_product_accounts",
235
+ limit_choices_to=models.Q(is_terminal_account=True),
236
+ blank=True,
237
+ null=True,
238
+ on_delete=models.SET_NULL,
239
+ verbose_name="Default Account",
240
+ help_text="If a default Account is set, then all newly created tradesare automatically matched to this sub account.",
241
+ )
242
+
243
+ fee_calculation = models.ForeignKey(
244
+ "wbportfolio.FeeCalculation",
245
+ null=True,
246
+ blank=True,
247
+ on_delete=models.SET_NULL,
248
+ related_name="products",
249
+ verbose_name="Fee Calculation Method",
250
+ )
251
+
252
+ termsheet = models.FileField(max_length=256, null=True, blank=True, upload_to="portfolio/product/termsheets")
253
+
254
+ class Dividend(models.TextChoices):
255
+ CAPITALISATION = "CAPITALISATION", "Capitalisation"
256
+ DISTRIBUTION = "DISTRIBUTION", "Distribution"
257
+
258
+ type_of_return = models.CharField(
259
+ max_length=16,
260
+ default=TypeOfReturn.TOTAL_RETURN.name,
261
+ choices=TypeOfReturn.choices(),
262
+ verbose_name="Type of Return",
263
+ )
264
+ asset_class = models.CharField(
265
+ max_length=16,
266
+ default=AssetClass.EQUITY.name,
267
+ choices=AssetClass.choices(),
268
+ verbose_name="Asset Class",
269
+ )
270
+ legal_structure = models.CharField(
271
+ max_length=16,
272
+ default=LegalStructure.AMC.name,
273
+ choices=LegalStructure.choices(),
274
+ verbose_name="Legal Structure",
275
+ )
276
+ investment_index = models.CharField(
277
+ max_length=16,
278
+ default=InvestmentIndex.LONG.name,
279
+ choices=InvestmentIndex.choices(),
280
+ verbose_name="Long/Short Strategy",
281
+ )
282
+ liquidity = models.CharField(
283
+ max_length=16,
284
+ default=Liquidy.DAILY.name,
285
+ choices=Liquidy.choices(),
286
+ verbose_name="Liquidity",
287
+ )
288
+ risk_scale = models.IntegerField(
289
+ validators=[MinValueValidator(1), MaxValueValidator(7)],
290
+ default=4,
291
+ verbose_name="Risk Scale",
292
+ )
293
+
294
+ jurisdiction = models.ForeignKey(
295
+ "geography.Geography",
296
+ related_name="products",
297
+ null=True,
298
+ blank=True,
299
+ on_delete=models.SET_NULL,
300
+ verbose_name="Country on which this product is issued and bonded by law",
301
+ help_text="The country of jurisdiction",
302
+ limit_choices_to={"level": 1},
303
+ )
304
+
305
+ dividend = models.CharField(
306
+ max_length=16,
307
+ default=Dividend.CAPITALISATION,
308
+ choices=Dividend.choices,
309
+ verbose_name="Type of Dividends",
310
+ )
311
+
312
+ minimum_subscription = models.IntegerField(
313
+ default=100,
314
+ help_text="Minimum subscription amount allowed to invest in the fund",
315
+ )
316
+
317
+ cut_off_time = models.TimeField(default=time(13, 0, 0))
318
+ external_webpage = models.URLField(blank=True, null=True)
319
+
320
+ bank_account = models.ForeignKey(
321
+ to="directory.BankingContact",
322
+ related_name="wbportfolio_products",
323
+ null=True,
324
+ blank=True,
325
+ on_delete=models.PROTECT,
326
+ )
327
+
328
+ objects = DefaultProductManager()
329
+
330
+ class Meta:
331
+ verbose_name = "Product"
332
+ verbose_name_plural = "Products"
333
+
334
+ def pre_save(self):
335
+ super().pre_save()
336
+ self.instrument_type = InstrumentType.PRODUCT
337
+ if "market_data" not in self.dl_parameters:
338
+ # we default to the internal dataloader
339
+ self.dl_parameters["market_data"] = {
340
+ "path": "wbfdm.contrib.internal.dataloaders.market_data.MarketDataDataloader"
341
+ }
342
+
343
+ self.is_managed = True
344
+
345
+ def get_title(self):
346
+ if self.parent:
347
+ return f"{self.parent.name} ({self.name})"
348
+ return self.name
349
+
350
+ def compute_str(self):
351
+ computed_str = f"{self.get_title()} - {self.bank.name} - {self.currency.key}"
352
+ if self.id:
353
+ fees = (self.bank_fees + self.management_fees) * 100
354
+ computed_str += f" - {fees:.2f} % Mgmt Fees"
355
+ computed_str += f" ({self.isin})"
356
+ return computed_str
357
+
358
+ @property
359
+ def white_label_product(self):
360
+ return self.white_label_customers.all().count() > 0
361
+
362
+ @property
363
+ def urlify_title(self):
364
+ url = self.name + "-" + self.bank.name + "-" + self.isin + "-" + self.currency.key
365
+ return url.lower()
366
+
367
+ @property
368
+ @admin.display(description="Invested")
369
+ def _is_invested(self) -> bool:
370
+ """
371
+ Return True if the associated asset portfolio is invested
372
+ """
373
+ return getattr(self, "is_invested", self.portfolio and self.portfolio.is_invested_at_date(date.today()))
374
+
375
+ @property
376
+ def group(self): # for backward compatibility
377
+ from wbportfolio.models.product_groups import ProductGroup
378
+
379
+ if self.parent:
380
+ with suppress(ProductGroup.DoesNotExist):
381
+ return ProductGroup.objects.get(id=self.parent.id)
382
+ return None
383
+
384
+ def get_fees_percent(self, val_date: date, fee_type: FeeProductPercentage.Type, net: bool = True) -> Decimal:
385
+ try:
386
+ fee = FeeProductPercentage.objects.get(
387
+ product=self, type=fee_type, timespan__startswith__lte=val_date, timespan__endswith__gt=val_date
388
+ )
389
+ percent = fee.percent
390
+ if not net:
391
+ percent += fee.vat_deduction
392
+ return percent
393
+ except FeeProductPercentage.DoesNotExist:
394
+ return Decimal(0)
395
+
396
+ @property
397
+ def bank_fees(self) -> Decimal:
398
+ return self.get_fees_percent(date.today(), FeeProductPercentage.Type.BANK)
399
+
400
+ @property
401
+ def management_fees(self) -> Decimal:
402
+ return self.get_fees_percent(date.today(), FeeProductPercentage.Type.MANAGEMENT)
403
+
404
+ @property
405
+ def performance_fees(self) -> Decimal:
406
+ return self.get_fees_percent(date.today(), FeeProductPercentage.Type.PERFORMANCE)
407
+
408
+ @property
409
+ def gross_performance_fees(self) -> Decimal:
410
+ return self.get_fees_percent(date.today(), FeeProductPercentage.Type.PERFORMANCE, net=False)
411
+
412
+ def get_high_water_mark(self, val_date: date) -> Decimal:
413
+ """Returns the high water mark of a instrument
414
+
415
+ The high water mark is the highest previously reached net price
416
+
417
+ Returns:
418
+ decimal.Decimal -- High Water Mark
419
+ """
420
+ latest_high_water_mark = (
421
+ self.valuations.filter(date__lt=val_date).aggregate(high_water_mark=models.Max(F("net_value")))[
422
+ "high_water_mark"
423
+ ]
424
+ or self.share_price
425
+ )
426
+ return max(latest_high_water_mark, self.initial_high_water_mark)
427
+
428
+ @classmethod
429
+ def get_endpoint_basename(cls):
430
+ return "wbportfolio:product"
431
+
432
+ @classmethod
433
+ def get_representation_endpoint(cls):
434
+ return "wbportfolio:productrepresentation-list"
435
+
436
+ @classmethod
437
+ def get_representation_value_key(cls):
438
+ return "id"
439
+
440
+ @classmethod
441
+ def get_representation_label_key(cls):
442
+ return "{{computed_str}} ({{bank_name}})"
443
+
444
+ @classmethod
445
+ def get_last_valid_active_product_date(cls):
446
+ current_date = (datetime.today() - BDay(1)).date()
447
+ filter_date = current_date
448
+ for product in Product.active_objects.all():
449
+ if product.valuations.filter(date__lte=current_date).exists():
450
+ last_valid_date = product.valuations.filter(date__lte=current_date).latest("date").date
451
+ if last_valid_date < filter_date:
452
+ filter_date = last_valid_date
453
+ return filter_date
454
+
455
+ @classmethod
456
+ def get_products(cls, person, base_qs: models.QuerySet | None = None):
457
+ if base_qs is None:
458
+ base_qs = cls.objects.all()
459
+ if person.is_internal or person.user_account.is_superuser:
460
+ return base_qs.all()
461
+ return base_qs.filter(
462
+ models.Q(white_label_customers__isnull=True)
463
+ | models.Q(white_label_customers__in=[person, *person.employers.all()])
464
+ ).distinct()
465
+
466
+ @classmethod
467
+ def subquery_is_white_label_product(cls, product_pk_name="pk"):
468
+ return Coalesce(
469
+ models.Subquery(
470
+ Entry.objects.filter(white_label_customer_products__pk=models.OuterRef(product_pk_name))
471
+ .values("white_label_customer_products")
472
+ .annotate(
473
+ num_white_label_customers=models.Count("*"),
474
+ is_white_label_product=models.Case(
475
+ models.When(num_white_label_customers__gt=0, then=True),
476
+ output_field=models.BooleanField(),
477
+ ),
478
+ )
479
+ .values("is_white_label_product")[:1]
480
+ ),
481
+ False,
482
+ output_field=models.BooleanField(),
483
+ )
484
+
485
+ @classmethod
486
+ def annotate_last_aum(cls, qs, val_date=None, date_key="last_valuation_date"):
487
+ if val_date:
488
+ fx_rate_subquery = CurrencyFXRates.get_fx_rates_subquery(
489
+ val_date, currency="currency", lookup_expr="exact"
490
+ )
491
+ else:
492
+ fx_rate_subquery = CurrencyFXRates.get_fx_rates_subquery(
493
+ date_key, currency="currency", lookup_expr="exact"
494
+ )
495
+
496
+ qs = InstrumentPrice.annotate_sum_shares(qs, val_date, date_key=date_key).annotate(
497
+ net_value=Subquery(
498
+ InstrumentPrice.objects.filter(
499
+ calculated=False, instrument=OuterRef("pk"), date=OuterRef("last_valuation_date")
500
+ ).values("net_value")[:1]
501
+ ),
502
+ assets_under_management=Coalesce(F("sum_shares") * F("net_value"), Decimal(0)),
503
+ fx_rate=Coalesce(
504
+ fx_rate_subquery,
505
+ Decimal(1.0),
506
+ ),
507
+ assets_under_management_usd=ExpressionWrapper(
508
+ F("assets_under_management") * F("fx_rate"),
509
+ output_field=DecimalField(),
510
+ ),
511
+ is_white_label=Case(
512
+ When(white_label_customers__isnull=True, then=Value(False)),
513
+ default=Value(True),
514
+ output_field=BooleanField(),
515
+ ),
516
+ )
517
+ return qs.distinct()
518
+
519
+
520
+ @shared_task(queue="portfolio")
521
+ def update_outstanding_shares_as_task(instrument_id: int, clear: bool = False):
522
+ instrument = PMSInstrument.objects.get(id=instrument_id)
523
+ instrument.update_outstanding_shares(clear=clear)
524
+
525
+
526
+ @receiver(pre_merge, sender="wbcrm.Account")
527
+ def handle_pre_merge_account_for_product(sender: models.Model, merged_object: Account, main_object: Account, **kwargs):
528
+ """
529
+ Simply reassign the product's default account linked to the merged account to the main account
530
+ """
531
+ Product.objects.filter(default_account=merged_object).update(default_account=main_object)
532
+
533
+
534
+ @receiver(pre_save, sender="wbfdm.Instrument")
535
+ def pre_save_instrument(sender, instance, raw, **kwargs):
536
+ if not raw and instance.id:
537
+ # Remove duplicates if existings
538
+ instance.old_isins = list(set(instance.old_isins))
539
+ pre_instance = sender.objects.get(id=instance.id)
540
+ if (
541
+ pre_instance.isin
542
+ and instance.isin
543
+ and pre_instance.isin != instance.isin
544
+ and pre_instance.isin not in instance.old_isins
545
+ ):
546
+ instance.old_isins = [*instance.old_isins, pre_instance.isin]
547
+ if pre_instance.currency != instance.currency:
548
+ # currency has changed we need to recompute currency_fx_rate
549
+ instance.prices.annotate(
550
+ new_currency_fx_rate_to_usd_id=models.Subquery(
551
+ CurrencyFXRates.objects.filter(currency=instance.currency, date=models.OuterRef("date")).values(
552
+ "id"
553
+ )[:1]
554
+ )
555
+ ).update(currency_fx_rate_to_usd=models.F("new_currency_fx_rate_to_usd_id"))
556
+ instance.assets.annotate(
557
+ new_currency_fx_rate_instrument_to_usd_id=models.Subquery(
558
+ CurrencyFXRates.objects.filter(currency=instance.currency, date=models.OuterRef("date")).values(
559
+ "id"
560
+ )[:1]
561
+ )
562
+ ).update(currency_fx_rate_instrument_to_usd=models.F("new_currency_fx_rate_instrument_to_usd_id"))
563
+
564
+
565
+ @receiver(add_llm_prompt, sender="wbcrm.Account")
566
+ def add_performance_to_account_heat(sender, instance, key, **kwargs):
567
+ if key == "analyze_relationship":
568
+ return get_holding_prompt(instance)
569
+ return []
@@ -0,0 +1,2 @@
1
+ from .account_reconciliations import AccountReconciliation
2
+ from .account_reconciliation_lines import AccountReconciliationLine