wbportfolio 1.44.5__py2.py3-none-any.whl → 1.45.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.
- 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 +23 -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 +161 -0
- wbportfolio/models/graphs/utils.py +83 -0
- wbportfolio/models/indexes.py +2 -13
- wbportfolio/models/mixins/instruments.py +28 -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 +12 -5
- 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 +2 -1
- 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 +619 -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/assets.py +12 -0
- 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.5.dist-info → wbportfolio-1.45.1.dist-info}/METADATA +4 -1
- wbportfolio-1.45.1.dist-info/RECORD +521 -0
- 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-1.44.5.dist-info/RECORD +0 -508
- /wbportfolio/{defaults/portfolio → models/graphs}/__init__.py +0 -0
- {wbportfolio-1.44.5.dist-info → wbportfolio-1.45.1.dist-info}/WHEEL +0 -0
- {wbportfolio-1.44.5.dist-info → wbportfolio-1.45.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
|
|
3
|
+
from wbcore import serializers as wb_serializers
|
|
4
|
+
from wbcore.serializers import DefaultFromGET
|
|
5
|
+
|
|
6
|
+
from wbportfolio.models import Portfolio, Rebalancer, RebalancingModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RebalancingModelRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
|
10
|
+
class Meta:
|
|
11
|
+
model = RebalancingModel
|
|
12
|
+
fields = (
|
|
13
|
+
"id",
|
|
14
|
+
"name",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RebalancerRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
|
19
|
+
_detail = wb_serializers.HyperlinkField(reverse_name="wbportfolio:rebalancer-detail")
|
|
20
|
+
|
|
21
|
+
class Meta:
|
|
22
|
+
model = Rebalancer
|
|
23
|
+
fields = ("id", "computed_str", "_detail")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RebalancerModelSerializer(wb_serializers.ModelSerializer):
|
|
27
|
+
_rebalancing_model = RebalancingModelRepresentationSerializer(source="rebalancing_model")
|
|
28
|
+
frequency_repr = wb_serializers.SerializerMethodField()
|
|
29
|
+
portfolio = wb_serializers.PrimaryKeyRelatedField(
|
|
30
|
+
queryset=Portfolio.objects.all(), default=DefaultFromGET("portfolio")
|
|
31
|
+
)
|
|
32
|
+
activation_date = wb_serializers.DateField(default=lambda: date.today())
|
|
33
|
+
|
|
34
|
+
rebalancing_dates = wb_serializers.SerializerMethodField(
|
|
35
|
+
label="Next 10 Rebalancing Dates", field_class=wb_serializers.ListField
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def get_rebalancing_dates(self, obj):
|
|
39
|
+
return list(map(lambda d: d.strftime("%Y-%m-%d"), obj.get_rrule(count=10)))
|
|
40
|
+
|
|
41
|
+
def get_frequency_repr(self, obj):
|
|
42
|
+
return obj.frequency_repr
|
|
43
|
+
|
|
44
|
+
class Meta:
|
|
45
|
+
model = Rebalancer
|
|
46
|
+
fields = (
|
|
47
|
+
"id",
|
|
48
|
+
"portfolio",
|
|
49
|
+
"computed_str",
|
|
50
|
+
"_rebalancing_model",
|
|
51
|
+
"rebalancing_model",
|
|
52
|
+
"approve_trade_proposal_automatically",
|
|
53
|
+
"activation_date",
|
|
54
|
+
"frequency",
|
|
55
|
+
"frequency_repr",
|
|
56
|
+
"rebalancing_dates",
|
|
57
|
+
)
|
|
@@ -2,14 +2,15 @@ from typing import TYPE_CHECKING, Any
|
|
|
2
2
|
|
|
3
3
|
from rest_framework.reverse import reverse
|
|
4
4
|
from wbcore import serializers
|
|
5
|
+
from wbcore.contrib.authentication.models import User
|
|
5
6
|
from wbcore.contrib.authentication.serializers import UserRepresentationSerializer
|
|
6
7
|
from wbcore.utils.urls import new_mode
|
|
7
8
|
from wbcrm.models import Account
|
|
8
9
|
from wbcrm.serializers.accounts import AccountRepresentationSerializer
|
|
10
|
+
|
|
9
11
|
from wbportfolio.models import AccountReconciliation
|
|
10
12
|
from wbportfolio.models.reconciliations import AccountReconciliationLine
|
|
11
13
|
from wbportfolio.serializers.products import ProductRepresentationSerializer
|
|
12
|
-
from wbcore.contrib.authentication.models import User
|
|
13
14
|
|
|
14
15
|
if TYPE_CHECKING:
|
|
15
16
|
from rest_framework.request import Request
|
wbportfolio/serializers/roles.py
CHANGED
|
@@ -2,6 +2,7 @@ from rest_framework import serializers
|
|
|
2
2
|
from wbcore import serializers as wb_serializers
|
|
3
3
|
from wbcore.contrib.directory.serializers import PersonRepresentationSerializer
|
|
4
4
|
from wbfdm.serializers import InvestableInstrumentRepresentationSerializer
|
|
5
|
+
|
|
5
6
|
from wbportfolio.models import PortfolioRole
|
|
6
7
|
|
|
7
8
|
|
|
@@ -11,6 +11,7 @@ from wbcore.filters.defaults import current_quarter_date_start
|
|
|
11
11
|
from wbcore.signals import add_instance_additional_resource
|
|
12
12
|
from wbcrm.serializers.accounts import AccountModelSerializer
|
|
13
13
|
from wbfdm.serializers import InstrumentModelSerializer
|
|
14
|
+
|
|
14
15
|
from wbportfolio.models import PortfolioRole, Trade
|
|
15
16
|
from wbportfolio.serializers.products import ProductModelSerializer
|
|
16
17
|
|
|
@@ -47,9 +48,9 @@ def add_instrument_serializer_resources(sender, serializer, instance, request, u
|
|
|
47
48
|
.exclude(transaction_subtype__in=[Trade.Type.REDEMPTION, Trade.Type.SUBSCRIPTION])
|
|
48
49
|
.exists()
|
|
49
50
|
):
|
|
50
|
-
additional_resources[
|
|
51
|
-
"
|
|
52
|
-
|
|
51
|
+
additional_resources["instrument_trades"] = (
|
|
52
|
+
f'{reverse("wbportfolio:instrument-trade-list", args=[instance.id], request=request)}?is_customer_trade=False'
|
|
53
|
+
)
|
|
53
54
|
|
|
54
55
|
if PortfolioRole.is_manager(user.profile):
|
|
55
56
|
additional_resources["portfoliorole"] = reverse(
|
|
@@ -74,12 +75,6 @@ def asset_portfolio_resources(sender, serializer, instance, request, user, **kwa
|
|
|
74
75
|
args=[portfolio.id],
|
|
75
76
|
request=request,
|
|
76
77
|
)
|
|
77
|
-
if request.user.has_perm("wbportfolio.administrate_instrument") and (
|
|
78
|
-
portfolio.portfolio_synchronization or hasattr(instance, "price_computation")
|
|
79
|
-
):
|
|
80
|
-
additional_resources[
|
|
81
|
-
"resynchronize"
|
|
82
|
-
] = f'{reverse("wbportfolio:portfolio-resynchronize", args=[portfolio.id], request=request)}?instrument={instance.id}'
|
|
83
78
|
return additional_resources
|
|
84
79
|
|
|
85
80
|
|
|
@@ -90,12 +85,12 @@ def transactions_resources(sender, serializer, instance, request, user, **kwargs
|
|
|
90
85
|
portfolio = instance.primary_portfolio
|
|
91
86
|
if portfolio:
|
|
92
87
|
if portfolio.transactions.exists() and user.profile.is_internal:
|
|
93
|
-
additional_resources[
|
|
94
|
-
"
|
|
95
|
-
|
|
96
|
-
additional_resources[
|
|
97
|
-
"
|
|
98
|
-
|
|
88
|
+
additional_resources["portfolio_subscriptionsredemptions"] = (
|
|
89
|
+
f'{reverse("wbportfolio:portfolio-trade-list", args=[portfolio.id], request=request)}?is_customer_trade=True'
|
|
90
|
+
)
|
|
91
|
+
additional_resources["portfolio_trades"] = (
|
|
92
|
+
f'{reverse("wbportfolio:portfolio-trade-list", args=[portfolio.id], request=request)}?is_customer_trade=False'
|
|
93
|
+
)
|
|
99
94
|
|
|
100
95
|
additional_resources["portfolio_transactions"] = reverse(
|
|
101
96
|
"wbportfolio:portfolio-transaction-list", args=[portfolio.id], request=request
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from .trade_proposals import TradeProposalModelSerializer, ReadOnlyTradeProposalModelSerializer
|
|
1
2
|
from .claim import (
|
|
2
3
|
ClaimAccountSerializer,
|
|
3
4
|
ClaimAPIModelSerializer,
|
|
@@ -12,7 +13,6 @@ from .expiry import ExpiryModelSerializer, ExpiryRepresentationSerializer
|
|
|
12
13
|
from .fees import FeesModelSerializer, FeesRepresentationSerializer
|
|
13
14
|
from .trades import (
|
|
14
15
|
TradeModelSerializer,
|
|
15
|
-
TradeProposalModelSerializer,
|
|
16
16
|
TradeProposalRepresentationSerializer,
|
|
17
17
|
TradeRepresentationSerializer,
|
|
18
18
|
TradeTradeProposalModelSerializer,
|
|
@@ -6,6 +6,7 @@ from wbcore.contrib.directory.models import Person
|
|
|
6
6
|
from wbcore.contrib.directory.serializers import EntryRepresentationSerializer
|
|
7
7
|
from wbcrm.models import Account
|
|
8
8
|
from wbcrm.serializers.accounts import TerminalAccountRepresentationSerializer
|
|
9
|
+
|
|
9
10
|
from wbportfolio.models import Product, Trade
|
|
10
11
|
from wbportfolio.models.transactions.claim import Claim
|
|
11
12
|
from wbportfolio.serializers.products import (
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
|
|
3
|
+
from django.core.exceptions import ValidationError
|
|
4
|
+
from rest_framework.reverse import reverse
|
|
5
|
+
from wbcore import serializers as wb_serializers
|
|
6
|
+
from wbcore.serializers import DefaultFromView
|
|
7
|
+
|
|
8
|
+
from wbportfolio.models import Portfolio, RebalancingModel, TradeProposal
|
|
9
|
+
|
|
10
|
+
from .. import PortfolioRepresentationSerializer, RebalancingModelRepresentationSerializer
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TradeProposalModelSerializer(wb_serializers.ModelSerializer):
|
|
14
|
+
rebalancing_model = wb_serializers.PrimaryKeyRelatedField(
|
|
15
|
+
queryset=RebalancingModel.objects.all(),
|
|
16
|
+
default=DefaultFromView("portfolio.automatic_rebalancer.rebalancing_model"),
|
|
17
|
+
)
|
|
18
|
+
_rebalancing_model = RebalancingModelRepresentationSerializer(source="rebalancing_model")
|
|
19
|
+
target_portfolio = wb_serializers.PrimaryKeyRelatedField(
|
|
20
|
+
queryset=Portfolio.objects.all(), write_only=True, required=False, default=DefaultFromView("portfolio")
|
|
21
|
+
)
|
|
22
|
+
_target_portfolio = PortfolioRepresentationSerializer(source="target_portfolio")
|
|
23
|
+
|
|
24
|
+
trade_date = wb_serializers.DateField(
|
|
25
|
+
read_only=lambda view: not view.new_mode, default=DefaultFromView("default_trade_date")
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def create(self, validated_data):
|
|
29
|
+
target_portfolio = validated_data.pop("target_portfolio", None)
|
|
30
|
+
rebalancing_model = validated_data.get("rebalancing_model", None)
|
|
31
|
+
if request := self.context.get("request"):
|
|
32
|
+
validated_data["creator"] = request.user.profile
|
|
33
|
+
obj = super().create(validated_data)
|
|
34
|
+
|
|
35
|
+
target_portfolio_dto = None
|
|
36
|
+
if (
|
|
37
|
+
target_portfolio
|
|
38
|
+
and not rebalancing_model
|
|
39
|
+
and (
|
|
40
|
+
last_effective_date := target_portfolio.get_latest_asset_position_date(
|
|
41
|
+
obj.trade_date, with_estimated=True
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
):
|
|
45
|
+
target_portfolio_dto = target_portfolio._build_dto(last_effective_date)
|
|
46
|
+
with suppress(
|
|
47
|
+
ValidationError
|
|
48
|
+
): # we ignore validation error at this point as the trade are automatically create for convenience
|
|
49
|
+
obj.reset_trades(target_portfolio=target_portfolio_dto)
|
|
50
|
+
return obj
|
|
51
|
+
|
|
52
|
+
@wb_serializers.register_only_instance_resource()
|
|
53
|
+
def additional_resources(self, instance, request, user, **kwargs):
|
|
54
|
+
res = {}
|
|
55
|
+
if instance.status == TradeProposal.Status.APPROVED:
|
|
56
|
+
res["replay"] = reverse("wbportfolio:tradeproposal-replay", args=[instance.id], request=request)
|
|
57
|
+
if instance.status == TradeProposal.Status.DRAFT:
|
|
58
|
+
res["reset"] = reverse("wbportfolio:tradeproposal-reset", args=[instance.id], request=request)
|
|
59
|
+
res["trades"] = reverse(
|
|
60
|
+
"wbportfolio:tradeproposal-trade-list",
|
|
61
|
+
args=[instance.id],
|
|
62
|
+
request=request,
|
|
63
|
+
)
|
|
64
|
+
return res
|
|
65
|
+
|
|
66
|
+
class Meta:
|
|
67
|
+
model = TradeProposal
|
|
68
|
+
only_fsm_transition_on_instance = True
|
|
69
|
+
fields = (
|
|
70
|
+
"id",
|
|
71
|
+
"trade_date",
|
|
72
|
+
"comment",
|
|
73
|
+
"status",
|
|
74
|
+
"portfolio",
|
|
75
|
+
"_rebalancing_model",
|
|
76
|
+
"rebalancing_model",
|
|
77
|
+
"target_portfolio",
|
|
78
|
+
"_target_portfolio",
|
|
79
|
+
"_additional_resources",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ReadOnlyTradeProposalModelSerializer(TradeProposalModelSerializer):
|
|
84
|
+
class Meta(TradeProposalModelSerializer.Meta):
|
|
85
|
+
read_only_fields = TradeProposalModelSerializer.Meta.fields
|
|
@@ -4,10 +4,10 @@ from decimal import Decimal
|
|
|
4
4
|
from rest_framework import serializers
|
|
5
5
|
from rest_framework.reverse import reverse
|
|
6
6
|
from wbcore import serializers as wb_serializers
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
from wbportfolio.models import PortfolioRole, Trade, TradeProposal
|
|
8
9
|
from wbportfolio.models.transactions.claim import Claim
|
|
9
10
|
from wbportfolio.serializers.custodians import CustodianRepresentationSerializer
|
|
10
|
-
from wbportfolio.serializers.portfolios import PortfolioRepresentationSerializer
|
|
11
11
|
from wbportfolio.serializers.registers import RegisterRepresentationSerializer
|
|
12
12
|
|
|
13
13
|
from .transactions import (
|
|
@@ -215,6 +215,8 @@ class TradeTradeProposalModelSerializer(TradeModelSerializer):
|
|
|
215
215
|
status = wb_serializers.ChoiceField(default=Trade.Status.DRAFT, choices=Trade.Status.choices)
|
|
216
216
|
target_weight = wb_serializers.DecimalField(max_digits=16, decimal_places=6, required=False, default=0)
|
|
217
217
|
effective_weight = wb_serializers.DecimalField(read_only=True, max_digits=16, decimal_places=6, default=0)
|
|
218
|
+
effective_shares = wb_serializers.DecimalField(read_only=True, max_digits=16, decimal_places=6, default=0)
|
|
219
|
+
target_shares = wb_serializers.DecimalField(read_only=True, max_digits=16, decimal_places=6, default=0)
|
|
218
220
|
|
|
219
221
|
def validate(self, data):
|
|
220
222
|
if self.instance and "underlying_instrument" in data:
|
|
@@ -240,6 +242,9 @@ class TradeTradeProposalModelSerializer(TradeModelSerializer):
|
|
|
240
242
|
"shares",
|
|
241
243
|
# "underlying_instrument",
|
|
242
244
|
# "_underlying_instrument",
|
|
245
|
+
"shares",
|
|
246
|
+
"effective_shares",
|
|
247
|
+
"target_shares",
|
|
243
248
|
)
|
|
244
249
|
fields = (
|
|
245
250
|
"id",
|
|
@@ -248,64 +253,17 @@ class TradeTradeProposalModelSerializer(TradeModelSerializer):
|
|
|
248
253
|
"_underlying_instrument",
|
|
249
254
|
"transaction_subtype",
|
|
250
255
|
"status",
|
|
251
|
-
# "total_value",
|
|
252
|
-
# "total_value_fx_portfolio",
|
|
253
|
-
# "total_value_gross",
|
|
254
|
-
# "total_value_gross_fx_portfolio",
|
|
255
256
|
"comment",
|
|
256
|
-
# "total_value_usd",
|
|
257
|
-
# "total_value_gross_usd",
|
|
258
257
|
"effective_weight",
|
|
259
258
|
"target_weight",
|
|
260
259
|
"weighting",
|
|
261
260
|
"trade_proposal",
|
|
262
261
|
"order",
|
|
262
|
+
"effective_shares",
|
|
263
|
+
"target_shares",
|
|
263
264
|
)
|
|
264
265
|
|
|
265
266
|
|
|
266
267
|
class ReadOnlyTradeTradeProposalModelSerializer(TradeTradeProposalModelSerializer):
|
|
267
268
|
class Meta(TradeTradeProposalModelSerializer.Meta):
|
|
268
269
|
read_only_fields = TradeTradeProposalModelSerializer.Meta.fields
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
class TradeProposalModelSerializer(wb_serializers.ModelSerializer):
|
|
272
|
-
_model_portfolio = PortfolioRepresentationSerializer(source="model_portfolio")
|
|
273
|
-
model_portfolio = wb_serializers.PrimaryKeyRelatedField(
|
|
274
|
-
queryset=Portfolio.objects.all(),
|
|
275
|
-
default=wb_serializers.DefaultFromKwargs("portfolio_id"),
|
|
276
|
-
read_only=lambda view: not view.new_mode,
|
|
277
|
-
)
|
|
278
|
-
trade_date = wb_serializers.DateField(read_only=lambda view: not view.new_mode)
|
|
279
|
-
|
|
280
|
-
def create(self, validated_data):
|
|
281
|
-
if request := self.context.get("request"):
|
|
282
|
-
validated_data["creator"] = request.user.profile
|
|
283
|
-
return super().create(validated_data)
|
|
284
|
-
|
|
285
|
-
@wb_serializers.register_only_instance_resource()
|
|
286
|
-
def additional_resources(self, instance, request, user, **kwargs):
|
|
287
|
-
res = {}
|
|
288
|
-
if instance.status == TradeProposal.Status.APPROVED:
|
|
289
|
-
res["replay"] = reverse("wbportfolio:tradeproposal-replay", args=[instance.id], request=request)
|
|
290
|
-
if instance.model_portfolio:
|
|
291
|
-
res["reset"] = reverse("wbportfolio:tradeproposal-reset", args=[instance.id], request=request)
|
|
292
|
-
res["trades"] = reverse(
|
|
293
|
-
"wbportfolio:tradeproposal-trade-list",
|
|
294
|
-
args=[instance.id],
|
|
295
|
-
request=request,
|
|
296
|
-
)
|
|
297
|
-
return res
|
|
298
|
-
|
|
299
|
-
class Meta:
|
|
300
|
-
model = TradeProposal
|
|
301
|
-
only_fsm_transition_on_instance = True
|
|
302
|
-
fields = (
|
|
303
|
-
"id",
|
|
304
|
-
"trade_date",
|
|
305
|
-
"comment",
|
|
306
|
-
"status",
|
|
307
|
-
"portfolio",
|
|
308
|
-
"_model_portfolio",
|
|
309
|
-
"model_portfolio",
|
|
310
|
-
"_additional_resources",
|
|
311
|
-
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from wbcore import serializers as wb_serializers
|
|
2
2
|
from wbcore.contrib.currency.serializers import CurrencyRepresentationSerializer
|
|
3
|
-
from wbfdm.serializers
|
|
3
|
+
from wbfdm.serializers import InvestableUniverseRepresentationSerializer
|
|
4
|
+
|
|
4
5
|
from wbportfolio.models import Transaction
|
|
5
6
|
from wbportfolio.serializers.portfolios import PortfolioRepresentationSerializer
|
|
6
7
|
|
|
@@ -21,7 +22,7 @@ class TransactionModelSerializer(wb_serializers.ModelSerializer):
|
|
|
21
22
|
transaction_underlying_type = wb_serializers.CharField(read_only=True)
|
|
22
23
|
transaction_url_type = wb_serializers.SerializerMethodField()
|
|
23
24
|
_portfolio = PortfolioRepresentationSerializer(source="portfolio")
|
|
24
|
-
_underlying_instrument =
|
|
25
|
+
_underlying_instrument = InvestableUniverseRepresentationSerializer(source="underlying_instrument")
|
|
25
26
|
_currency = CurrencyRepresentationSerializer(source="currency")
|
|
26
27
|
|
|
27
28
|
total_value = wb_serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
|
|
@@ -54,7 +55,7 @@ class TransactionModelSerializer(wb_serializers.ModelSerializer):
|
|
|
54
55
|
),
|
|
55
56
|
"total_value_gross_fx_portfolio": wb_serializers.decorator(
|
|
56
57
|
position="left", value="{{_portfolio.currency_symbol}}"
|
|
57
|
-
)
|
|
58
|
+
),
|
|
58
59
|
# "total_value_fx_portfolio": wb_serializers.decorator(decorator_type="text", position="left", value="{{_portfolio.currency_symbol}}}"),
|
|
59
60
|
# "total_value_gross_fx_portfolio": wb_serializers.decorator(decorator_type="text", position="left", value="{{_portfolio.currency_symbol}}"),
|
|
60
61
|
}
|
wbportfolio/tasks.py
CHANGED
|
@@ -1,23 +1,14 @@
|
|
|
1
1
|
from contextlib import suppress
|
|
2
2
|
from datetime import date, timedelta
|
|
3
3
|
|
|
4
|
-
from celery import chain, chord
|
|
5
4
|
from django.db.models import ProtectedError, Q
|
|
6
|
-
|
|
7
|
-
from tqdm import tqdm
|
|
8
|
-
from wbfdm.models import InstrumentPrice
|
|
5
|
+
|
|
9
6
|
from wbportfolio.models import Portfolio, Trade
|
|
10
|
-
from wbportfolio.models.portfolio import propagate_or_update_portfolio_assets_as_task
|
|
11
7
|
from wbportfolio.models.products import Product, update_outstanding_shares_as_task
|
|
12
8
|
|
|
13
9
|
from .fdm.tasks import * # noqa
|
|
14
10
|
|
|
15
11
|
|
|
16
|
-
@shared_task()
|
|
17
|
-
def dummy_task():
|
|
18
|
-
return True
|
|
19
|
-
|
|
20
|
-
|
|
21
12
|
@shared_task(queue="portfolio")
|
|
22
13
|
def periodically_update_outstanding_shares_for_active_products():
|
|
23
14
|
qs = Product.active_objects.all()
|
|
@@ -42,29 +33,6 @@ def periodically_clean_marked_for_deletion_trades(max_allowed_iterations: int =
|
|
|
42
33
|
i += 1
|
|
43
34
|
|
|
44
35
|
|
|
45
|
-
# Daily synchronization tasks.
|
|
46
|
-
# This tasks needs to be ran at maximum once a day in order to guarantee data consitency in
|
|
47
|
-
# case of change in change (e.g. reimport).
|
|
48
|
-
@shared_task(queue="portfolio")
|
|
49
|
-
def daily_instrument_price_statistics_synchronization(today: date = None, day_periods: int = 7):
|
|
50
|
-
if not today:
|
|
51
|
-
today = date.today()
|
|
52
|
-
|
|
53
|
-
# We query for the last 7 days unsynch instrument prices.
|
|
54
|
-
prices = (
|
|
55
|
-
InstrumentPrice.objects.filter(
|
|
56
|
-
date__gte=today - timedelta(days=day_periods), instrument__related_instruments__isnull=False
|
|
57
|
-
)
|
|
58
|
-
.filter(Q(sharpe_ratio__isnull=True) | Q(correlation__isnull=True) | Q(beta__isnull=True))
|
|
59
|
-
.distinct()
|
|
60
|
-
)
|
|
61
|
-
objs = []
|
|
62
|
-
for p in prices.iterator():
|
|
63
|
-
p.compute_and_update_statistics()
|
|
64
|
-
objs.append(p)
|
|
65
|
-
InstrumentPrice.objects.bulk_update(objs, fields=["sharpe_ratio", "correlation", "beta"])
|
|
66
|
-
|
|
67
|
-
|
|
68
36
|
# A Task to run every day to update automatically the preferred classification
|
|
69
37
|
# per instrument of each wbportfolio containing assets.
|
|
70
38
|
@shared_task(queue="portfolio")
|
|
@@ -78,48 +46,3 @@ def update_preferred_classification_per_instrument_and_portfolio_as_task():
|
|
|
78
46
|
# - propagate (or update) t-2 asset positions into t-1
|
|
79
47
|
# - Synchronize wbportfolio at t-1
|
|
80
48
|
# - Compute Instrument Price estimate at t-1
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
@shared_task(queue="portfolio")
|
|
84
|
-
def general_portfolio_synchronization_and_update(task_date=None):
|
|
85
|
-
if not task_date:
|
|
86
|
-
task_date = date.today()
|
|
87
|
-
t_1 = (task_date - BDay(1)).date()
|
|
88
|
-
t_2 = (t_1 - BDay(1)).date()
|
|
89
|
-
|
|
90
|
-
# We propagate or update assets position from t-2 to t-1 (Needs stainly t-1 prices)
|
|
91
|
-
subroutines_propagate_or_update_portfolio_assets = list()
|
|
92
|
-
for portfolio in Portfolio.tracked_objects.all():
|
|
93
|
-
if (
|
|
94
|
-
portfolio.assets.filter(date=t_2).exists()
|
|
95
|
-
and not portfolio.assets.filter(date=t_1, is_estimated=False).exists()
|
|
96
|
-
):
|
|
97
|
-
subroutines_propagate_or_update_portfolio_assets.append(
|
|
98
|
-
propagate_or_update_portfolio_assets_as_task.si(portfolio.id, t_2, t_1)
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
# # Synchronize wbportfolio at t-1 (needs propagated data)
|
|
102
|
-
# subroutines_synchronize_computed_positions = list()
|
|
103
|
-
# for method in PortfolioSynchronization.objects.all():
|
|
104
|
-
# for portfolio in method.portfolios.all():
|
|
105
|
-
# subroutines_synchronize_computed_positions.append(
|
|
106
|
-
# synchronize_portfolio_as_task.si(portfolio.id, t_1, synchronization_method_id=method.id)
|
|
107
|
-
# )
|
|
108
|
-
#
|
|
109
|
-
# # Compute price estimate at t-1 (needs synchronized wbportfolio)
|
|
110
|
-
# subroutines_compute_price = list()
|
|
111
|
-
# for method in PriceComputation.objects.all():
|
|
112
|
-
# for instrument in method.instruments.all():
|
|
113
|
-
# subroutines_compute_price.append(
|
|
114
|
-
# compute_price_as_task.si(instrument.id, t_1, price_computation_method_id=method.id)
|
|
115
|
-
# )
|
|
116
|
-
|
|
117
|
-
subroutines_propagate_or_update_portfolio_assets_group = chord(
|
|
118
|
-
subroutines_propagate_or_update_portfolio_assets, dummy_task.si()
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
chain(
|
|
122
|
-
subroutines_propagate_or_update_portfolio_assets_group,
|
|
123
|
-
# subroutines_synchronize_computed_positions_group,
|
|
124
|
-
# subroutines_compute_price_group
|
|
125
|
-
).apply_async()
|
wbportfolio/tests/conftest.py
CHANGED
|
@@ -21,6 +21,7 @@ from wbcore.contrib.directory.factories.entries import (
|
|
|
21
21
|
)
|
|
22
22
|
from wbcore.contrib.geography.factories import CityFactory, CountryFactory, StateFactory
|
|
23
23
|
from wbcore.contrib.io.factories import (
|
|
24
|
+
CrontabScheduleFactory,
|
|
24
25
|
DataBackendFactory,
|
|
25
26
|
ImportSourceFactory,
|
|
26
27
|
ParserHandlerFactory,
|
|
@@ -67,13 +68,8 @@ from wbportfolio.factories import (
|
|
|
67
68
|
TradeFactory,
|
|
68
69
|
TradeProposalFactory,
|
|
69
70
|
WhiteLabelProductFactory,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
CrontabScheduleFactory,
|
|
73
|
-
PeriodicTaskFactory,
|
|
74
|
-
PortfolioSynchronizationFactory,
|
|
75
|
-
PriceComputationFactory,
|
|
76
|
-
SynchronizationTaskFactory,
|
|
71
|
+
RebalancerFactory,
|
|
72
|
+
RebalancingModelFactory
|
|
77
73
|
)
|
|
78
74
|
|
|
79
75
|
from wbcore.tests.conftest import * # isort:skip
|
|
@@ -90,7 +86,7 @@ register(DataBackendFactory)
|
|
|
90
86
|
register(ProviderFactory)
|
|
91
87
|
register(SourceFactory)
|
|
92
88
|
register(ParserHandlerFactory)
|
|
93
|
-
|
|
89
|
+
register(CrontabScheduleFactory)
|
|
94
90
|
register(AssetPositionFactory)
|
|
95
91
|
register(ProductFactory)
|
|
96
92
|
register(ProductGroupFactory)
|
|
@@ -110,6 +106,8 @@ register(IndexProductFactory, "index_product")
|
|
|
110
106
|
register(CurrencyFXRatesFactory)
|
|
111
107
|
register(ProductPortfolioRoleFactory, "product_portfolio_role")
|
|
112
108
|
register(ManagerPortfolioRoleFactory, "manager_portfolio_role")
|
|
109
|
+
register(RebalancerFactory)
|
|
110
|
+
register(RebalancingModelFactory)
|
|
113
111
|
|
|
114
112
|
register(CurrencyFactory)
|
|
115
113
|
register(CityFactory)
|
|
@@ -128,11 +126,6 @@ register(UserFactory)
|
|
|
128
126
|
register(SuperUserFactory, "superuser")
|
|
129
127
|
register(CustodianFactory)
|
|
130
128
|
|
|
131
|
-
register(CrontabScheduleFactory)
|
|
132
|
-
register(PeriodicTaskFactory)
|
|
133
|
-
register(SynchronizationTaskFactory)
|
|
134
|
-
register(PortfolioSynchronizationFactory)
|
|
135
|
-
register(PriceComputationFactory)
|
|
136
129
|
register(AdjustmentFactory)
|
|
137
130
|
register(IndexFactory)
|
|
138
131
|
|
|
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING
|
|
|
4
4
|
import pytest
|
|
5
5
|
from django.db import IntegrityError
|
|
6
6
|
from wbcrm.models import Account
|
|
7
|
+
|
|
7
8
|
from wbportfolio.models import AccountReconciliation, Claim
|
|
8
9
|
from wbportfolio.models.products import Product
|
|
9
10
|
from wbportfolio.models.reconciliations.account_reconciliation_lines import (
|
|
@@ -12,6 +13,7 @@ from wbportfolio.models.reconciliations.account_reconciliation_lines import (
|
|
|
12
13
|
|
|
13
14
|
if TYPE_CHECKING:
|
|
14
15
|
from wbcore.contrib.authentication.models import User
|
|
16
|
+
|
|
15
17
|
from wbportfolio.factories import AccountReconciliationLineFactory, ClaimFactory
|
|
16
18
|
|
|
17
19
|
|
|
@@ -2,6 +2,7 @@ from decimal import Decimal
|
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
from pandas.tseries.offsets import BDay
|
|
5
|
+
|
|
5
6
|
from wbportfolio.models import AssetPosition, PortfolioRole
|
|
6
7
|
|
|
7
8
|
|
|
@@ -52,13 +53,13 @@ class TestAssetPositionModel:
|
|
|
52
53
|
i2 = instrument_price_factory.create(instrument=equity_factory.create(), market_capitalization=30000000000)
|
|
53
54
|
asset_position_factory.create(
|
|
54
55
|
portfolio=portfolio,
|
|
55
|
-
|
|
56
|
+
underlying_quote_price=i1,
|
|
56
57
|
currency_fx_rate_instrument_to_usd=fx_rate,
|
|
57
58
|
currency_fx_rate_portfolio_to_usd=fx_rate,
|
|
58
59
|
) # small
|
|
59
60
|
asset_position_factory.create(
|
|
60
61
|
portfolio=portfolio,
|
|
61
|
-
|
|
62
|
+
underlying_quote_price=i2,
|
|
62
63
|
currency_fx_rate_instrument_to_usd=fx_rate,
|
|
63
64
|
currency_fx_rate_portfolio_to_usd=fx_rate,
|
|
64
65
|
) # larg
|
|
@@ -130,24 +131,24 @@ class TestAssetPositionModel:
|
|
|
130
131
|
assert ap4.performance is None
|
|
131
132
|
assert ep4._net_value_usd / ep3._net_value_usd is not None
|
|
132
133
|
|
|
133
|
-
def
|
|
134
|
+
def test_get_underlying_quote_price(self, asset_position_factory, instrument_price_factory):
|
|
134
135
|
"""
|
|
135
|
-
In this test, we confirm that an asset position with no matching real price for the same date and underlying instrument will result in an empty
|
|
136
|
+
In this test, we confirm that an asset position with no matching real price for the same date and underlying instrument will result in an empty underlying_quote_price
|
|
136
137
|
"""
|
|
137
|
-
a1 = asset_position_factory.create(
|
|
138
|
+
a1 = asset_position_factory.create(underlying_quote_price=None)
|
|
138
139
|
a1.save()
|
|
139
|
-
assert a1.
|
|
140
|
+
assert a1.underlying_quote_price is None
|
|
140
141
|
p2_calculated = instrument_price_factory.create(calculated=True)
|
|
141
142
|
a2 = asset_position_factory.create(
|
|
142
|
-
|
|
143
|
+
underlying_quote_price=None, underlying_instrument=p2_calculated.instrument, date=p2_calculated.date
|
|
143
144
|
)
|
|
144
145
|
a2.save()
|
|
145
|
-
assert a2.
|
|
146
|
+
assert a2.underlying_quote_price is None
|
|
146
147
|
p2_real = instrument_price_factory.create(
|
|
147
148
|
calculated=False, date=p2_calculated.date, instrument=p2_calculated.instrument
|
|
148
149
|
)
|
|
149
150
|
a2.save()
|
|
150
|
-
assert a2.
|
|
151
|
+
assert a2.underlying_quote_price == p2_real
|
|
151
152
|
|
|
152
153
|
def test_change_currency_recompute_currency_fx_rate_for_price_and_assets(
|
|
153
154
|
self, weekday, instrument, currency_fx_rates_factory, instrument_price_factory, asset_position_factory
|
|
@@ -173,21 +174,28 @@ class TestAssetPositionModel:
|
|
|
173
174
|
def test_create_price_set_assetposition(self, asset_position_factory, instrument_price_factory):
|
|
174
175
|
p0 = instrument_price_factory.create()
|
|
175
176
|
d1 = (p0.date + BDay(1)).date()
|
|
176
|
-
a1 = asset_position_factory.create(
|
|
177
|
-
date=d1, underlying_instrument=p0.instrument, underlying_instrument_price=None
|
|
178
|
-
)
|
|
177
|
+
a1 = asset_position_factory.create(date=d1, underlying_instrument=p0.instrument, underlying_quote_price=None)
|
|
179
178
|
|
|
180
179
|
# check it is linked to the only price
|
|
181
|
-
assert a1.
|
|
180
|
+
assert a1.underlying_quote_price == p0
|
|
182
181
|
p1 = instrument_price_factory.create(instrument=p0.instrument, date=d1)
|
|
183
182
|
a1.refresh_from_db()
|
|
184
|
-
assert a1.
|
|
183
|
+
assert a1.underlying_quote_price == p1
|
|
185
184
|
|
|
186
185
|
d_1 = (p0.date - BDay(1)).date()
|
|
187
|
-
a_1 = asset_position_factory.create(
|
|
188
|
-
|
|
189
|
-
)
|
|
190
|
-
assert a_1.underlying_instrument_price is None
|
|
186
|
+
a_1 = asset_position_factory.create(date=d_1, underlying_instrument=p1.instrument, underlying_quote_price=None)
|
|
187
|
+
assert a_1.underlying_quote_price is None
|
|
191
188
|
p_1 = instrument_price_factory.create(instrument=p1.instrument, date=d_1)
|
|
192
189
|
a_1.refresh_from_db()
|
|
193
|
-
assert a_1.
|
|
190
|
+
assert a_1.underlying_quote_price == p_1
|
|
191
|
+
|
|
192
|
+
def test_save_asset_without_instrument(self, asset_position_factory, instrument_factory):
|
|
193
|
+
parent = instrument_factory.create()
|
|
194
|
+
instrument_factory.create(parent=parent) # noise
|
|
195
|
+
primary_quote = instrument_factory.create(parent=parent, is_primary=True)
|
|
196
|
+
|
|
197
|
+
a = asset_position_factory.create(underlying_quote=primary_quote, underlying_instrument=None)
|
|
198
|
+
assert a.underlying_instrument == parent
|
|
199
|
+
|
|
200
|
+
a = asset_position_factory.create(underlying_quote=None, underlying_instrument=parent)
|
|
201
|
+
assert a.underlying_quote == primary_quote
|