wbportfolio 1.44.4__py2.py3-none-any.whl → 1.45.0__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 (315) hide show
  1. wbportfolio/admin/__init__.py +1 -1
  2. wbportfolio/admin/asset.py +2 -1
  3. wbportfolio/admin/custodians.py +1 -0
  4. wbportfolio/admin/indexes.py +15 -0
  5. wbportfolio/admin/portfolio.py +12 -7
  6. wbportfolio/admin/portfolio_relationships.py +1 -0
  7. wbportfolio/admin/product_groups.py +2 -0
  8. wbportfolio/admin/products.py +2 -1
  9. wbportfolio/admin/reconciliations.py +1 -0
  10. wbportfolio/admin/registers.py +1 -0
  11. wbportfolio/admin/roles.py +1 -0
  12. wbportfolio/admin/transactions/__init__.py +1 -0
  13. wbportfolio/admin/transactions/claim.py +1 -0
  14. wbportfolio/admin/transactions/dividends.py +1 -0
  15. wbportfolio/admin/transactions/fees.py +1 -0
  16. wbportfolio/admin/transactions/rebalancing.py +26 -0
  17. wbportfolio/admin/transactions/trades.py +4 -3
  18. wbportfolio/admin/transactions/transactions.py +1 -0
  19. wbportfolio/analysis/claims.py +2 -1
  20. wbportfolio/contrib/company_portfolio/models.py +3 -6
  21. wbportfolio/contrib/company_portfolio/tests/conftest.py +0 -12
  22. wbportfolio/contrib/company_portfolio/tests/test_models.py +1 -0
  23. wbportfolio/defaults/fees/default.py +1 -0
  24. wbportfolio/factories/__init__.py +1 -7
  25. wbportfolio/factories/adjustments.py +1 -0
  26. wbportfolio/factories/assets.py +13 -7
  27. wbportfolio/factories/claim.py +1 -0
  28. wbportfolio/factories/custodians.py +1 -0
  29. wbportfolio/factories/dividends.py +1 -0
  30. wbportfolio/factories/fees.py +1 -0
  31. wbportfolio/factories/indexes.py +1 -0
  32. wbportfolio/factories/portfolio_cash_flow.py +1 -0
  33. wbportfolio/factories/portfolio_cash_targets.py +1 -0
  34. wbportfolio/factories/portfolio_swing_pricings.py +1 -0
  35. wbportfolio/factories/portfolios.py +3 -0
  36. wbportfolio/factories/product_groups.py +1 -0
  37. wbportfolio/factories/products.py +1 -0
  38. wbportfolio/factories/rebalancing.py +23 -0
  39. wbportfolio/factories/reconciliations.py +1 -0
  40. wbportfolio/factories/roles.py +1 -0
  41. wbportfolio/factories/trades.py +1 -0
  42. wbportfolio/factories/transactions.py +1 -0
  43. wbportfolio/fdm/tasks.py +1 -0
  44. wbportfolio/filters/__init__.py +1 -1
  45. wbportfolio/filters/assets.py +8 -9
  46. wbportfolio/filters/assets_and_net_new_money_progression.py +1 -0
  47. wbportfolio/filters/custodians.py +1 -0
  48. wbportfolio/filters/esg.py +1 -0
  49. wbportfolio/filters/performances.py +7 -6
  50. wbportfolio/filters/portfolios.py +21 -1
  51. wbportfolio/filters/positions.py +1 -0
  52. wbportfolio/filters/products.py +1 -0
  53. wbportfolio/filters/roles.py +1 -0
  54. wbportfolio/filters/signals.py +1 -0
  55. wbportfolio/filters/transactions/claim.py +1 -0
  56. wbportfolio/filters/transactions/fees.py +1 -0
  57. wbportfolio/filters/transactions/trades.py +2 -1
  58. wbportfolio/filters/transactions/transactions.py +1 -0
  59. wbportfolio/import_export/backends/ubs/mixin.py +1 -0
  60. wbportfolio/import_export/backends/wbfdm/adjustment.py +1 -0
  61. wbportfolio/import_export/handlers/asset_position.py +11 -13
  62. wbportfolio/import_export/handlers/fees.py +1 -0
  63. wbportfolio/import_export/handlers/portfolio_cash_flow.py +1 -0
  64. wbportfolio/import_export/handlers/trade.py +1 -0
  65. wbportfolio/import_export/parsers/jpmorgan/customer_trade.py +1 -0
  66. wbportfolio/import_export/parsers/jpmorgan/fees.py +1 -0
  67. wbportfolio/import_export/parsers/jpmorgan/strategy.py +5 -4
  68. wbportfolio/import_export/parsers/jpmorgan/valuation.py +1 -0
  69. wbportfolio/import_export/parsers/leonteq/customer_trade.py +1 -0
  70. wbportfolio/import_export/parsers/leonteq/equity.py +13 -12
  71. wbportfolio/import_export/parsers/leonteq/fees.py +1 -0
  72. wbportfolio/import_export/parsers/leonteq/trade.py +1 -0
  73. wbportfolio/import_export/parsers/leonteq/valuation.py +1 -0
  74. wbportfolio/import_export/parsers/natixis/customer_trade.py +1 -0
  75. wbportfolio/import_export/parsers/natixis/d1_customer_trade.py +1 -0
  76. wbportfolio/import_export/parsers/natixis/d1_equity.py +3 -2
  77. wbportfolio/import_export/parsers/natixis/d1_fees.py +1 -0
  78. wbportfolio/import_export/parsers/natixis/d1_trade.py +1 -0
  79. wbportfolio/import_export/parsers/natixis/d1_valuation.py +1 -0
  80. wbportfolio/import_export/parsers/natixis/equity.py +5 -5
  81. wbportfolio/import_export/parsers/natixis/trade.py +1 -0
  82. wbportfolio/import_export/parsers/natixis/utils.py +8 -7
  83. wbportfolio/import_export/parsers/sg_lux/custodian_positions.py +1 -0
  84. wbportfolio/import_export/parsers/sg_lux/customer_trade.py +1 -0
  85. wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py +2 -1
  86. wbportfolio/import_export/parsers/sg_lux/customer_trade_slk.py +2 -1
  87. wbportfolio/import_export/parsers/sg_lux/customer_trade_without_pw.py +1 -0
  88. wbportfolio/import_export/parsers/sg_lux/equity.py +7 -8
  89. wbportfolio/import_export/parsers/sg_lux/portfolio_cash_flow.py +1 -0
  90. wbportfolio/import_export/parsers/sg_lux/portfolio_future_cash_flow.py +1 -0
  91. wbportfolio/import_export/parsers/sg_lux/registers.py +2 -1
  92. wbportfolio/import_export/parsers/societe_generale/customer_trade.py +1 -0
  93. wbportfolio/import_export/parsers/societe_generale/strategy.py +8 -9
  94. wbportfolio/import_export/parsers/societe_generale/valuation.py +1 -0
  95. wbportfolio/import_export/parsers/tellco/equity.py +5 -4
  96. wbportfolio/import_export/parsers/ubs/api/asset_position.py +15 -14
  97. wbportfolio/import_export/parsers/ubs/api/fees.py +1 -0
  98. wbportfolio/import_export/parsers/ubs/customer_trade.py +1 -0
  99. wbportfolio/import_export/parsers/ubs/equity.py +3 -2
  100. wbportfolio/import_export/parsers/ubs/historical_customer_trade.py +1 -0
  101. wbportfolio/import_export/parsers/ubs/valuation.py +1 -0
  102. wbportfolio/import_export/parsers/vontobel/asset_position.py +19 -19
  103. wbportfolio/import_export/parsers/vontobel/customer_trade.py +1 -0
  104. wbportfolio/import_export/parsers/vontobel/historical_customer_trade.py +1 -0
  105. wbportfolio/import_export/parsers/vontobel/management_fees.py +1 -0
  106. wbportfolio/import_export/parsers/vontobel/performance_fees.py +1 -0
  107. wbportfolio/import_export/parsers/vontobel/trade.py +1 -0
  108. wbportfolio/import_export/parsers/vontobel/valuation_api.py +20 -0
  109. wbportfolio/import_export/resources/assets.py +4 -3
  110. wbportfolio/import_export/resources/trades.py +1 -0
  111. wbportfolio/metric/backends/base.py +1 -0
  112. wbportfolio/metric/backends/portfolio_base.py +1 -0
  113. wbportfolio/metric/backends/portfolio_esg.py +1 -0
  114. wbportfolio/metric/tests/test_portfolio_base.py +1 -0
  115. wbportfolio/migrations/0052_remove_cash_instrument_ptr_and_more.py +1 -131
  116. wbportfolio/migrations/0067_assetposition_unique_asset_position.py +1 -1
  117. wbportfolio/migrations/0070_remove_assetposition_unique_asset_position_and_more.py +1 -1
  118. wbportfolio/migrations/0073_remove_product_price_computation_and_more.py +407 -0
  119. wbportfolio/models/__init__.py +0 -5
  120. wbportfolio/models/adjustments.py +8 -2
  121. wbportfolio/models/asset.py +117 -98
  122. wbportfolio/models/graphs/portfolio.py +144 -0
  123. wbportfolio/models/graphs/utils.py +83 -0
  124. wbportfolio/models/indexes.py +2 -13
  125. wbportfolio/models/mixins/instruments.py +30 -8
  126. wbportfolio/models/portfolio.py +538 -332
  127. wbportfolio/models/portfolio_cash_flow.py +1 -0
  128. wbportfolio/models/portfolio_relationship.py +6 -2
  129. wbportfolio/models/product_groups.py +3 -2
  130. wbportfolio/models/products.py +3 -17
  131. wbportfolio/models/reconciliations/account_reconciliation_lines.py +1 -0
  132. wbportfolio/models/reconciliations/account_reconciliations.py +1 -0
  133. wbportfolio/models/registers.py +1 -0
  134. wbportfolio/models/transactions/__init__.py +1 -0
  135. wbportfolio/models/transactions/claim.py +8 -8
  136. wbportfolio/models/transactions/dividends.py +1 -0
  137. wbportfolio/models/transactions/fees.py +1 -0
  138. wbportfolio/models/transactions/rebalancing.py +153 -0
  139. wbportfolio/models/transactions/trade_proposals.py +153 -155
  140. wbportfolio/models/transactions/trades.py +48 -40
  141. wbportfolio/models/transactions/transactions.py +6 -12
  142. wbportfolio/models/utils.py +1 -0
  143. wbportfolio/pms/analytics/__init__.py +0 -0
  144. wbportfolio/pms/analytics/portfolio.py +28 -0
  145. wbportfolio/pms/trading/handler.py +13 -16
  146. wbportfolio/pms/typing.py +13 -29
  147. wbportfolio/rebalancing/__init__.py +0 -0
  148. wbportfolio/rebalancing/base.py +16 -0
  149. wbportfolio/rebalancing/decorators.py +17 -0
  150. wbportfolio/rebalancing/models/__init__.py +3 -0
  151. wbportfolio/rebalancing/models/composite.py +31 -0
  152. wbportfolio/rebalancing/models/equally_weighted.py +21 -0
  153. wbportfolio/rebalancing/models/model_portfolio.py +35 -0
  154. wbportfolio/reports/monthly_position_report.py +1 -1
  155. wbportfolio/risk_management/backends/accounts.py +7 -6
  156. wbportfolio/risk_management/backends/controversy_portfolio.py +1 -0
  157. wbportfolio/risk_management/backends/exposure_portfolio.py +1 -0
  158. wbportfolio/risk_management/backends/instrument_list_portfolio.py +1 -0
  159. wbportfolio/risk_management/backends/liquidity_risk.py +1 -0
  160. wbportfolio/risk_management/backends/liquidity_stress_instrument.py +1 -0
  161. wbportfolio/risk_management/backends/mixins.py +1 -0
  162. wbportfolio/risk_management/backends/product_integrity.py +6 -1
  163. wbportfolio/risk_management/backends/stop_loss_instrument.py +1 -0
  164. wbportfolio/risk_management/backends/stop_loss_portfolio.py +1 -0
  165. wbportfolio/risk_management/backends/ucits_portfolio.py +1 -0
  166. wbportfolio/risk_management/tests/test_accounts.py +1 -0
  167. wbportfolio/risk_management/tests/test_controversy_portfolio.py +1 -0
  168. wbportfolio/risk_management/tests/test_exposure_portfolio.py +1 -0
  169. wbportfolio/risk_management/tests/test_instrument_list_portfolio.py +1 -0
  170. wbportfolio/risk_management/tests/test_liquidity_risk.py +1 -0
  171. wbportfolio/risk_management/tests/test_product_integrity.py +1 -0
  172. wbportfolio/risk_management/tests/test_stop_loss_instrument.py +1 -0
  173. wbportfolio/risk_management/tests/test_stop_loss_portfolio.py +1 -0
  174. wbportfolio/risk_management/tests/test_ucits_portfolio.py +1 -0
  175. wbportfolio/serializers/__init__.py +5 -5
  176. wbportfolio/serializers/adjustments.py +1 -0
  177. wbportfolio/serializers/assets.py +18 -19
  178. wbportfolio/serializers/custodians.py +1 -0
  179. wbportfolio/serializers/portfolio_cash_flow.py +1 -0
  180. wbportfolio/serializers/portfolio_cash_targets.py +1 -0
  181. wbportfolio/serializers/portfolio_relationship.py +1 -0
  182. wbportfolio/serializers/portfolio_swing_pricing.py +1 -0
  183. wbportfolio/serializers/portfolios.py +61 -40
  184. wbportfolio/serializers/positions.py +1 -0
  185. wbportfolio/serializers/product_group.py +1 -0
  186. wbportfolio/serializers/products.py +4 -7
  187. wbportfolio/serializers/rebalancing.py +57 -0
  188. wbportfolio/serializers/reconciliations.py +2 -1
  189. wbportfolio/serializers/registers.py +1 -0
  190. wbportfolio/serializers/roles.py +1 -0
  191. wbportfolio/serializers/signals.py +10 -15
  192. wbportfolio/serializers/transactions/__init__.py +1 -1
  193. wbportfolio/serializers/transactions/claim.py +1 -0
  194. wbportfolio/serializers/transactions/fees.py +1 -0
  195. wbportfolio/serializers/transactions/trade_proposals.py +85 -0
  196. wbportfolio/serializers/transactions/trades.py +9 -51
  197. wbportfolio/serializers/transactions/transactions.py +4 -3
  198. wbportfolio/tasks.py +1 -78
  199. wbportfolio/tests/conftest.py +6 -13
  200. wbportfolio/tests/models/test_account_reconciliation.py +2 -0
  201. wbportfolio/tests/models/test_assets.py +27 -19
  202. wbportfolio/tests/models/test_customer_trades.py +1 -0
  203. wbportfolio/tests/models/test_imports.py +5 -1
  204. wbportfolio/tests/models/test_merge.py +5 -4
  205. wbportfolio/tests/models/test_portfolio_cash_flow.py +8 -6
  206. wbportfolio/tests/models/test_portfolios.py +594 -154
  207. wbportfolio/tests/models/test_product_groups.py +1 -0
  208. wbportfolio/tests/models/test_products.py +6 -3
  209. wbportfolio/tests/models/test_roles.py +1 -0
  210. wbportfolio/tests/models/test_splits.py +1 -0
  211. wbportfolio/tests/models/transactions/test_claim.py +1 -0
  212. wbportfolio/tests/models/transactions/test_fees.py +1 -0
  213. wbportfolio/tests/models/transactions/test_rebalancing.py +81 -0
  214. wbportfolio/tests/models/transactions/test_trades.py +1 -0
  215. wbportfolio/tests/models/utils.py +1 -0
  216. wbportfolio/tests/pms/__init__.py +0 -0
  217. wbportfolio/tests/pms/test_analytics.py +35 -0
  218. wbportfolio/tests/rebalancing/__init__.py +0 -0
  219. wbportfolio/tests/rebalancing/test_models.py +127 -0
  220. wbportfolio/tests/serializers/test_claims.py +1 -0
  221. wbportfolio/tests/signals.py +1 -7
  222. wbportfolio/tests/tests.py +2 -0
  223. wbportfolio/tests/viewsets/test_assets.py +1 -0
  224. wbportfolio/tests/viewsets/test_performances.py +1 -0
  225. wbportfolio/tests/viewsets/test_products.py +1 -0
  226. wbportfolio/tests/viewsets/transactions/test_claims.py +1 -0
  227. wbportfolio/urls.py +26 -12
  228. wbportfolio/viewsets/__init__.py +2 -5
  229. wbportfolio/viewsets/adjustments.py +1 -0
  230. wbportfolio/viewsets/assets.py +62 -51
  231. wbportfolio/viewsets/assets_and_net_new_money_progression.py +1 -0
  232. wbportfolio/viewsets/charts/assets.py +3 -1
  233. wbportfolio/viewsets/configs/buttons/__init__.py +1 -1
  234. wbportfolio/viewsets/configs/buttons/assets.py +1 -0
  235. wbportfolio/viewsets/configs/buttons/custodians.py +1 -0
  236. wbportfolio/viewsets/configs/buttons/mixins.py +1 -20
  237. wbportfolio/viewsets/configs/buttons/portfolios.py +90 -76
  238. wbportfolio/viewsets/configs/buttons/signals.py +1 -0
  239. wbportfolio/viewsets/configs/buttons/trades.py +1 -0
  240. wbportfolio/viewsets/configs/display/__init__.py +2 -1
  241. wbportfolio/viewsets/configs/display/adjustments.py +1 -0
  242. wbportfolio/viewsets/configs/display/assets.py +7 -6
  243. wbportfolio/viewsets/configs/display/claim.py +1 -0
  244. wbportfolio/viewsets/configs/display/portfolios.py +127 -79
  245. wbportfolio/viewsets/configs/display/product_performance.py +1 -0
  246. wbportfolio/viewsets/configs/display/rebalancing.py +27 -0
  247. wbportfolio/viewsets/configs/display/trade_proposals.py +7 -4
  248. wbportfolio/viewsets/configs/display/trades.py +75 -42
  249. wbportfolio/viewsets/configs/endpoints/__init__.py +3 -1
  250. wbportfolio/viewsets/configs/endpoints/claim.py +1 -0
  251. wbportfolio/viewsets/configs/endpoints/portfolios.py +23 -7
  252. wbportfolio/viewsets/configs/endpoints/rebalancing.py +6 -0
  253. wbportfolio/viewsets/configs/endpoints/reconciliations.py +1 -0
  254. wbportfolio/viewsets/configs/endpoints/trade_proposals.py +1 -0
  255. wbportfolio/viewsets/configs/endpoints/trades.py +1 -0
  256. wbportfolio/viewsets/configs/menu/adjustments.py +1 -0
  257. wbportfolio/viewsets/configs/menu/assets.py +1 -0
  258. wbportfolio/viewsets/configs/menu/fees.py +1 -0
  259. wbportfolio/viewsets/configs/menu/portfolio_cash_flow.py +1 -0
  260. wbportfolio/viewsets/configs/menu/portfolios.py +4 -2
  261. wbportfolio/viewsets/configs/menu/positions.py +1 -0
  262. wbportfolio/viewsets/configs/menu/roles.py +1 -0
  263. wbportfolio/viewsets/configs/menu/transactions.py +1 -0
  264. wbportfolio/viewsets/configs/previews/portfolios.py +1 -6
  265. wbportfolio/viewsets/configs/titles/__init__.py +1 -1
  266. wbportfolio/viewsets/configs/titles/assets.py +1 -0
  267. wbportfolio/viewsets/configs/titles/fees.py +1 -0
  268. wbportfolio/viewsets/configs/titles/instrument_prices.py +1 -0
  269. wbportfolio/viewsets/configs/titles/portfolios.py +13 -11
  270. wbportfolio/viewsets/configs/titles/roles.py +1 -0
  271. wbportfolio/viewsets/configs/titles/trades.py +1 -0
  272. wbportfolio/viewsets/configs/titles/transactions.py +1 -0
  273. wbportfolio/viewsets/custodians.py +1 -0
  274. wbportfolio/viewsets/esg.py +1 -0
  275. wbportfolio/viewsets/mixins.py +1 -0
  276. wbportfolio/viewsets/portfolio_cash_flow.py +1 -0
  277. wbportfolio/viewsets/portfolio_cash_targets.py +1 -0
  278. wbportfolio/viewsets/portfolio_relationship.py +1 -0
  279. wbportfolio/viewsets/portfolio_swing_pricing.py +1 -0
  280. wbportfolio/viewsets/portfolios.py +228 -61
  281. wbportfolio/viewsets/positions.py +3 -2
  282. wbportfolio/viewsets/product_groups.py +1 -0
  283. wbportfolio/viewsets/product_performance.py +1 -0
  284. wbportfolio/viewsets/products.py +1 -0
  285. wbportfolio/viewsets/reconciliations.py +1 -0
  286. wbportfolio/viewsets/registers.py +1 -0
  287. wbportfolio/viewsets/roles.py +1 -0
  288. wbportfolio/viewsets/signals.py +1 -0
  289. wbportfolio/viewsets/transactions/__init__.py +1 -0
  290. wbportfolio/viewsets/transactions/claim.py +2 -1
  291. wbportfolio/viewsets/transactions/fees.py +1 -0
  292. wbportfolio/viewsets/transactions/mixins.py +1 -0
  293. wbportfolio/viewsets/transactions/rebalancing.py +31 -0
  294. wbportfolio/viewsets/transactions/trade_proposals.py +25 -5
  295. wbportfolio/viewsets/transactions/trades.py +16 -9
  296. wbportfolio/viewsets/transactions/transactions.py +1 -0
  297. {wbportfolio-1.44.4.dist-info → wbportfolio-1.45.0.dist-info}/METADATA +4 -1
  298. {wbportfolio-1.44.4.dist-info → wbportfolio-1.45.0.dist-info}/RECORD +301 -288
  299. wbportfolio/admin/synchronization/__init__.py +0 -2
  300. wbportfolio/admin/synchronization/admin.py +0 -114
  301. wbportfolio/admin/synchronization/portfolio_synchronization.py +0 -18
  302. wbportfolio/admin/synchronization/price_computation.py +0 -21
  303. wbportfolio/defaults/portfolio/default_rebalancing.py +0 -45
  304. wbportfolio/factories/pytest_utils.py +0 -121
  305. wbportfolio/factories/synchronization.py +0 -40
  306. wbportfolio/models/synchronization/__init__.py +0 -3
  307. wbportfolio/models/synchronization/portfolio_synchronization.py +0 -292
  308. wbportfolio/models/synchronization/price_computation.py +0 -200
  309. wbportfolio/models/synchronization/synchronization.py +0 -188
  310. wbportfolio/serializers/synchronization.py +0 -18
  311. wbportfolio/tests/models/test_synchronization.py +0 -617
  312. wbportfolio/viewsets/synchronization.py +0 -25
  313. /wbportfolio/{defaults/portfolio → models/graphs}/__init__.py +0 -0
  314. {wbportfolio-1.44.4.dist-info → wbportfolio-1.45.0.dist-info}/WHEEL +0 -0
  315. {wbportfolio-1.44.4.dist-info → wbportfolio-1.45.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,6 +5,7 @@ from django.db import models
5
5
  from wbcore.contrib.io.mixins import ImportMixin
6
6
  from wbcore.contrib.notifications.utils import create_notification_type
7
7
  from wbcore.models import WBModel
8
+
8
9
  from wbportfolio.import_export.handlers.portfolio_cash_flow import (
9
10
  DailyPortfolioCashFlowImportHandler,
10
11
  )
@@ -56,7 +56,6 @@ class InstrumentPortfolioThroughModel(models.Model):
56
56
  )
57
57
 
58
58
  class Meta:
59
- unique_together = ("instrument", "portfolio")
60
59
  constraints = [
61
60
  models.UniqueConstraint(fields=["instrument"], name="unique_instrument"),
62
61
  models.UniqueConstraint(fields=["instrument", "portfolio"], name="unique_portfolio_relationship"),
@@ -114,7 +113,12 @@ class PortfolioInstrumentPreferredClassificationThroughModel(models.Model):
114
113
  class Meta:
115
114
  verbose_name = "Portfolio Prefered Classification Per Instrument"
116
115
  verbose_name_plural = "Portfolio Prefered Classification Per Instruments"
117
- unique_together = ("portfolio", "instrument", "classification_group")
116
+ constraints = [
117
+ models.UniqueConstraint(
118
+ fields=["portfolio", "instrument", "classification_group"],
119
+ name="unique_prefered_classification_relationship",
120
+ ),
121
+ ]
118
122
 
119
123
 
120
124
  @receiver(pre_merge, sender="wbfdm.Instrument")
@@ -6,12 +6,13 @@ from django.db.models import F, Q, Sum
6
6
  from wbcore.models import WBModel
7
7
  from wbfdm.models import InstrumentType
8
8
  from wbfdm.models.instruments.instrument_prices import InstrumentPrice
9
+
9
10
  from wbportfolio.models.products import FeeProductPercentage, Product
10
11
 
11
- from .mixins.instruments import PMSInstrument
12
+ from .mixins.instruments import PMSInstrumentAbstractModel
12
13
 
13
14
 
14
- class ProductGroup(PMSInstrument):
15
+ class ProductGroup(PMSInstrumentAbstractModel):
15
16
  class ProductGroupType(models.TextChoices):
16
17
  FUND = "Fund"
17
18
 
@@ -6,7 +6,6 @@ from celery import shared_task
6
6
  from django.contrib import admin
7
7
  from django.contrib.postgres.constraints import ExclusionConstraint
8
8
  from django.contrib.postgres.fields import DateRangeField, RangeOperators
9
- from django.core.validators import MaxValueValidator, MinValueValidator
10
9
  from django.db import models
11
10
  from django.db.models import (
12
11
  BooleanField,
@@ -31,10 +30,11 @@ from wbcore.utils.enum import ChoiceEnum
31
30
  from wbcrm.models.accounts import Account
32
31
  from wbfdm.models.instruments.instrument_prices import InstrumentPrice
33
32
  from wbfdm.models.instruments.instruments import InstrumentManager, InstrumentType
33
+
34
34
  from wbportfolio.models.llm.wbcrm.analyze_relationship import get_holding_prompt
35
35
  from wbportfolio.models.portfolio_relationship import InstrumentPortfolioThroughModel
36
36
 
37
- from .mixins.instruments import PMSInstrument
37
+ from .mixins.instruments import PMSInstrument, PMSInstrumentAbstractModel
38
38
 
39
39
 
40
40
  class TypeOfReturn(ChoiceEnum):
@@ -192,16 +192,7 @@ class FeeProductPercentage(models.Model):
192
192
  return self.percent + self.vat_deduction
193
193
 
194
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
-
195
+ class Product(PMSInstrumentAbstractModel):
205
196
  share_price = models.PositiveIntegerField(
206
197
  default=100,
207
198
  verbose_name="Share Price",
@@ -285,11 +276,6 @@ class Product(PMSInstrument):
285
276
  choices=Liquidy.choices(),
286
277
  verbose_name="Liquidity",
287
278
  )
288
- risk_scale = models.IntegerField(
289
- validators=[MinValueValidator(1), MaxValueValidator(7)],
290
- default=4,
291
- verbose_name="Risk Scale",
292
- )
293
279
 
294
280
  jurisdiction = models.ForeignKey(
295
281
  "geography.Geography",
@@ -8,6 +8,7 @@ from django.utils.translation import gettext_lazy as _
8
8
  from wbcore.models import WBModel
9
9
  from wbcrm.models import Account
10
10
  from wbfdm.models.instruments import InstrumentPrice
11
+
11
12
  from wbportfolio.models.transactions.claim import Claim
12
13
 
13
14
  if TYPE_CHECKING:
@@ -9,6 +9,7 @@ from dynamic_preferences.registries import global_preferences_registry
9
9
  from wbcore.contrib.authentication.models import User
10
10
  from wbcore.contrib.notifications.dispatch import send_notification
11
11
  from wbcore.contrib.notifications.utils import create_notification_type
12
+
12
13
  from wbportfolio.models.reconciliations.account_reconciliation_lines import (
13
14
  AccountReconciliationLine,
14
15
  )
@@ -1,6 +1,7 @@
1
1
  from django.db import models
2
2
  from wbcore.contrib.io.mixins import ImportMixin
3
3
  from wbcore.models import WBModel
4
+
4
5
  from wbportfolio.import_export.handlers.register import RegisterImportHandler
5
6
 
6
7
 
@@ -3,5 +3,6 @@ from .dividends import DividendTransaction
3
3
  from .expiry import Expiry
4
4
  from .fees import FeeCalculation, Fees
5
5
  from .trade_proposals import TradeProposal
6
+ from .rebalancing import RebalancingModel, Rebalancer
6
7
  from .trades import Trade
7
8
  from .transactions import Transaction
@@ -1,6 +1,5 @@
1
- from datetime import date
1
+ from datetime import date, timedelta
2
2
  from datetime import date as date_lib
3
- from datetime import timedelta
4
3
  from decimal import Decimal
5
4
 
6
5
  from django.db import models
@@ -34,6 +33,7 @@ from wbcore.utils.strings import ReferenceIDMixin
34
33
  from wbcrm.models import AccountRole
35
34
  from wbcrm.models.accounts import Account
36
35
  from wbfdm.models.instruments.instrument_prices import InstrumentPrice
36
+
37
37
  from wbportfolio.models.llm.wbcrm.analyze_relationship import get_performances_prompt
38
38
 
39
39
  from ..custodians import Custodian
@@ -350,13 +350,13 @@ class Claim(ReferenceIDMixin, WBModel):
350
350
  and not product.valuations.filter(date=claim_date).exists()
351
351
  ):
352
352
  if (prices_qs := product.valuations.filter(date__lt=claim_date)).exists():
353
- errors[
354
- "date"
355
- ] = f"For product {product.name}, the latest valid valuation date before {claim_date:%Y-%m-%d} is {prices_qs.latest('date').date:%Y-%m-%d}: Please select a valid date."
353
+ errors["date"] = (
354
+ f"For product {product.name}, the latest valid valuation date before {claim_date:%Y-%m-%d} is {prices_qs.latest('date').date:%Y-%m-%d}: Please select a valid date."
355
+ )
356
356
  else:
357
- errors[
358
- "date"
359
- ] = f"There is no valuation before {claim_date:%Y-%m-%d} for product {product.name}: Please select a valid date."
357
+ errors["date"] = (
358
+ f"There is no valuation before {claim_date:%Y-%m-%d} for product {product.name}: Please select a valid date."
359
+ )
360
360
  return errors
361
361
 
362
362
  @transition(
@@ -1,4 +1,5 @@
1
1
  from django.db import models
2
+
2
3
  from wbportfolio.import_export.handlers.dividend import DividendImportHandler
3
4
 
4
5
  from .transactions import ShareMixin, Transaction
@@ -6,6 +6,7 @@ from django.db import models
6
6
  from django.db.models import Exists, OuterRef, Q, QuerySet
7
7
  from django.dispatch import receiver
8
8
  from wbfdm.models.instruments.instrument_prices import InstrumentPrice
9
+
9
10
  from wbportfolio.import_export.handlers.fees import FeesImportHandler
10
11
  from wbportfolio.models.products import Product
11
12
 
@@ -0,0 +1,153 @@
1
+ from datetime import date
2
+
3
+ from dateutil import rrule
4
+ from django.core.exceptions import ObjectDoesNotExist, ValidationError
5
+ from django.db import models
6
+ from django.db.models.signals import post_migrate
7
+ from django.dispatch import receiver
8
+ from django.utils.functional import cached_property
9
+ from django.utils.module_loading import autodiscover_modules
10
+ from django.utils.translation import gettext_lazy as _
11
+ from wbcore.utils.importlib import import_from_dotted_path
12
+ from wbcore.utils.models import ComplexToStringMixin
13
+ from wbcore.utils.rrules import convert_rrulestr_to_dict, humanize_rrule
14
+
15
+ from wbportfolio.models.portfolio import Portfolio
16
+ from wbportfolio.models.transactions.trade_proposals import TradeProposal
17
+ from wbportfolio.pms.typing import Portfolio as PortfolioDTO
18
+ from wbportfolio.rebalancing.base import AbstractRebalancingModel
19
+
20
+
21
+ class RebalancingModel(models.Model):
22
+ name = models.CharField(max_length=64, verbose_name="Name")
23
+ class_path = models.CharField(max_length=512, verbose_name="Class path")
24
+
25
+ def __str__(self):
26
+ return self.name
27
+
28
+ class Meta:
29
+ verbose_name = "Rebalancing Model"
30
+ verbose_name_plural = "Rebalancing Models"
31
+
32
+ @cached_property
33
+ def model_class(self) -> type[AbstractRebalancingModel]:
34
+ """
35
+ Return the imported backend class
36
+ Returns:
37
+ The backend class
38
+ """
39
+ return import_from_dotted_path(self.class_path)
40
+
41
+ def get_target_portfolio(
42
+ self, portfolio: Portfolio, trade_date: date, last_effective_date: date, **kwargs
43
+ ) -> PortfolioDTO:
44
+ model = self.model_class(portfolio, trade_date, last_effective_date)
45
+ if not model.is_valid():
46
+ raise ValidationError("Rebalacing cannot applied for these parameters")
47
+ return model.get_target_portfolio(**kwargs)
48
+
49
+ @classmethod
50
+ def get_representation_endpoint(cls) -> str:
51
+ return "wbportfolio:rebalancingmodelrepresentation-list"
52
+
53
+ @classmethod
54
+ def get_representation_value_key(cls) -> str:
55
+ return "id"
56
+
57
+ @classmethod
58
+ def get_representation_label_key(cls) -> str:
59
+ return "{{name}}"
60
+
61
+
62
+ class Rebalancer(ComplexToStringMixin, models.Model):
63
+ portfolio = models.OneToOneField(
64
+ "wbportfolio.Portfolio", on_delete=models.CASCADE, related_name="automatic_rebalancer"
65
+ )
66
+ rebalancing_model = models.ForeignKey(
67
+ RebalancingModel, on_delete=models.PROTECT, related_name="rebalancers", verbose_name="Rebalancing Model"
68
+ )
69
+ parameters = models.JSONField(default=dict, verbose_name="Parameters", blank=True)
70
+ approve_trade_proposal_automatically = models.BooleanField(
71
+ default=False, verbose_name="Apply Trade Proposal Automatically"
72
+ )
73
+ activation_date = models.DateField(verbose_name="Activation Date")
74
+ frequency = models.CharField(
75
+ default="RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=1",
76
+ max_length=56,
77
+ verbose_name=_("Evaluation Frequency"),
78
+ help_text=_("The Evaluation Frequency in RRULE format"),
79
+ )
80
+
81
+ def save(self, *args, **kwargs):
82
+ if not self.activation_date:
83
+ try:
84
+ self.activation_date = self.portfolio.assets.earliest("date").date
85
+ except ObjectDoesNotExist:
86
+ self.activation_date = date.today()
87
+ super().save(*args, **kwargs)
88
+
89
+ def is_valid(self, trade_date: date) -> bool:
90
+ valid_dates = [_d.date() for _d in self.get_rrule(trade_date)]
91
+ return trade_date in valid_dates
92
+
93
+ def evaluate_rebalancing(self, trade_date: date):
94
+ trade_proposal, _ = TradeProposal.objects.get_or_create(
95
+ trade_date=trade_date,
96
+ portfolio=self.portfolio,
97
+ defaults={
98
+ "comment": "Automatic rebalancing",
99
+ "rebalancing_model": self.rebalancing_model,
100
+ },
101
+ )
102
+ if trade_proposal.rebalancing_model == self.rebalancing_model:
103
+ trade_proposal.status = TradeProposal.Status.DRAFT
104
+ target_portfolio = self.rebalancing_model.get_target_portfolio(
105
+ self.portfolio, trade_date, trade_proposal.last_effective_date, **self.parameters
106
+ )
107
+ try:
108
+ trade_proposal.reset_trades(target_portfolio)
109
+ trade_proposal.submit()
110
+ except ValidationError:
111
+ pass # Do something
112
+
113
+ if self.approve_trade_proposal_automatically and self.portfolio.can_be_rebalanced:
114
+ trade_proposal.approve()
115
+ trade_proposal.save()
116
+
117
+ return trade_proposal
118
+
119
+ @property
120
+ def rrule(self):
121
+ return self.get_rrule()
122
+
123
+ @property
124
+ def frequency_repr(self):
125
+ return humanize_rrule(self.rrule)
126
+
127
+ def get_rrule(self, to_date: date | None = None, count: int | None = None):
128
+ rrule_dict = convert_rrulestr_to_dict(self.frequency, dtstart=self.activation_date, until=to_date, count=count)
129
+ return rrule.rrule(**rrule_dict)
130
+
131
+ def compute_str(self):
132
+ return f"{self.frequency_repr} {self.portfolio.name} ({self.rebalancing_model.name})"
133
+
134
+ class Meta:
135
+ verbose_name = "Rebalancer"
136
+ verbose_name_plural = "Rebalancers"
137
+
138
+ @classmethod
139
+ def get_endpoint_basename(cls) -> str:
140
+ return "wbportfolio:rebalancer"
141
+
142
+ @classmethod
143
+ def get_representation_endpoint(cls) -> str:
144
+ return "wbportfolio:rebalancerrepresentation-list"
145
+
146
+ @classmethod
147
+ def get_representation_value_key(cls) -> str:
148
+ return "id"
149
+
150
+
151
+ @receiver(post_migrate, sender=RebalancingModel)
152
+ def post_migrate_rebalancing_model(sender, verbosity, interactive, stdout, using, plan, apps, **kwargs):
153
+ autodiscover_modules("rebalancing.models")