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,195 @@
1
+ from datetime import date
2
+
3
+ from django.db.models import F, OuterRef, Subquery, Sum
4
+ from django.utils.functional import cached_property
5
+ from wbcore import viewsets
6
+ from wbcore.contrib.currency.models import CurrencyFXRates
7
+ from wbcore.contrib.directory.models import EmployerEmployeeRelationship
8
+ from wbcore.contrib.directory.serializers import NewPersonModelSerializer
9
+ from wbcore.contrib.directory.serializers import (
10
+ PersonModelSerializer as BasePersonModelSerializer,
11
+ )
12
+ from wbcore.contrib.directory.viewsets.entries import (
13
+ CompanyModelViewSet as OriginalCompanyModelViewSet,
14
+ )
15
+ from wbcore.contrib.directory.viewsets.entries import (
16
+ PersonModelViewSet as OriginalPersonModelViewSet,
17
+ )
18
+ from wbcore.utils.strings import format_number
19
+
20
+ from .configs import (
21
+ AssetAllocationDisplay,
22
+ AssetAllocationModelEndpointConfig,
23
+ CompanyModelDisplay,
24
+ CompanyPreviewConfig,
25
+ GeographicFocusDisplay,
26
+ GeographicFocusModelEndpointConfig,
27
+ PersonModelDisplay,
28
+ )
29
+ from .filters import CompanyFilter, PersonFilter
30
+ from .models import AssetAllocation, AssetAllocationType, GeographicFocus
31
+ from .serializers import (
32
+ AssetAllocationModelSerializer,
33
+ AssetAllocationTypeModelSerializer,
34
+ AssetAllocationTypeRepresentationSerializer,
35
+ CompanyModelListSerializer,
36
+ CompanyModelSerializer,
37
+ GeographicFocusModelSerializer,
38
+ PersonModelListSerializer,
39
+ PersonModelSerializer,
40
+ )
41
+
42
+
43
+ class CompanyModelViewSet(OriginalCompanyModelViewSet):
44
+ LIST_DOCUMENTATION = "wbportfolio/markdown/documentation/company.md"
45
+ display_config_class = CompanyModelDisplay
46
+ preview_config_class = CompanyPreviewConfig
47
+ filterset_class = CompanyFilter
48
+ ordering_fields = OriginalCompanyModelViewSet.ordering_fields + (
49
+ "invested_assets_under_management_usd__nulls_last",
50
+ "potential__nulls_last",
51
+ "asset_under_management__nulls_last",
52
+ )
53
+
54
+ @cached_property
55
+ def fx_rate_date(self) -> date:
56
+ try:
57
+ return CurrencyFXRates.objects.latest("date").date
58
+ except CurrencyFXRates.DoesNotExist:
59
+ return date.today()
60
+
61
+ def get_serializer_class(self):
62
+ if self.get_action() in ["list", "list-metadata"]:
63
+ return CompanyModelListSerializer
64
+ return CompanyModelSerializer
65
+
66
+ def get_aggregates(self, queryset, **kwargs):
67
+ sum_potential = queryset.aggregate(sum_potential=Sum("potential")).get("sum_potential", 0.0)
68
+ sum_assets_under_management_usd = queryset.aggregate(
69
+ sum_assets_under_management_usd=Sum("assets_under_management_usd")
70
+ ).get("sum_assets_under_management_usd", 0.0)
71
+ sum_invested_assets_under_management_usd = queryset.aggregate(
72
+ sum_invested_assets_under_management_usd=Sum("invested_assets_under_management_usd")
73
+ ).get("sum_invested_assets_under_management_usd", 0.0)
74
+ return {
75
+ "potential": {"Σ": format_number(sum_potential)},
76
+ "asset_under_management": {"Σ": format_number(sum_assets_under_management_usd)},
77
+ "invested_assets_under_management_usd": {"Σ": format_number(sum_invested_assets_under_management_usd)},
78
+ }
79
+
80
+ def get_queryset(self):
81
+ return (
82
+ super()
83
+ .get_queryset()
84
+ .annotate(
85
+ asset_under_management=F("portfolio_data__assets_under_management"),
86
+ assets_under_management_currency=F("portfolio_data__assets_under_management_currency"),
87
+ assets_under_management_currency_repr=F("portfolio_data__assets_under_management_currency__symbol"),
88
+ fx=CurrencyFXRates.get_fx_rates_subquery(
89
+ self.fx_rate_date, currency="assets_under_management_currency", lookup_expr="exact"
90
+ ),
91
+ assets_under_management_usd=F("asset_under_management") * F("fx"),
92
+ invested_assets_under_management_usd=F("portfolio_data__invested_assets_under_management_usd"),
93
+ investment_discretion=F("portfolio_data__investment_discretion"),
94
+ potential=F("portfolio_data__potential"),
95
+ potential_currency=F("portfolio_data__potential_currency"),
96
+ )
97
+ )
98
+
99
+
100
+ class PersonModelViewSet(OriginalPersonModelViewSet):
101
+ LIST_DOCUMENTATION = "wbportfolio/markdown/documentation/person.md"
102
+ display_config_class = PersonModelDisplay
103
+ filterset_class = PersonFilter
104
+ ordering_fields = OriginalPersonModelViewSet.ordering_fields + (
105
+ "invested_assets_under_management_usd__nulls_last",
106
+ "asset_under_management__nulls_last",
107
+ "potential",
108
+ )
109
+ serializer_class = PersonModelSerializer
110
+
111
+ def get_serializer_class(self) -> BasePersonModelSerializer:
112
+ if self.get_action() in ["list", "list-metadata"]:
113
+ return PersonModelListSerializer
114
+ elif "pk" not in self.kwargs:
115
+ return NewPersonModelSerializer
116
+ return super().get_serializer_class()
117
+
118
+ def get_queryset(self):
119
+ qs = super().get_queryset()
120
+ qs = qs.annotate(
121
+ asset_under_management=Subquery(
122
+ EmployerEmployeeRelationship.objects.filter(primary=True, employee__id=OuterRef("id")).values(
123
+ "employer__portfolio_data__assets_under_management"
124
+ )[:1],
125
+ ),
126
+ asset_under_management_currency_repr=Subquery(
127
+ EmployerEmployeeRelationship.objects.filter(primary=True, employee__id=OuterRef("id")).values(
128
+ "employer__portfolio_data__assets_under_management_currency__key"
129
+ )[:1],
130
+ ),
131
+ invested_assets_under_management_usd=Subquery(
132
+ EmployerEmployeeRelationship.objects.filter(primary=True, employee__id=OuterRef("id")).values(
133
+ "employer__portfolio_data__invested_assets_under_management_usd"
134
+ )[:1],
135
+ ),
136
+ potential=Subquery(
137
+ EmployerEmployeeRelationship.objects.filter(primary=True, employee__id=OuterRef("id")).values(
138
+ "employer__portfolio_data__potential"
139
+ )[:1],
140
+ ),
141
+ potential_currency=Subquery(
142
+ EmployerEmployeeRelationship.objects.filter(primary=True, employee__id=OuterRef("id")).values(
143
+ "employer__portfolio_data__potential_currency"
144
+ )[:1],
145
+ ),
146
+ assets_under_management_currency=Subquery(
147
+ EmployerEmployeeRelationship.objects.filter(primary=True, employee__id=OuterRef("id")).values(
148
+ "employer__portfolio_data__assets_under_management_currency"
149
+ )[:1],
150
+ ),
151
+ investment_discretion=EmployerEmployeeRelationship.objects.filter(
152
+ primary=True, employee__id=OuterRef("id")
153
+ ).values("employer__portfolio_data__investment_discretion")[:1],
154
+ )
155
+ return qs
156
+
157
+
158
+ class AssetAllocationTypeRepresentationViewSet(viewsets.RepresentationViewSet):
159
+ queryset = AssetAllocationType.objects.all()
160
+ serializer_class = AssetAllocationTypeRepresentationSerializer
161
+
162
+
163
+ class AssetAllocationTypeModelViewSet(viewsets.ModelViewSet):
164
+ queryset = AssetAllocationType.objects.all()
165
+ serializer_class = AssetAllocationTypeModelSerializer
166
+
167
+
168
+ class AssetAllocationModelViewSet(viewsets.ModelViewSet):
169
+ queryset = AssetAllocation.objects.all()
170
+ serializer_class = AssetAllocationModelSerializer
171
+
172
+ display_config_class = AssetAllocationDisplay
173
+ endpoint_config_class = AssetAllocationModelEndpointConfig
174
+
175
+ def get_queryset(self):
176
+ qs = super().get_queryset()
177
+ if company_id := self.kwargs.get("company_id", None):
178
+ qs = qs.filter(company_id=company_id)
179
+ return qs.select_related(
180
+ "asset_type",
181
+ "company",
182
+ )
183
+
184
+
185
+ class GeographicFocusModelViewSet(viewsets.ModelViewSet):
186
+ queryset = GeographicFocus.objects.all()
187
+ serializer_class = GeographicFocusModelSerializer
188
+
189
+ display_config_class = GeographicFocusDisplay
190
+ endpoint_config_class = GeographicFocusModelEndpointConfig
191
+
192
+ def get_queryset(self):
193
+ if company_id := self.kwargs.get("company_id", None):
194
+ return super().get_queryset().filter(company_id=company_id)
195
+ return super().get_queryset()
File without changes
File without changes
@@ -0,0 +1,92 @@
1
+ from wbfdm.models import Cash, InstrumentPrice
2
+ from wbportfolio.models import FeeProductPercentage, Fees, Product
3
+
4
+
5
+ def fees_calculation(price_id):
6
+ price = InstrumentPrice.objects.get(id=price_id)
7
+ if price.calculated:
8
+ raise ValueError("Cannot compute fees on calculated price")
9
+ currency = price.instrument.currency
10
+ underlying_instrument = Cash.objects.filter(currency=currency).first()
11
+ product = Product.objects.get(id=price.instrument.id)
12
+ portfolio = product.portfolio
13
+ previous_price = price.previous_price
14
+
15
+ if previous_price:
16
+ shares = product.total_shares(previous_price.date)
17
+ multiplicator = shares * previous_price.net_value
18
+ else:
19
+ shares = product.total_shares(price.date)
20
+ multiplicator = shares * product.share_price
21
+
22
+ product_net_management_fees = product.get_fees_percent(price.date, FeeProductPercentage.Type.MANAGEMENT)
23
+ product_net_performance_fees = product.get_fees_percent(price.date, FeeProductPercentage.Type.PERFORMANCE)
24
+ product_gross_performance_fees = product.get_fees_percent(
25
+ price.date, FeeProductPercentage.Type.PERFORMANCE, net=False
26
+ )
27
+
28
+ if previous_price and previous_price.date.weekday() == 4:
29
+ # The previous InstrumentPrice was a Friday. This means we have to calculate for the weekend
30
+ previous_total_value = shares * previous_price.net_value
31
+
32
+ sat_bank_fees = (product.bank_fees / 360) * previous_total_value
33
+ sat_management_fees = (product_net_management_fees / 360) * previous_total_value
34
+ previous_total_value = previous_total_value - sat_bank_fees - sat_management_fees
35
+
36
+ sun_bank_fees = (product.bank_fees / 360) * previous_total_value
37
+ sun_management_fees = (product_net_management_fees / 360) * previous_total_value
38
+ previous_total_value = previous_total_value - sun_bank_fees - sun_management_fees
39
+
40
+ mon_bank_fees = (product.bank_fees / 360) * previous_total_value
41
+ mon_management_fees = (product_net_management_fees / 360) * previous_total_value
42
+ bank_fees = sat_bank_fees + sun_bank_fees + mon_bank_fees
43
+ management_fees = sat_management_fees + sun_management_fees + mon_management_fees
44
+ else:
45
+ bank_fees = (product.bank_fees / 360) * multiplicator
46
+ management_fees = (product_net_management_fees / 360) * multiplicator
47
+
48
+ value = (
49
+ price.net_value or price.gross_value
50
+ ) # Flipped around, so that if we have a good net price from the bank, we rather use that one. Issue is, that if the gross price is completely off, the performance fees can be really really high.
51
+ multiplicator = max(0, value - product.get_high_water_mark(price.date)) * shares
52
+ performance_fees_net = product_net_performance_fees * multiplicator
53
+ performance_fees_gross = product_gross_performance_fees * multiplicator
54
+ base_fields = [
55
+ "total_value",
56
+ "total_value_fx_portfolio",
57
+ "total_value_gross",
58
+ "total_value_gross_fx_portfolio",
59
+ ]
60
+ yield {
61
+ "portfolio": portfolio,
62
+ "linked_product": product,
63
+ "transaction_date": price.date,
64
+ "transaction_subtype": Fees.Type.MANAGEMENT,
65
+ "underlying_instrument": underlying_instrument,
66
+ "currency": currency,
67
+ "calculated": True,
68
+ **{f: management_fees for f in base_fields},
69
+ }
70
+ yield {
71
+ "portfolio": portfolio,
72
+ "linked_product": product,
73
+ "transaction_date": price.date,
74
+ "transaction_subtype": Fees.Type.ISSUER,
75
+ "underlying_instrument": underlying_instrument,
76
+ "currency": currency,
77
+ "calculated": True,
78
+ **{f: bank_fees for f in base_fields},
79
+ }
80
+ yield {
81
+ "portfolio": portfolio,
82
+ "linked_product": product,
83
+ "transaction_date": price.date,
84
+ "transaction_subtype": Fees.Type.PERFORMANCE,
85
+ "underlying_instrument": underlying_instrument,
86
+ "currency": currency,
87
+ "calculated": True,
88
+ "total_value": performance_fees_net,
89
+ "total_value_fx_portfolio": performance_fees_net,
90
+ "total_value_gross": performance_fees_gross,
91
+ "total_value_gross_fx_portfolio": performance_fees_gross,
92
+ }
File without changes
@@ -0,0 +1,45 @@
1
+ from datetime import date
2
+ from decimal import Decimal
3
+ from typing import Any, Dict, Optional
4
+
5
+ import pandas as pd
6
+ from wbportfolio.models import Portfolio
7
+
8
+
9
+ def callback(
10
+ portfolio: Portfolio,
11
+ sync_date: date,
12
+ rebalancing_freq: Optional[str] = "B",
13
+ equally_weighted: Optional[bool] = False,
14
+ composite: Optional[bool] = False,
15
+ base_assets: Optional[Dict] = dict(),
16
+ **kwargs: Any,
17
+ ):
18
+ """Recursively calculates the position for a portfolio
19
+
20
+ Arguments:
21
+ portfolio {portfolio.Portfolio} -- The Portfolio on which the assets will be computed
22
+ sync_date {datetime.date} -- The date on which the assets will be computed
23
+
24
+ Keyword Arguments:
25
+ portfolio {portfolio.Portfolio} -- The core portfolio from which the computed position are created (default: {None})
26
+ adjusted_weighting {int} -- the adjusted weight of the current level of index (default: {1})
27
+ adjusted_currency_fx_rate {int} -- the adjusted currency exchange rate on the current level of index (default: {1})
28
+
29
+ Yields:
30
+ tuple[dict, dict] -- Two dictionaries: One with filter parameters and one with default values
31
+ """
32
+ assets = portfolio.assets.filter(date=sync_date)
33
+ if composite:
34
+ last_trade_proposals = portfolio.trade_proposals.filter(trade_date__lte=sync_date)
35
+ if last_trade_proposals.exists():
36
+ base_assets = last_trade_proposals.latest("trade_date").base_assets
37
+ if assets.exists() and assets.filter(date=sync_date).exists():
38
+ for asset in assets.all():
39
+ new_weight = asset.weighting
40
+ if pd.date_range(end=sync_date, periods=1, freq=rebalancing_freq)[0] == pd.Timestamp(sync_date):
41
+ if equally_weighted:
42
+ new_weight = Decimal(1 / assets.count())
43
+ elif base_assets and (proposed_weight := base_assets.get(asset.underlying_instrument.id, None)):
44
+ new_weight = proposed_weight
45
+ yield asset._build_dto(), asset._build_dto(new_weight)
@@ -0,0 +1,58 @@
1
+ from dynamic_preferences.preferences import Section
2
+ from dynamic_preferences.registries import global_preferences_registry
3
+ from dynamic_preferences.types import (
4
+ IntegerPreference,
5
+ LongStringPreference,
6
+ StringPreference,
7
+ )
8
+
9
+ portfolio = Section("wbportfolio")
10
+ commission = Section("wbcommission")
11
+
12
+
13
+ @global_preferences_registry.register
14
+ class TimedeltaImportInstrumentPrice(IntegerPreference):
15
+ section = portfolio
16
+ name = "timedelta_import_instrument_price"
17
+ default = 2
18
+
19
+ verbose_name = "The Timedelta for the instrument price import windows"
20
+
21
+
22
+ @global_preferences_registry.register
23
+ class DaysToRecomputeRebateFromFeesThreshold(IntegerPreference):
24
+ section = commission
25
+ name = "days_to_recompute_rebate_from_fees_threshold"
26
+ default = 90 # 1 quarter
27
+
28
+ verbose_name = "Number of Days to recompute rebate from fees"
29
+
30
+
31
+ @global_preferences_registry.register
32
+ class MonthlyNetNewMoneyTarget(IntegerPreference):
33
+ section = portfolio
34
+ name = "monthly_nnm_target"
35
+ default = int(1e6)
36
+
37
+ verbose_name = "Monthly Net New Money Target"
38
+
39
+
40
+ @global_preferences_registry.register
41
+ class AccountHoldingReconciliationNotificationTitle(StringPreference):
42
+ section = portfolio
43
+ name = "account_holding_reconciliation_notification_title"
44
+ default = "Account Holdings Reconciliation"
45
+
46
+
47
+ @global_preferences_registry.register
48
+ class AccountHoldingReconciliationNotificationBody(LongStringPreference):
49
+ section = portfolio
50
+ name = "account_holding_reconciliation_notification_body"
51
+ default = """To validate your holdings, please review the reconciliation for the account {account} on {reconciliation_date}."""
52
+
53
+
54
+ @global_preferences_registry.register
55
+ class AccountHoldingReconciliationNotificationBodyUpdate(LongStringPreference):
56
+ section = portfolio
57
+ name = "account_holding_reconciliation_notification_body_update"
58
+ default = """A reconcilation has been updated and requires your review. Please review the reconciliation for the account {account} on {reconciliation_date}."""
@@ -0,0 +1,35 @@
1
+ from .adjustments import AdjustmentFactory
2
+ from .assets import AssetPositionFactory
3
+ from .claim import (
4
+ ApprovedClaimFactory,
5
+ ClaimFactory,
6
+ NegativeClaimFactory,
7
+ PositiveClaimFactory,
8
+ )
9
+ from .custodians import CustodianFactory
10
+ from .dividends import DividendTransactionsFactory
11
+ from .fees import FeesFactory
12
+ from wbfdm.factories.instrument_prices import InstrumentPriceFactory
13
+ from .portfolios import (
14
+ InstrumentPortfolioThroughModelFactory,
15
+ ModelPortfolioFactory,
16
+ ModelPortfolioWithBaseProductFactory,
17
+ PortfolioFactory,
18
+ )
19
+ from .portfolio_swing_pricings import PortfolioSwingPricingFactory
20
+ from .portfolio_cash_targets import PortfolioCashTargetFactory
21
+ from .portfolio_cash_flow import DailyPortfolioCashFlowFactory
22
+ from .product_groups import ProductGroupFactory, ProductGroupRepresentantFactory
23
+ from .products import IndexProductFactory, ProductFactory, WhiteLabelProductFactory
24
+ from .reconciliations import AccountReconciliationFactory, AccountReconciliationLineFactory
25
+ from .roles import ManagerPortfolioRoleFactory, ProductPortfolioRoleFactory
26
+ from .synchronization import (
27
+ CrontabScheduleFactory,
28
+ PeriodicTaskFactory,
29
+ PortfolioSynchronizationFactory,
30
+ PriceComputationFactory,
31
+ SynchronizationTaskFactory,
32
+ )
33
+ from .trades import CustomerTradeFactory, TradeFactory, TradeProposalFactory
34
+ from .transactions import TransactionFactory
35
+ from .indexes import IndexFactory
@@ -0,0 +1,17 @@
1
+ import factory
2
+ from faker import Faker
3
+ from pandas.tseries.offsets import BDay
4
+ from wbportfolio.models import Adjustment
5
+
6
+ fake = Faker()
7
+
8
+
9
+ class AdjustmentFactory(factory.django.DjangoModelFactory):
10
+ date = factory.LazyAttribute(lambda o: (fake.future_date() + BDay(0)).date())
11
+ instrument = factory.SubFactory("wbfdm.factories.instruments.InstrumentFactory")
12
+ factor = factory.Faker("pydecimal", min_value=2, max_value=10)
13
+ last_handler = factory.SubFactory("wbcore.contrib.directory.factories.entries.PersonFactory")
14
+ status = Adjustment.Status.PENDING
15
+
16
+ class Meta:
17
+ model = Adjustment
@@ -0,0 +1,75 @@
1
+ import random
2
+ from datetime import date, timedelta
3
+ from decimal import Decimal
4
+
5
+ import factory
6
+ from wbcore.contrib.currency.models import CurrencyFXRates
7
+ from wbfdm.factories import InstrumentPriceFactory
8
+ from wbportfolio.models import AssetPosition
9
+
10
+
11
+ def get_weekday(o):
12
+ if o.underlying_instrument.id and o.underlying_instrument.assets.exists():
13
+ latest_position = o.underlying_instrument.assets.latest("date").date
14
+ else:
15
+ latest_position = date(2020, 1, 1)
16
+
17
+ latest_position += timedelta(days=1)
18
+ while latest_position.weekday() > 4:
19
+ latest_position += timedelta(days=1)
20
+
21
+ return latest_position
22
+
23
+
24
+ class AssetPositionFactory(factory.django.DjangoModelFactory):
25
+ class Meta:
26
+ model = AssetPosition
27
+ django_get_or_create = ("portfolio", "underlying_instrument", "date", "portfolio_created")
28
+
29
+ underlying_instrument = factory.SubFactory("wbfdm.factories.instruments.InstrumentFactory")
30
+
31
+ date = factory.LazyAttribute(lambda o: get_weekday(o))
32
+ asset_valuation_date = factory.LazyAttribute(lambda o: o.date)
33
+
34
+ initial_price = factory.Faker("pydecimal", min_value=100, max_value=120, right_digits=4)
35
+ underlying_instrument_price = factory.LazyAttribute(
36
+ lambda o: InstrumentPriceFactory.create(
37
+ instrument=o.underlying_instrument, calculated=False, date=o.date, net_value=o.initial_price
38
+ )
39
+ )
40
+
41
+ initial_shares = factory.LazyAttribute(lambda o: Decimal(random.randint(10, 10000)))
42
+ initial_currency_fx_rate = Decimal(1)
43
+ weighting = factory.LazyAttribute(lambda o: Decimal(random.random()))
44
+
45
+ portfolio = factory.SubFactory("wbportfolio.factories.portfolios.PortfolioFactory")
46
+ portfolio_created = None
47
+
48
+ currency = factory.LazyAttribute(lambda o: o.underlying_instrument.currency)
49
+ currency_fx_rate_instrument_to_usd = factory.LazyAttribute(
50
+ lambda o: CurrencyFXRates.objects.get_or_create(
51
+ currency=o.currency, date=o.date, defaults={"value": Decimal(1.0)}
52
+ )[0]
53
+ )
54
+
55
+ is_estimated = False
56
+
57
+ @classmethod
58
+ def _create(cls, model_class, *args, **kwargs):
59
+ if (asset_valuation_date := kwargs.get("asset_valuation_date", None)) and (
60
+ currency := kwargs.get("currency", None)
61
+ ):
62
+ kwargs["currency_fx_rate_instrument_to_usd"] = CurrencyFXRates.objects.get_or_create(
63
+ date=asset_valuation_date, currency=currency, defaults={"value": 1.0}
64
+ )[0]
65
+ if (asset_valuation_date := kwargs.get("asset_valuation_date", None)) and (
66
+ portfolio := kwargs.get("portfolio", None)
67
+ ):
68
+ kwargs["currency_fx_rate_portfolio_to_usd"] = CurrencyFXRates.objects.get_or_create(
69
+ date=asset_valuation_date, currency=portfolio.currency, defaults={"value": 1.0}
70
+ )[0]
71
+ return super()._create(model_class, *args, **kwargs)
72
+
73
+ # @factory.pre
74
+ # def generate_currency_rates(self, create, extracted, **kwargs):
75
+ #
@@ -0,0 +1,39 @@
1
+ import factory
2
+ from pandas.tseries.offsets import BDay
3
+ from wbfdm.factories.instrument_prices import InstrumentPriceFactory
4
+ from wbportfolio.models.transactions.claim import Claim
5
+
6
+
7
+ class ClaimFactory(factory.django.DjangoModelFactory):
8
+ status = Claim.Status.DRAFT
9
+ account = factory.SubFactory("wbcrm.factories.AccountFactory")
10
+ trade = factory.SubFactory("wbportfolio.factories.CustomerTradeFactory")
11
+ product = factory.LazyAttribute(lambda x: x.trade.product)
12
+ claimant = factory.SubFactory("wbcore.contrib.directory.factories.entries.PersonFactory")
13
+ creator = factory.SubFactory("wbcore.contrib.directory.factories.entries.PersonFactory")
14
+ date = factory.LazyAttribute(lambda x: (x.trade.transaction_date - BDay(0)).date())
15
+ bank = factory.Faker("company")
16
+ reference = factory.Faker("company")
17
+ shares = factory.LazyAttribute(lambda x: x.trade.shares)
18
+ nominal_amount = factory.LazyAttribute(lambda x: x.shares * x.product.share_price)
19
+ external_id = factory.Sequence(lambda n: f"{n:06}")
20
+
21
+ @factory.post_generation
22
+ def create_product_val(self, create, extracted, **kwargs):
23
+ if extracted is None or extracted is True:
24
+ InstrumentPriceFactory.create(instrument=self.product, date=self.date, calculated=False)
25
+
26
+ class Meta:
27
+ model = Claim
28
+
29
+
30
+ class PositiveClaimFactory(ClaimFactory):
31
+ shares = factory.Faker("pydecimal", min_value=0, max_value=1000000)
32
+
33
+
34
+ class NegativeClaimFactory(ClaimFactory):
35
+ shares = factory.Faker("pydecimal", min_value=-1000000, max_value=0)
36
+
37
+
38
+ class ApprovedClaimFactory(ClaimFactory):
39
+ status = Claim.Status.APPROVED
@@ -0,0 +1,11 @@
1
+ import factory
2
+ from wbportfolio.models import Custodian
3
+
4
+
5
+ class CustodianFactory(factory.django.DjangoModelFactory):
6
+ name = factory.Faker("company")
7
+ mapping = factory.List([factory.Faker("company") for _ in range(5)])
8
+
9
+ class Meta:
10
+ model = Custodian
11
+ django_get_or_create = ("name",)
@@ -0,0 +1,14 @@
1
+ import random
2
+
3
+ import factory
4
+ from wbportfolio.models import DividendTransaction
5
+
6
+ from .transactions import TransactionFactory
7
+
8
+
9
+ class DividendTransactionsFactory(TransactionFactory):
10
+ class Meta:
11
+ model = DividendTransaction
12
+
13
+ shares = factory.LazyAttribute(lambda o: random.randint(10, 10000))
14
+ price = factory.LazyAttribute(lambda o: random.randint(10, 10000))
@@ -0,0 +1,15 @@
1
+ import factory
2
+ from faker import Faker
3
+ from wbportfolio.models import Fees
4
+
5
+ from .transactions import TransactionFactory
6
+
7
+ faker = Faker()
8
+
9
+
10
+ class FeesFactory(TransactionFactory):
11
+ class Meta:
12
+ model = Fees
13
+
14
+ transaction_subtype = factory.Faker("random_element", elements=[x[0] for x in Fees.Type.choices])
15
+ linked_product = factory.SubFactory("wbportfolio.factories.ProductFactory")
@@ -0,0 +1,17 @@
1
+ import random
2
+
3
+ import factory
4
+ from faker import Faker
5
+ from wbfdm.factories import InstrumentFactory, InstrumentTypeFactory
6
+ from wbportfolio.models import Index
7
+
8
+ fake = Faker()
9
+
10
+
11
+ class IndexFactory(InstrumentFactory):
12
+ risk_scale = factory.LazyAttribute(lambda o: random.randint(1, 7))
13
+
14
+ instrument_type = factory.LazyAttribute(lambda o: InstrumentTypeFactory.create(name="Index", key="index"))
15
+
16
+ class Meta:
17
+ model = Index
@@ -0,0 +1,20 @@
1
+ import factory
2
+ from faker import Faker
3
+ from wbportfolio.factories.portfolios import PortfolioFactory
4
+ from wbportfolio.models import DailyPortfolioCashFlow
5
+
6
+ fake = Faker()
7
+
8
+
9
+ class DailyPortfolioCashFlowFactory(factory.django.DjangoModelFactory):
10
+ class Meta:
11
+ model = DailyPortfolioCashFlow
12
+
13
+ value_date = factory.Faker("date")
14
+ portfolio = factory.SubFactory(PortfolioFactory)
15
+
16
+ total_assets = factory.Faker("pydecimal", left_digits=7, right_digits=4, min_value=0.01)
17
+ cash = factory.Faker("pydecimal", left_digits=7, right_digits=4, min_value=0.01)
18
+ cash_flow_forecast = factory.LazyAttribute(lambda o: fake.pydecimal(min_value=0.01, max_value=o.total_assets))
19
+
20
+ pending = factory.Faker("pybool")
@@ -0,0 +1,15 @@
1
+ import factory
2
+ from wbportfolio.factories.portfolios import PortfolioFactory
3
+ from wbportfolio.models import PortfolioCashTarget
4
+
5
+
6
+ class PortfolioCashTargetFactory(factory.django.DjangoModelFactory):
7
+ class Meta:
8
+ model = PortfolioCashTarget
9
+
10
+ valid_date = factory.Faker("date")
11
+ portfolio = factory.SubFactory(PortfolioFactory)
12
+ min_target = factory.Faker("pydecimal", left_digits=0, right_digits=4, max_value=0.01)
13
+ target = factory.Faker("pydecimal", left_digits=0, right_digits=4, max_value=0.01)
14
+ max_target = factory.Faker("pydecimal", left_digits=0, right_digits=4, min_value=0.01)
15
+ comment = factory.Faker("paragraph", nb_sentences=1)