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.
- wbportfolio/admin/__init__.py +1 -1
- wbportfolio/admin/asset.py +2 -1
- wbportfolio/admin/custodians.py +1 -0
- wbportfolio/admin/indexes.py +15 -0
- wbportfolio/admin/portfolio.py +12 -7
- wbportfolio/admin/portfolio_relationships.py +1 -0
- wbportfolio/admin/product_groups.py +2 -0
- wbportfolio/admin/products.py +2 -1
- wbportfolio/admin/reconciliations.py +1 -0
- wbportfolio/admin/registers.py +1 -0
- wbportfolio/admin/roles.py +1 -0
- wbportfolio/admin/transactions/__init__.py +1 -0
- wbportfolio/admin/transactions/claim.py +1 -0
- wbportfolio/admin/transactions/dividends.py +1 -0
- wbportfolio/admin/transactions/fees.py +1 -0
- wbportfolio/admin/transactions/rebalancing.py +26 -0
- wbportfolio/admin/transactions/trades.py +4 -3
- wbportfolio/admin/transactions/transactions.py +1 -0
- wbportfolio/analysis/claims.py +2 -1
- wbportfolio/contrib/company_portfolio/models.py +3 -6
- wbportfolio/contrib/company_portfolio/tests/conftest.py +0 -12
- wbportfolio/contrib/company_portfolio/tests/test_models.py +1 -0
- wbportfolio/defaults/fees/default.py +1 -0
- wbportfolio/factories/__init__.py +1 -7
- wbportfolio/factories/adjustments.py +1 -0
- wbportfolio/factories/assets.py +13 -7
- wbportfolio/factories/claim.py +1 -0
- wbportfolio/factories/custodians.py +1 -0
- wbportfolio/factories/dividends.py +1 -0
- wbportfolio/factories/fees.py +1 -0
- wbportfolio/factories/indexes.py +1 -0
- wbportfolio/factories/portfolio_cash_flow.py +1 -0
- wbportfolio/factories/portfolio_cash_targets.py +1 -0
- wbportfolio/factories/portfolio_swing_pricings.py +1 -0
- wbportfolio/factories/portfolios.py +3 -0
- wbportfolio/factories/product_groups.py +1 -0
- wbportfolio/factories/products.py +1 -0
- wbportfolio/factories/rebalancing.py +23 -0
- wbportfolio/factories/reconciliations.py +1 -0
- wbportfolio/factories/roles.py +1 -0
- wbportfolio/factories/trades.py +1 -0
- wbportfolio/factories/transactions.py +1 -0
- wbportfolio/fdm/tasks.py +1 -0
- wbportfolio/filters/__init__.py +1 -1
- wbportfolio/filters/assets.py +8 -9
- wbportfolio/filters/assets_and_net_new_money_progression.py +1 -0
- wbportfolio/filters/custodians.py +1 -0
- wbportfolio/filters/esg.py +1 -0
- wbportfolio/filters/performances.py +7 -6
- wbportfolio/filters/portfolios.py +21 -1
- wbportfolio/filters/positions.py +1 -0
- wbportfolio/filters/products.py +1 -0
- wbportfolio/filters/roles.py +1 -0
- wbportfolio/filters/signals.py +1 -0
- wbportfolio/filters/transactions/claim.py +1 -0
- wbportfolio/filters/transactions/fees.py +1 -0
- wbportfolio/filters/transactions/trades.py +2 -1
- wbportfolio/filters/transactions/transactions.py +1 -0
- wbportfolio/import_export/backends/ubs/mixin.py +1 -0
- wbportfolio/import_export/backends/wbfdm/adjustment.py +1 -0
- wbportfolio/import_export/handlers/asset_position.py +11 -13
- wbportfolio/import_export/handlers/fees.py +1 -0
- wbportfolio/import_export/handlers/portfolio_cash_flow.py +1 -0
- wbportfolio/import_export/handlers/trade.py +1 -0
- wbportfolio/import_export/parsers/jpmorgan/customer_trade.py +1 -0
- wbportfolio/import_export/parsers/jpmorgan/fees.py +1 -0
- wbportfolio/import_export/parsers/jpmorgan/strategy.py +5 -4
- wbportfolio/import_export/parsers/jpmorgan/valuation.py +1 -0
- wbportfolio/import_export/parsers/leonteq/customer_trade.py +1 -0
- wbportfolio/import_export/parsers/leonteq/equity.py +13 -12
- wbportfolio/import_export/parsers/leonteq/fees.py +1 -0
- wbportfolio/import_export/parsers/leonteq/trade.py +1 -0
- wbportfolio/import_export/parsers/leonteq/valuation.py +1 -0
- wbportfolio/import_export/parsers/natixis/customer_trade.py +1 -0
- wbportfolio/import_export/parsers/natixis/d1_customer_trade.py +1 -0
- wbportfolio/import_export/parsers/natixis/d1_equity.py +3 -2
- wbportfolio/import_export/parsers/natixis/d1_fees.py +1 -0
- wbportfolio/import_export/parsers/natixis/d1_trade.py +1 -0
- wbportfolio/import_export/parsers/natixis/d1_valuation.py +1 -0
- wbportfolio/import_export/parsers/natixis/equity.py +5 -5
- wbportfolio/import_export/parsers/natixis/trade.py +1 -0
- wbportfolio/import_export/parsers/natixis/utils.py +8 -7
- wbportfolio/import_export/parsers/sg_lux/custodian_positions.py +1 -0
- wbportfolio/import_export/parsers/sg_lux/customer_trade.py +1 -0
- wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py +2 -1
- wbportfolio/import_export/parsers/sg_lux/customer_trade_slk.py +2 -1
- wbportfolio/import_export/parsers/sg_lux/customer_trade_without_pw.py +1 -0
- wbportfolio/import_export/parsers/sg_lux/equity.py +7 -8
- wbportfolio/import_export/parsers/sg_lux/portfolio_cash_flow.py +1 -0
- wbportfolio/import_export/parsers/sg_lux/portfolio_future_cash_flow.py +1 -0
- wbportfolio/import_export/parsers/sg_lux/registers.py +2 -1
- wbportfolio/import_export/parsers/societe_generale/customer_trade.py +1 -0
- wbportfolio/import_export/parsers/societe_generale/strategy.py +8 -9
- wbportfolio/import_export/parsers/societe_generale/valuation.py +1 -0
- wbportfolio/import_export/parsers/tellco/equity.py +5 -4
- wbportfolio/import_export/parsers/ubs/api/asset_position.py +15 -14
- wbportfolio/import_export/parsers/ubs/api/fees.py +1 -0
- wbportfolio/import_export/parsers/ubs/customer_trade.py +1 -0
- wbportfolio/import_export/parsers/ubs/equity.py +3 -2
- wbportfolio/import_export/parsers/ubs/historical_customer_trade.py +1 -0
- wbportfolio/import_export/parsers/ubs/valuation.py +1 -0
- wbportfolio/import_export/parsers/vontobel/asset_position.py +19 -19
- wbportfolio/import_export/parsers/vontobel/customer_trade.py +1 -0
- wbportfolio/import_export/parsers/vontobel/historical_customer_trade.py +1 -0
- wbportfolio/import_export/parsers/vontobel/management_fees.py +1 -0
- wbportfolio/import_export/parsers/vontobel/performance_fees.py +1 -0
- wbportfolio/import_export/parsers/vontobel/trade.py +1 -0
- wbportfolio/import_export/parsers/vontobel/valuation_api.py +20 -0
- wbportfolio/import_export/resources/assets.py +4 -3
- wbportfolio/import_export/resources/trades.py +1 -0
- wbportfolio/metric/backends/base.py +1 -0
- wbportfolio/metric/backends/portfolio_base.py +1 -0
- wbportfolio/metric/backends/portfolio_esg.py +1 -0
- wbportfolio/metric/tests/test_portfolio_base.py +1 -0
- wbportfolio/migrations/0052_remove_cash_instrument_ptr_and_more.py +1 -131
- wbportfolio/migrations/0067_assetposition_unique_asset_position.py +1 -1
- wbportfolio/migrations/0070_remove_assetposition_unique_asset_position_and_more.py +1 -1
- wbportfolio/migrations/0073_remove_product_price_computation_and_more.py +407 -0
- wbportfolio/models/__init__.py +0 -5
- wbportfolio/models/adjustments.py +8 -2
- wbportfolio/models/asset.py +117 -98
- wbportfolio/models/graphs/portfolio.py +144 -0
- wbportfolio/models/graphs/utils.py +83 -0
- wbportfolio/models/indexes.py +2 -13
- wbportfolio/models/mixins/instruments.py +30 -8
- wbportfolio/models/portfolio.py +538 -332
- wbportfolio/models/portfolio_cash_flow.py +1 -0
- wbportfolio/models/portfolio_relationship.py +6 -2
- wbportfolio/models/product_groups.py +3 -2
- wbportfolio/models/products.py +3 -17
- wbportfolio/models/reconciliations/account_reconciliation_lines.py +1 -0
- wbportfolio/models/reconciliations/account_reconciliations.py +1 -0
- wbportfolio/models/registers.py +1 -0
- wbportfolio/models/transactions/__init__.py +1 -0
- wbportfolio/models/transactions/claim.py +8 -8
- wbportfolio/models/transactions/dividends.py +1 -0
- wbportfolio/models/transactions/fees.py +1 -0
- wbportfolio/models/transactions/rebalancing.py +153 -0
- wbportfolio/models/transactions/trade_proposals.py +153 -155
- wbportfolio/models/transactions/trades.py +48 -40
- wbportfolio/models/transactions/transactions.py +6 -12
- wbportfolio/models/utils.py +1 -0
- wbportfolio/pms/analytics/__init__.py +0 -0
- wbportfolio/pms/analytics/portfolio.py +28 -0
- wbportfolio/pms/trading/handler.py +13 -16
- wbportfolio/pms/typing.py +13 -29
- wbportfolio/rebalancing/__init__.py +0 -0
- wbportfolio/rebalancing/base.py +16 -0
- wbportfolio/rebalancing/decorators.py +17 -0
- wbportfolio/rebalancing/models/__init__.py +3 -0
- wbportfolio/rebalancing/models/composite.py +31 -0
- wbportfolio/rebalancing/models/equally_weighted.py +21 -0
- wbportfolio/rebalancing/models/model_portfolio.py +35 -0
- wbportfolio/reports/monthly_position_report.py +1 -1
- wbportfolio/risk_management/backends/accounts.py +7 -6
- wbportfolio/risk_management/backends/controversy_portfolio.py +1 -0
- wbportfolio/risk_management/backends/exposure_portfolio.py +1 -0
- wbportfolio/risk_management/backends/instrument_list_portfolio.py +1 -0
- wbportfolio/risk_management/backends/liquidity_risk.py +1 -0
- wbportfolio/risk_management/backends/liquidity_stress_instrument.py +1 -0
- wbportfolio/risk_management/backends/mixins.py +1 -0
- wbportfolio/risk_management/backends/product_integrity.py +6 -1
- wbportfolio/risk_management/backends/stop_loss_instrument.py +1 -0
- wbportfolio/risk_management/backends/stop_loss_portfolio.py +1 -0
- wbportfolio/risk_management/backends/ucits_portfolio.py +1 -0
- wbportfolio/risk_management/tests/test_accounts.py +1 -0
- wbportfolio/risk_management/tests/test_controversy_portfolio.py +1 -0
- wbportfolio/risk_management/tests/test_exposure_portfolio.py +1 -0
- wbportfolio/risk_management/tests/test_instrument_list_portfolio.py +1 -0
- wbportfolio/risk_management/tests/test_liquidity_risk.py +1 -0
- wbportfolio/risk_management/tests/test_product_integrity.py +1 -0
- wbportfolio/risk_management/tests/test_stop_loss_instrument.py +1 -0
- wbportfolio/risk_management/tests/test_stop_loss_portfolio.py +1 -0
- wbportfolio/risk_management/tests/test_ucits_portfolio.py +1 -0
- wbportfolio/serializers/__init__.py +5 -5
- wbportfolio/serializers/adjustments.py +1 -0
- wbportfolio/serializers/assets.py +18 -19
- wbportfolio/serializers/custodians.py +1 -0
- wbportfolio/serializers/portfolio_cash_flow.py +1 -0
- wbportfolio/serializers/portfolio_cash_targets.py +1 -0
- wbportfolio/serializers/portfolio_relationship.py +1 -0
- wbportfolio/serializers/portfolio_swing_pricing.py +1 -0
- wbportfolio/serializers/portfolios.py +61 -40
- wbportfolio/serializers/positions.py +1 -0
- wbportfolio/serializers/product_group.py +1 -0
- wbportfolio/serializers/products.py +4 -7
- wbportfolio/serializers/rebalancing.py +57 -0
- wbportfolio/serializers/reconciliations.py +2 -1
- wbportfolio/serializers/registers.py +1 -0
- wbportfolio/serializers/roles.py +1 -0
- wbportfolio/serializers/signals.py +10 -15
- wbportfolio/serializers/transactions/__init__.py +1 -1
- wbportfolio/serializers/transactions/claim.py +1 -0
- wbportfolio/serializers/transactions/fees.py +1 -0
- wbportfolio/serializers/transactions/trade_proposals.py +85 -0
- wbportfolio/serializers/transactions/trades.py +9 -51
- wbportfolio/serializers/transactions/transactions.py +4 -3
- wbportfolio/tasks.py +1 -78
- wbportfolio/tests/conftest.py +6 -13
- wbportfolio/tests/models/test_account_reconciliation.py +2 -0
- wbportfolio/tests/models/test_assets.py +27 -19
- wbportfolio/tests/models/test_customer_trades.py +1 -0
- wbportfolio/tests/models/test_imports.py +5 -1
- wbportfolio/tests/models/test_merge.py +5 -4
- wbportfolio/tests/models/test_portfolio_cash_flow.py +8 -6
- wbportfolio/tests/models/test_portfolios.py +594 -154
- wbportfolio/tests/models/test_product_groups.py +1 -0
- wbportfolio/tests/models/test_products.py +6 -3
- wbportfolio/tests/models/test_roles.py +1 -0
- wbportfolio/tests/models/test_splits.py +1 -0
- wbportfolio/tests/models/transactions/test_claim.py +1 -0
- wbportfolio/tests/models/transactions/test_fees.py +1 -0
- wbportfolio/tests/models/transactions/test_rebalancing.py +81 -0
- wbportfolio/tests/models/transactions/test_trades.py +1 -0
- wbportfolio/tests/models/utils.py +1 -0
- wbportfolio/tests/pms/__init__.py +0 -0
- wbportfolio/tests/pms/test_analytics.py +35 -0
- wbportfolio/tests/rebalancing/__init__.py +0 -0
- wbportfolio/tests/rebalancing/test_models.py +127 -0
- wbportfolio/tests/serializers/test_claims.py +1 -0
- wbportfolio/tests/signals.py +1 -7
- wbportfolio/tests/tests.py +2 -0
- wbportfolio/tests/viewsets/test_assets.py +1 -0
- wbportfolio/tests/viewsets/test_performances.py +1 -0
- wbportfolio/tests/viewsets/test_products.py +1 -0
- wbportfolio/tests/viewsets/transactions/test_claims.py +1 -0
- wbportfolio/urls.py +26 -12
- wbportfolio/viewsets/__init__.py +2 -5
- wbportfolio/viewsets/adjustments.py +1 -0
- wbportfolio/viewsets/assets.py +62 -51
- wbportfolio/viewsets/assets_and_net_new_money_progression.py +1 -0
- wbportfolio/viewsets/charts/assets.py +3 -1
- wbportfolio/viewsets/configs/buttons/__init__.py +1 -1
- wbportfolio/viewsets/configs/buttons/assets.py +1 -0
- wbportfolio/viewsets/configs/buttons/custodians.py +1 -0
- wbportfolio/viewsets/configs/buttons/mixins.py +1 -20
- wbportfolio/viewsets/configs/buttons/portfolios.py +90 -76
- wbportfolio/viewsets/configs/buttons/signals.py +1 -0
- wbportfolio/viewsets/configs/buttons/trades.py +1 -0
- wbportfolio/viewsets/configs/display/__init__.py +2 -1
- wbportfolio/viewsets/configs/display/adjustments.py +1 -0
- wbportfolio/viewsets/configs/display/assets.py +7 -6
- wbportfolio/viewsets/configs/display/claim.py +1 -0
- wbportfolio/viewsets/configs/display/portfolios.py +127 -79
- wbportfolio/viewsets/configs/display/product_performance.py +1 -0
- wbportfolio/viewsets/configs/display/rebalancing.py +27 -0
- wbportfolio/viewsets/configs/display/trade_proposals.py +7 -4
- wbportfolio/viewsets/configs/display/trades.py +75 -42
- wbportfolio/viewsets/configs/endpoints/__init__.py +3 -1
- wbportfolio/viewsets/configs/endpoints/claim.py +1 -0
- wbportfolio/viewsets/configs/endpoints/portfolios.py +23 -7
- wbportfolio/viewsets/configs/endpoints/rebalancing.py +6 -0
- wbportfolio/viewsets/configs/endpoints/reconciliations.py +1 -0
- wbportfolio/viewsets/configs/endpoints/trade_proposals.py +1 -0
- wbportfolio/viewsets/configs/endpoints/trades.py +1 -0
- wbportfolio/viewsets/configs/menu/adjustments.py +1 -0
- wbportfolio/viewsets/configs/menu/assets.py +1 -0
- wbportfolio/viewsets/configs/menu/fees.py +1 -0
- wbportfolio/viewsets/configs/menu/portfolio_cash_flow.py +1 -0
- wbportfolio/viewsets/configs/menu/portfolios.py +4 -2
- wbportfolio/viewsets/configs/menu/positions.py +1 -0
- wbportfolio/viewsets/configs/menu/roles.py +1 -0
- wbportfolio/viewsets/configs/menu/transactions.py +1 -0
- wbportfolio/viewsets/configs/previews/portfolios.py +1 -6
- wbportfolio/viewsets/configs/titles/__init__.py +1 -1
- wbportfolio/viewsets/configs/titles/assets.py +1 -0
- wbportfolio/viewsets/configs/titles/fees.py +1 -0
- wbportfolio/viewsets/configs/titles/instrument_prices.py +1 -0
- wbportfolio/viewsets/configs/titles/portfolios.py +13 -11
- wbportfolio/viewsets/configs/titles/roles.py +1 -0
- wbportfolio/viewsets/configs/titles/trades.py +1 -0
- wbportfolio/viewsets/configs/titles/transactions.py +1 -0
- wbportfolio/viewsets/custodians.py +1 -0
- wbportfolio/viewsets/esg.py +1 -0
- wbportfolio/viewsets/mixins.py +1 -0
- wbportfolio/viewsets/portfolio_cash_flow.py +1 -0
- wbportfolio/viewsets/portfolio_cash_targets.py +1 -0
- wbportfolio/viewsets/portfolio_relationship.py +1 -0
- wbportfolio/viewsets/portfolio_swing_pricing.py +1 -0
- wbportfolio/viewsets/portfolios.py +228 -61
- wbportfolio/viewsets/positions.py +3 -2
- wbportfolio/viewsets/product_groups.py +1 -0
- wbportfolio/viewsets/product_performance.py +1 -0
- wbportfolio/viewsets/products.py +1 -0
- wbportfolio/viewsets/reconciliations.py +1 -0
- wbportfolio/viewsets/registers.py +1 -0
- wbportfolio/viewsets/roles.py +1 -0
- wbportfolio/viewsets/signals.py +1 -0
- wbportfolio/viewsets/transactions/__init__.py +1 -0
- wbportfolio/viewsets/transactions/claim.py +2 -1
- wbportfolio/viewsets/transactions/fees.py +1 -0
- wbportfolio/viewsets/transactions/mixins.py +1 -0
- wbportfolio/viewsets/transactions/rebalancing.py +31 -0
- wbportfolio/viewsets/transactions/trade_proposals.py +25 -5
- wbportfolio/viewsets/transactions/trades.py +16 -9
- wbportfolio/viewsets/transactions/transactions.py +1 -0
- {wbportfolio-1.44.4.dist-info → wbportfolio-1.45.0.dist-info}/METADATA +4 -1
- {wbportfolio-1.44.4.dist-info → wbportfolio-1.45.0.dist-info}/RECORD +301 -288
- wbportfolio/admin/synchronization/__init__.py +0 -2
- wbportfolio/admin/synchronization/admin.py +0 -114
- wbportfolio/admin/synchronization/portfolio_synchronization.py +0 -18
- wbportfolio/admin/synchronization/price_computation.py +0 -21
- wbportfolio/defaults/portfolio/default_rebalancing.py +0 -45
- wbportfolio/factories/pytest_utils.py +0 -121
- wbportfolio/factories/synchronization.py +0 -40
- wbportfolio/models/synchronization/__init__.py +0 -3
- wbportfolio/models/synchronization/portfolio_synchronization.py +0 -292
- wbportfolio/models/synchronization/price_computation.py +0 -200
- wbportfolio/models/synchronization/synchronization.py +0 -188
- wbportfolio/serializers/synchronization.py +0 -18
- wbportfolio/tests/models/test_synchronization.py +0 -617
- wbportfolio/viewsets/synchronization.py +0 -25
- /wbportfolio/{defaults/portfolio → models/graphs}/__init__.py +0 -0
- {wbportfolio-1.44.4.dist-info → wbportfolio-1.45.0.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
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
|
|
12
|
+
from .mixins.instruments import PMSInstrumentAbstractModel
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
class ProductGroup(
|
|
15
|
+
class ProductGroup(PMSInstrumentAbstractModel):
|
|
15
16
|
class ProductGroupType(models.TextChoices):
|
|
16
17
|
FUND = "Fund"
|
|
17
18
|
|
wbportfolio/models/products.py
CHANGED
|
@@ -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(
|
|
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
|
)
|
wbportfolio/models/registers.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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(
|
|
@@ -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")
|