wbportfolio 1.52.0__py2.py3-none-any.whl → 1.59.4__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 +3 -1
- wbportfolio/admin/indexes.py +1 -1
- wbportfolio/admin/orders/__init__.py +2 -0
- wbportfolio/admin/orders/order_proposals.py +16 -0
- wbportfolio/admin/orders/orders.py +32 -0
- wbportfolio/admin/portfolio.py +11 -5
- wbportfolio/admin/product_groups.py +1 -1
- wbportfolio/admin/products.py +2 -1
- wbportfolio/admin/{transactions/rebalancing.py → rebalancing.py} +1 -1
- wbportfolio/admin/transactions/__init__.py +0 -2
- wbportfolio/admin/transactions/dividends.py +40 -4
- wbportfolio/admin/transactions/fees.py +24 -14
- wbportfolio/admin/transactions/trades.py +34 -27
- wbportfolio/analysis/claims.py +5 -6
- wbportfolio/api_clients/ubs.py +162 -0
- wbportfolio/constants.py +1 -0
- wbportfolio/contrib/company_portfolio/configs/display.py +22 -10
- wbportfolio/contrib/company_portfolio/configs/previews.py +3 -3
- wbportfolio/contrib/company_portfolio/filters.py +10 -10
- wbportfolio/contrib/company_portfolio/models.py +69 -39
- wbportfolio/contrib/company_portfolio/scripts.py +7 -2
- wbportfolio/contrib/company_portfolio/serializers.py +32 -22
- wbportfolio/contrib/company_portfolio/tasks.py +12 -1
- wbportfolio/contrib/company_portfolio/tests/conftest.py +2 -2
- wbportfolio/defaults/fees/default.py +7 -15
- wbportfolio/factories/__init__.py +2 -2
- wbportfolio/factories/assets.py +1 -1
- wbportfolio/factories/dividends.py +8 -3
- wbportfolio/factories/fees.py +8 -4
- wbportfolio/factories/orders/__init__.py +2 -0
- wbportfolio/factories/orders/order_proposals.py +21 -0
- wbportfolio/factories/orders/orders.py +34 -0
- wbportfolio/factories/portfolios.py +2 -1
- wbportfolio/factories/product_groups.py +3 -3
- wbportfolio/factories/products.py +3 -3
- wbportfolio/factories/rebalancing.py +1 -1
- wbportfolio/factories/trades.py +12 -16
- wbportfolio/filters/assets.py +18 -4
- wbportfolio/filters/orders/__init__.py +2 -0
- wbportfolio/filters/orders/order_proposals.py +55 -0
- wbportfolio/filters/orders/orders.py +11 -0
- wbportfolio/filters/portfolios.py +38 -1
- wbportfolio/filters/positions.py +0 -1
- wbportfolio/filters/transactions/__init__.py +1 -2
- wbportfolio/filters/transactions/fees.py +5 -12
- wbportfolio/filters/transactions/trades.py +16 -8
- wbportfolio/filters/transactions/utils.py +42 -0
- wbportfolio/import_export/backends/ubs/__init__.py +1 -0
- wbportfolio/import_export/backends/ubs/asset_position.py +6 -7
- wbportfolio/import_export/backends/ubs/fees.py +10 -20
- wbportfolio/import_export/backends/ubs/instrument_price.py +6 -6
- wbportfolio/import_export/backends/ubs/trade.py +48 -0
- wbportfolio/import_export/backends/utils.py +0 -17
- wbportfolio/import_export/handlers/asset_position.py +22 -10
- wbportfolio/import_export/handlers/dividend.py +8 -8
- wbportfolio/import_export/handlers/fees.py +13 -23
- wbportfolio/import_export/handlers/orders.py +71 -0
- wbportfolio/import_export/handlers/trade.py +53 -77
- wbportfolio/import_export/parsers/default_mapping.py +1 -1
- wbportfolio/import_export/parsers/jpmorgan/customer_trade.py +2 -2
- wbportfolio/import_export/parsers/jpmorgan/fees.py +4 -4
- wbportfolio/import_export/parsers/jpmorgan/strategy.py +59 -85
- wbportfolio/import_export/parsers/jpmorgan/valuation.py +2 -2
- wbportfolio/import_export/parsers/leonteq/customer_trade.py +5 -5
- wbportfolio/import_export/parsers/leonteq/fees.py +11 -7
- wbportfolio/import_export/parsers/leonteq/trade.py +2 -6
- wbportfolio/import_export/parsers/natixis/d1_fees.py +2 -2
- wbportfolio/import_export/parsers/natixis/dividend.py +4 -9
- wbportfolio/import_export/parsers/natixis/equity.py +22 -4
- wbportfolio/import_export/parsers/natixis/fees.py +7 -9
- wbportfolio/import_export/parsers/natixis/utils.py +13 -19
- wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py +1 -1
- wbportfolio/import_export/parsers/sg_lux/equity.py +10 -10
- wbportfolio/import_export/parsers/sg_lux/fees.py +2 -2
- wbportfolio/import_export/parsers/sg_lux/perf_fees.py +2 -2
- wbportfolio/import_export/parsers/sg_lux/sylk.py +12 -11
- wbportfolio/import_export/parsers/sg_lux/utils.py +2 -2
- wbportfolio/import_export/parsers/sg_lux/valuation.py +4 -2
- wbportfolio/import_export/parsers/societe_generale/strategy.py +5 -5
- wbportfolio/import_export/parsers/tellco/customer_trade.py +2 -1
- wbportfolio/import_export/parsers/tellco/valuation.py +4 -3
- wbportfolio/import_export/parsers/ubs/api/fees.py +2 -2
- wbportfolio/import_export/parsers/ubs/api/trade.py +39 -0
- wbportfolio/import_export/parsers/ubs/customer_trade.py +7 -5
- wbportfolio/import_export/parsers/ubs/equity.py +3 -2
- wbportfolio/import_export/parsers/ubs/valuation.py +2 -1
- wbportfolio/import_export/parsers/vontobel/customer_trade.py +2 -3
- wbportfolio/import_export/parsers/vontobel/historical_customer_trade.py +0 -1
- wbportfolio/import_export/parsers/vontobel/management_fees.py +12 -20
- wbportfolio/import_export/parsers/vontobel/performance_fees.py +5 -8
- wbportfolio/import_export/parsers/vontobel/valuation_api.py +4 -1
- wbportfolio/import_export/resources/trades.py +3 -3
- wbportfolio/import_export/utils.py +3 -1
- wbportfolio/jinja2/wbportfolio/sql/aum_nnm.sql +2 -2
- wbportfolio/metric/backends/base.py +2 -2
- wbportfolio/migrations/0059_fees_unique_fees.py +1 -1
- wbportfolio/migrations/0077_remove_transaction_currency_and_more.py +622 -0
- wbportfolio/migrations/0078_trade_drift_factor.py +26 -0
- wbportfolio/migrations/0079_alter_trade_drift_factor.py +19 -0
- wbportfolio/migrations/0080_alter_trade_drift_factor_alter_trade_weighting.py +19 -0
- wbportfolio/migrations/0081_alter_trade_drift_factor.py +19 -0
- wbportfolio/migrations/0082_remove_tradeproposal_creator_and_more.py +93 -0
- wbportfolio/migrations/0083_order_alter_trade_options_and_more.py +181 -0
- wbportfolio/migrations/0084_orderproposal_min_order_value.py +25 -0
- wbportfolio/migrations/0085_order_desired_target_weight.py +26 -0
- wbportfolio/migrations/0086_orderproposal_total_cash_weight.py +19 -0
- wbportfolio/migrations/0087_product_order_routing_custodian_adapter.py +94 -0
- wbportfolio/migrations/0088_orderproposal_total_effective_portfolio_contribution.py +19 -0
- wbportfolio/migrations/0089_orderproposal_min_weighting.py +71 -0
- wbportfolio/migrations/0090_dividendtransaction_price_fx_portfolio_and_more.py +44 -0
- wbportfolio/migrations/0091_remove_order_execution_confirmed_and_more.py +32 -0
- wbportfolio/migrations/0092_order_quantization_error_alter_orderproposal_status.py +49 -0
- wbportfolio/migrations/0093_remove_portfolioportfoliothroughmodel_unique_primary_and_more.py +35 -0
- wbportfolio/models/__init__.py +2 -0
- wbportfolio/models/adjustments.py +1 -1
- wbportfolio/models/asset.py +28 -170
- wbportfolio/models/builder.py +323 -0
- wbportfolio/models/custodians.py +3 -3
- wbportfolio/models/exceptions.py +1 -1
- wbportfolio/models/graphs/portfolio.py +1 -1
- wbportfolio/models/graphs/utils.py +11 -11
- wbportfolio/models/mixins/instruments.py +7 -0
- wbportfolio/models/mixins/liquidity_stress_test.py +4 -4
- wbportfolio/models/orders/__init__.py +2 -0
- wbportfolio/models/orders/order_proposals.py +1414 -0
- wbportfolio/models/orders/orders.py +410 -0
- wbportfolio/models/portfolio.py +311 -289
- wbportfolio/models/portfolio_relationship.py +6 -0
- wbportfolio/models/products.py +12 -0
- wbportfolio/models/{transactions/rebalancing.py → rebalancing.py} +40 -27
- wbportfolio/models/roles.py +4 -10
- wbportfolio/models/transactions/__init__.py +0 -4
- wbportfolio/models/transactions/claim.py +7 -6
- wbportfolio/models/transactions/dividends.py +42 -5
- wbportfolio/models/transactions/fees.py +55 -22
- wbportfolio/models/transactions/trades.py +121 -442
- wbportfolio/models/transactions/transactions.py +78 -158
- wbportfolio/models/utils.py +100 -1
- wbportfolio/order_routing/__init__.py +35 -0
- wbportfolio/order_routing/adapters/__init__.py +65 -0
- wbportfolio/order_routing/adapters/ubs.py +195 -0
- wbportfolio/order_routing/router.py +33 -0
- wbportfolio/order_routing/tests/__init__.py +0 -0
- wbportfolio/order_routing/tests/test_router.py +110 -0
- wbportfolio/permissions.py +7 -0
- wbportfolio/pms/analytics/portfolio.py +17 -9
- wbportfolio/pms/analytics/utils.py +9 -0
- wbportfolio/pms/trading/__init__.py +0 -1
- wbportfolio/pms/trading/optimizer.py +61 -0
- wbportfolio/pms/typing.py +198 -63
- wbportfolio/rebalancing/base.py +12 -1
- wbportfolio/rebalancing/decorators.py +1 -1
- wbportfolio/rebalancing/models/composite.py +4 -8
- wbportfolio/rebalancing/models/equally_weighted.py +13 -11
- wbportfolio/rebalancing/models/market_capitalization_weighted.py +21 -14
- wbportfolio/rebalancing/models/model_portfolio.py +14 -18
- wbportfolio/risk_management/backends/__init__.py +1 -0
- wbportfolio/risk_management/backends/controversy_portfolio.py +2 -2
- wbportfolio/risk_management/backends/esg_aggregation_portfolio.py +64 -0
- wbportfolio/risk_management/backends/exposure_portfolio.py +4 -4
- wbportfolio/risk_management/backends/instrument_list_portfolio.py +3 -3
- wbportfolio/risk_management/tests/test_esg_aggregation_portfolio.py +49 -0
- wbportfolio/risk_management/tests/test_exposure_portfolio.py +1 -1
- wbportfolio/risk_management/tests/test_stop_loss_instrument.py +2 -2
- wbportfolio/risk_management/tests/test_stop_loss_portfolio.py +1 -1
- wbportfolio/serializers/__init__.py +1 -0
- wbportfolio/serializers/orders/__init__.py +2 -0
- wbportfolio/serializers/orders/order_proposals.py +115 -0
- wbportfolio/serializers/orders/orders.py +283 -0
- wbportfolio/serializers/portfolios.py +7 -7
- wbportfolio/serializers/positions.py +2 -2
- wbportfolio/serializers/rebalancing.py +1 -1
- wbportfolio/serializers/signals.py +9 -12
- wbportfolio/serializers/transactions/__init__.py +1 -10
- wbportfolio/serializers/transactions/claim.py +2 -2
- wbportfolio/serializers/transactions/dividends.py +37 -9
- wbportfolio/serializers/transactions/fees.py +39 -10
- wbportfolio/serializers/transactions/trades.py +55 -157
- wbportfolio/tasks.py +43 -5
- wbportfolio/tests/analysis/__init__.py +0 -0
- wbportfolio/tests/analysis/test_claims.py +85 -0
- wbportfolio/tests/conftest.py +12 -12
- wbportfolio/tests/models/orders/__init__.py +0 -0
- wbportfolio/tests/models/orders/test_order_proposals.py +1046 -0
- wbportfolio/tests/models/test_assets.py +7 -3
- wbportfolio/tests/models/test_imports.py +9 -13
- wbportfolio/tests/models/test_portfolios.py +102 -95
- wbportfolio/tests/models/test_products.py +11 -0
- wbportfolio/tests/models/test_splits.py +1 -6
- wbportfolio/tests/models/test_utils.py +140 -0
- wbportfolio/tests/models/transactions/test_fees.py +7 -13
- wbportfolio/tests/models/transactions/test_rebalancing.py +5 -5
- wbportfolio/tests/models/transactions/test_trades.py +0 -20
- wbportfolio/tests/pms/test_analytics.py +22 -3
- wbportfolio/tests/rebalancing/test_models.py +51 -57
- wbportfolio/tests/signals.py +10 -20
- wbportfolio/tests/tests.py +3 -1
- wbportfolio/tests/viewsets/test_products.py +1 -0
- wbportfolio/urls.py +10 -13
- wbportfolio/viewsets/__init__.py +9 -4
- wbportfolio/viewsets/assets.py +3 -204
- wbportfolio/viewsets/charts/__init__.py +6 -1
- wbportfolio/viewsets/charts/assets.py +344 -154
- wbportfolio/viewsets/configs/buttons/__init__.py +2 -2
- wbportfolio/viewsets/configs/buttons/assets.py +1 -1
- wbportfolio/viewsets/configs/buttons/mixins.py +4 -4
- wbportfolio/viewsets/configs/buttons/portfolios.py +45 -1
- wbportfolio/viewsets/configs/buttons/products.py +32 -2
- wbportfolio/viewsets/configs/display/__init__.py +2 -5
- wbportfolio/viewsets/configs/display/assets.py +6 -19
- wbportfolio/viewsets/configs/display/fees.py +3 -3
- wbportfolio/viewsets/configs/display/portfolios.py +5 -5
- wbportfolio/viewsets/configs/display/products.py +1 -1
- wbportfolio/viewsets/configs/display/rebalancing.py +2 -2
- wbportfolio/viewsets/configs/display/reconciliations.py +4 -4
- wbportfolio/viewsets/configs/display/trades.py +1 -189
- wbportfolio/viewsets/configs/endpoints/__init__.py +3 -7
- wbportfolio/viewsets/configs/endpoints/fees.py +2 -2
- wbportfolio/viewsets/configs/endpoints/trades.py +0 -41
- wbportfolio/viewsets/configs/menu/__init__.py +1 -1
- wbportfolio/viewsets/configs/menu/orders.py +11 -0
- wbportfolio/viewsets/configs/titles/__init__.py +2 -3
- wbportfolio/viewsets/configs/titles/fees.py +4 -8
- wbportfolio/viewsets/esg.py +3 -5
- wbportfolio/viewsets/mixins.py +5 -1
- wbportfolio/viewsets/orders/__init__.py +6 -0
- wbportfolio/viewsets/orders/configs/__init__.py +4 -0
- wbportfolio/viewsets/orders/configs/buttons/__init__.py +2 -0
- wbportfolio/viewsets/orders/configs/buttons/order_proposals.py +188 -0
- wbportfolio/viewsets/orders/configs/buttons/orders.py +113 -0
- wbportfolio/viewsets/orders/configs/displays/__init__.py +2 -0
- wbportfolio/viewsets/orders/configs/displays/order_proposals.py +157 -0
- wbportfolio/viewsets/orders/configs/displays/orders.py +232 -0
- wbportfolio/viewsets/orders/configs/endpoints/__init__.py +2 -0
- wbportfolio/viewsets/orders/configs/endpoints/order_proposals.py +21 -0
- wbportfolio/viewsets/orders/configs/endpoints/orders.py +28 -0
- wbportfolio/viewsets/orders/configs/titles/__init__.py +0 -0
- wbportfolio/viewsets/orders/configs/titles/orders.py +0 -0
- wbportfolio/viewsets/orders/order_proposals.py +252 -0
- wbportfolio/viewsets/orders/orders.py +277 -0
- wbportfolio/viewsets/portfolios.py +36 -12
- wbportfolio/viewsets/positions.py +3 -2
- wbportfolio/viewsets/products.py +6 -6
- wbportfolio/viewsets/{transactions/rebalancing.py → rebalancing.py} +2 -2
- wbportfolio/viewsets/transactions/__init__.py +3 -14
- wbportfolio/viewsets/transactions/fees.py +22 -22
- wbportfolio/viewsets/transactions/trades.py +1 -180
- {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/METADATA +3 -1
- {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/RECORD +252 -203
- {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/WHEEL +1 -1
- wbportfolio/admin/transactions/transactions.py +0 -38
- wbportfolio/factories/transactions.py +0 -22
- wbportfolio/fdm/tasks.py +0 -13
- wbportfolio/filters/transactions/transactions.py +0 -99
- wbportfolio/models/transactions/expiry.py +0 -7
- wbportfolio/models/transactions/trade_proposals.py +0 -704
- wbportfolio/pms/trading/handler.py +0 -161
- wbportfolio/serializers/transactions/expiry.py +0 -18
- wbportfolio/serializers/transactions/trade_proposals.py +0 -76
- wbportfolio/serializers/transactions/transactions.py +0 -85
- wbportfolio/tests/models/transactions/test_trade_proposals.py +0 -410
- wbportfolio/viewsets/configs/buttons/trade_proposals.py +0 -66
- wbportfolio/viewsets/configs/display/trade_proposals.py +0 -100
- wbportfolio/viewsets/configs/display/transactions.py +0 -55
- wbportfolio/viewsets/configs/endpoints/trade_proposals.py +0 -18
- wbportfolio/viewsets/configs/endpoints/transactions.py +0 -14
- wbportfolio/viewsets/configs/menu/transactions.py +0 -9
- wbportfolio/viewsets/configs/titles/transactions.py +0 -9
- wbportfolio/viewsets/signals.py +0 -43
- wbportfolio/viewsets/transactions/trade_proposals.py +0 -139
- wbportfolio/viewsets/transactions/transactions.py +0 -122
- /wbportfolio/{fdm → api_clients}/__init__.py +0 -0
- {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
from datetime import date
|
|
2
|
-
from decimal import Decimal
|
|
3
|
-
|
|
4
|
-
from django.core.exceptions import ValidationError
|
|
5
|
-
|
|
6
|
-
from wbportfolio.pms.typing import Portfolio, Trade, TradeBatch
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class TradingService:
|
|
10
|
-
"""
|
|
11
|
-
This class represents the trading service. It can be instantiated either with the target portfolio and the effective portfolio or given a direct list of trade
|
|
12
|
-
In any case, it will compute all three states
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
def __init__(
|
|
16
|
-
self,
|
|
17
|
-
trade_date: date,
|
|
18
|
-
effective_portfolio: Portfolio | None = None,
|
|
19
|
-
target_portfolio: Portfolio | None = None,
|
|
20
|
-
trades_batch: TradeBatch | None = None,
|
|
21
|
-
total_value: Decimal = None,
|
|
22
|
-
):
|
|
23
|
-
self.total_value = total_value
|
|
24
|
-
self.trade_date = trade_date
|
|
25
|
-
if target_portfolio is None:
|
|
26
|
-
target_portfolio = Portfolio(positions=())
|
|
27
|
-
if effective_portfolio is None:
|
|
28
|
-
effective_portfolio = Portfolio(positions=())
|
|
29
|
-
# If effective portfoolio and trades batch is provided, we ensure the trade batch contains at least one trade for every position
|
|
30
|
-
trades_batch = self.build_trade_batch(effective_portfolio, target_portfolio, trades_batch=trades_batch)
|
|
31
|
-
# if no trade but a effective portfolio is provided, we get the trade batch only from the effective portofolio (and the target portfolio if provided, but optional. Without it, the trade delta weight will be 0 )
|
|
32
|
-
# Finally, we compute the target portfolio
|
|
33
|
-
if trades_batch and not target_portfolio:
|
|
34
|
-
target_portfolio = trades_batch.convert_to_portfolio()
|
|
35
|
-
|
|
36
|
-
self.trades_batch = trades_batch
|
|
37
|
-
self.effective_portfolio = effective_portfolio
|
|
38
|
-
self.target_portfolio = target_portfolio
|
|
39
|
-
|
|
40
|
-
@property
|
|
41
|
-
def errors(self) -> list[str]:
|
|
42
|
-
"""
|
|
43
|
-
Returned the list of errors stored during the validation process. Can only be called after is_valid
|
|
44
|
-
"""
|
|
45
|
-
if not hasattr(self, "_errors"):
|
|
46
|
-
msg = "You must call `.is_valid()` before accessing `.errors`."
|
|
47
|
-
raise AssertionError(msg)
|
|
48
|
-
return self._errors
|
|
49
|
-
|
|
50
|
-
@property
|
|
51
|
-
def validated_trades(self) -> list[Trade]:
|
|
52
|
-
"""
|
|
53
|
-
Returned the list of validated trade stored during the validation process. Can only be called after is_valid
|
|
54
|
-
"""
|
|
55
|
-
if not hasattr(self, "_validated_trades"):
|
|
56
|
-
msg = "You must call `.is_valid()` before accessing `.validated_trades`."
|
|
57
|
-
raise AssertionError(msg)
|
|
58
|
-
return self._validated_trades
|
|
59
|
-
|
|
60
|
-
def run_validation(self, validated_trades: list[Trade]):
|
|
61
|
-
"""
|
|
62
|
-
Test the given value against all the validators on the field,
|
|
63
|
-
and either raise a `ValidationError` or simply return.
|
|
64
|
-
"""
|
|
65
|
-
# TradeBatch(validated_trades).validate()
|
|
66
|
-
if self.effective_portfolio:
|
|
67
|
-
for trade in validated_trades:
|
|
68
|
-
if (
|
|
69
|
-
trade.effective_weight
|
|
70
|
-
and trade.underlying_instrument not in self.effective_portfolio.positions_map
|
|
71
|
-
):
|
|
72
|
-
raise ValidationError("All effective position needs to be matched with a validated trade")
|
|
73
|
-
|
|
74
|
-
def build_trade_batch(
|
|
75
|
-
self,
|
|
76
|
-
effective_portfolio: Portfolio,
|
|
77
|
-
target_portfolio: Portfolio,
|
|
78
|
-
trades_batch: TradeBatch | None = None,
|
|
79
|
-
) -> TradeBatch:
|
|
80
|
-
"""
|
|
81
|
-
Given combination of effective portfolio and either a trades batch or a target portfolio, ensure all theres variables are set
|
|
82
|
-
|
|
83
|
-
Args:
|
|
84
|
-
effective_portfolio: The effective portfolio
|
|
85
|
-
target_portfolio: The optional target portfolio
|
|
86
|
-
trades_batch: The optional trades batch
|
|
87
|
-
|
|
88
|
-
Returns: The normalized trades batch
|
|
89
|
-
"""
|
|
90
|
-
instruments = list(effective_portfolio.positions_map.keys())
|
|
91
|
-
instruments.extend(list(target_portfolio.positions_map.keys()))
|
|
92
|
-
if trades_batch:
|
|
93
|
-
instruments.extend(list(trades_batch.trades_map.keys()))
|
|
94
|
-
_trades: list[Trade] = []
|
|
95
|
-
for instrument in set(instruments):
|
|
96
|
-
effective_weight = target_weight = 0
|
|
97
|
-
effective_shares = 0
|
|
98
|
-
instrument_type = currency = None
|
|
99
|
-
if effective_pos := effective_portfolio.positions_map.get(instrument, None):
|
|
100
|
-
effective_weight = effective_pos.weighting
|
|
101
|
-
effective_shares = effective_pos.shares
|
|
102
|
-
instrument_type, currency = effective_pos.instrument_type, effective_pos.currency
|
|
103
|
-
if target_pos := target_portfolio.positions_map.get(instrument, None):
|
|
104
|
-
target_weight = target_pos.weighting
|
|
105
|
-
instrument_type, currency = target_pos.instrument_type, target_pos.currency
|
|
106
|
-
if trades_batch and (trade := trades_batch.trades_map.get(instrument, None)):
|
|
107
|
-
effective_weight, target_weight = trade.effective_weight, trade.target_weight
|
|
108
|
-
effective_shares = trade.effective_shares
|
|
109
|
-
instrument_type, currency = trade.instrument_type, trade.currency
|
|
110
|
-
|
|
111
|
-
_trades.append(
|
|
112
|
-
Trade(
|
|
113
|
-
underlying_instrument=instrument,
|
|
114
|
-
effective_weight=effective_weight,
|
|
115
|
-
target_weight=target_weight,
|
|
116
|
-
effective_shares=effective_shares,
|
|
117
|
-
date=self.trade_date,
|
|
118
|
-
instrument_type=instrument_type,
|
|
119
|
-
currency=currency,
|
|
120
|
-
)
|
|
121
|
-
)
|
|
122
|
-
return TradeBatch(tuple(_trades))
|
|
123
|
-
|
|
124
|
-
def is_valid(self, ignore_error: bool = False) -> bool:
|
|
125
|
-
"""
|
|
126
|
-
Validate the trade batch against a set of default rules. Populate the validated_trades and errors property.
|
|
127
|
-
Ignore error by default
|
|
128
|
-
Args:
|
|
129
|
-
ignore_error: If true, will raise the error. False by default
|
|
130
|
-
|
|
131
|
-
Returns: True if the trades batch is valid
|
|
132
|
-
"""
|
|
133
|
-
if not hasattr(self, "_validated_trades"):
|
|
134
|
-
self._validated_trades = []
|
|
135
|
-
self._errors = []
|
|
136
|
-
# Run validation for every trade. If a trade is not valid, we simply exclude it from the validated trades list
|
|
137
|
-
for _, trade in self.trades_batch.trades_map.items():
|
|
138
|
-
try:
|
|
139
|
-
trade.validate()
|
|
140
|
-
self._validated_trades.append(trade)
|
|
141
|
-
except ValidationError as exc:
|
|
142
|
-
self._errors.append(exc.message)
|
|
143
|
-
try:
|
|
144
|
-
# Check the overall validity of the trade batch. If this fail, we consider all trade invalids
|
|
145
|
-
self.run_validation(self._validated_trades)
|
|
146
|
-
except ValidationError as exc:
|
|
147
|
-
self._validated_trades = []
|
|
148
|
-
self._errors.append(exc.message)
|
|
149
|
-
|
|
150
|
-
if self._errors and not ignore_error:
|
|
151
|
-
raise ValidationError(self.errors)
|
|
152
|
-
|
|
153
|
-
return not bool(self._errors)
|
|
154
|
-
|
|
155
|
-
def normalize(self):
|
|
156
|
-
"""
|
|
157
|
-
Normalize the instantiate trades batch so that the target weight is 100%
|
|
158
|
-
"""
|
|
159
|
-
self.trades_batch = TradeBatch(
|
|
160
|
-
[trade.normalize_target(self.trades_batch.total_target_weight) for trade in self.trades_batch.trades]
|
|
161
|
-
)
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
from wbportfolio.models import Expiry
|
|
2
|
-
|
|
3
|
-
from .transactions import (
|
|
4
|
-
TransactionModelSerializer,
|
|
5
|
-
TransactionRepresentationSerializer,
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class ExpiryRepresentationSerializer(TransactionRepresentationSerializer):
|
|
10
|
-
class Meta:
|
|
11
|
-
model = Expiry
|
|
12
|
-
fields = TransactionRepresentationSerializer.Meta.fields
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class ExpiryModelSerializer(TransactionModelSerializer):
|
|
16
|
-
class Meta:
|
|
17
|
-
model = Expiry
|
|
18
|
-
fields = TransactionModelSerializer.Meta.fields
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
from django.contrib.messages import warning
|
|
2
|
-
from django.core.exceptions import ValidationError
|
|
3
|
-
from rest_framework.reverse import reverse
|
|
4
|
-
from wbcore import serializers as wb_serializers
|
|
5
|
-
from wbcore.serializers import DefaultFromView
|
|
6
|
-
|
|
7
|
-
from wbportfolio.models import Portfolio, RebalancingModel, TradeProposal
|
|
8
|
-
|
|
9
|
-
from .. import PortfolioRepresentationSerializer, RebalancingModelRepresentationSerializer
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TradeProposalModelSerializer(wb_serializers.ModelSerializer):
|
|
13
|
-
rebalancing_model = wb_serializers.PrimaryKeyRelatedField(queryset=RebalancingModel.objects.all(), required=False)
|
|
14
|
-
_rebalancing_model = RebalancingModelRepresentationSerializer(source="rebalancing_model")
|
|
15
|
-
target_portfolio = wb_serializers.PrimaryKeyRelatedField(
|
|
16
|
-
queryset=Portfolio.objects.all(), write_only=True, required=False, default=DefaultFromView("portfolio")
|
|
17
|
-
)
|
|
18
|
-
_target_portfolio = PortfolioRepresentationSerializer(source="target_portfolio")
|
|
19
|
-
|
|
20
|
-
trade_date = wb_serializers.DateField(
|
|
21
|
-
read_only=lambda view: not view.new_mode, default=DefaultFromView("default_trade_date")
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
def create(self, validated_data):
|
|
25
|
-
target_portfolio = validated_data.pop("target_portfolio", None)
|
|
26
|
-
rebalancing_model = validated_data.get("rebalancing_model", None)
|
|
27
|
-
if request := self.context.get("request"):
|
|
28
|
-
validated_data["creator"] = request.user.profile
|
|
29
|
-
obj = super().create(validated_data)
|
|
30
|
-
|
|
31
|
-
target_portfolio_dto = None
|
|
32
|
-
if target_portfolio and not rebalancing_model and (last_effective_date := obj.last_effective_date):
|
|
33
|
-
target_portfolio_dto = target_portfolio._build_dto(last_effective_date)
|
|
34
|
-
try:
|
|
35
|
-
obj.reset_trades(target_portfolio=target_portfolio_dto)
|
|
36
|
-
except ValidationError as e:
|
|
37
|
-
if request := self.context.get("request"):
|
|
38
|
-
warning(request, str(e), extra_tags="auto_close=0")
|
|
39
|
-
return obj
|
|
40
|
-
|
|
41
|
-
@wb_serializers.register_only_instance_resource()
|
|
42
|
-
def additional_resources(self, instance, request, user, **kwargs):
|
|
43
|
-
res = {}
|
|
44
|
-
if instance.status == TradeProposal.Status.APPROVED:
|
|
45
|
-
res["replay"] = reverse("wbportfolio:tradeproposal-replay", args=[instance.id], request=request)
|
|
46
|
-
if instance.status == TradeProposal.Status.DRAFT:
|
|
47
|
-
res["reset"] = reverse("wbportfolio:tradeproposal-reset", args=[instance.id], request=request)
|
|
48
|
-
res["normalize"] = reverse("wbportfolio:tradeproposal-normalize", args=[instance.id], request=request)
|
|
49
|
-
res["deleteall"] = reverse("wbportfolio:tradeproposal-deleteall", args=[instance.id], request=request)
|
|
50
|
-
res["trades"] = reverse(
|
|
51
|
-
"wbportfolio:tradeproposal-trade-list",
|
|
52
|
-
args=[instance.id],
|
|
53
|
-
request=request,
|
|
54
|
-
)
|
|
55
|
-
return res
|
|
56
|
-
|
|
57
|
-
class Meta:
|
|
58
|
-
model = TradeProposal
|
|
59
|
-
only_fsm_transition_on_instance = True
|
|
60
|
-
fields = (
|
|
61
|
-
"id",
|
|
62
|
-
"trade_date",
|
|
63
|
-
"comment",
|
|
64
|
-
"status",
|
|
65
|
-
"portfolio",
|
|
66
|
-
"_rebalancing_model",
|
|
67
|
-
"rebalancing_model",
|
|
68
|
-
"target_portfolio",
|
|
69
|
-
"_target_portfolio",
|
|
70
|
-
"_additional_resources",
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
class ReadOnlyTradeProposalModelSerializer(TradeProposalModelSerializer):
|
|
75
|
-
class Meta(TradeProposalModelSerializer.Meta):
|
|
76
|
-
read_only_fields = TradeProposalModelSerializer.Meta.fields
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
from wbcore import serializers as wb_serializers
|
|
2
|
-
from wbcore.contrib.currency.serializers import CurrencyRepresentationSerializer
|
|
3
|
-
from wbfdm.serializers import InvestableUniverseRepresentationSerializer
|
|
4
|
-
|
|
5
|
-
from wbportfolio.models import Transaction
|
|
6
|
-
from wbportfolio.serializers.portfolios import PortfolioRepresentationSerializer
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class TransactionRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
|
10
|
-
_detail = wb_serializers.HyperlinkField(reverse_name="wbportfolio:transaction-detail")
|
|
11
|
-
|
|
12
|
-
class Meta:
|
|
13
|
-
model = Transaction
|
|
14
|
-
fields = ("id", "transaction_date", "total_value", "_detail")
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class TransactionModelSerializer(wb_serializers.ModelSerializer):
|
|
18
|
-
external_id = wb_serializers.CharField(required=False, read_only=True)
|
|
19
|
-
value_date = wb_serializers.DateField(required=False, read_only=True)
|
|
20
|
-
total_value_usd = wb_serializers.FloatField(default=0, read_only=True, label="Total Value ($)")
|
|
21
|
-
total_value_gross_usd = wb_serializers.FloatField(default=0, read_only=True, label="Total Value Gross ($)")
|
|
22
|
-
transaction_underlying_type = wb_serializers.CharField(read_only=True)
|
|
23
|
-
transaction_url_type = wb_serializers.SerializerMethodField()
|
|
24
|
-
_portfolio = PortfolioRepresentationSerializer(source="portfolio")
|
|
25
|
-
_underlying_instrument = InvestableUniverseRepresentationSerializer(source="underlying_instrument")
|
|
26
|
-
_currency = CurrencyRepresentationSerializer(source="currency")
|
|
27
|
-
|
|
28
|
-
total_value = wb_serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
|
|
29
|
-
total_value_fx_portfolio = wb_serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
|
|
30
|
-
total_value_gross = wb_serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
|
|
31
|
-
total_value_gross_fx_portfolio = wb_serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True)
|
|
32
|
-
|
|
33
|
-
def get_transaction_url_type(self, obj):
|
|
34
|
-
return obj.transaction_type.lower()
|
|
35
|
-
|
|
36
|
-
# def get_transaction_underlying_type(self, obj):
|
|
37
|
-
# try:
|
|
38
|
-
# casted_transaction = obj.get_casted_transaction()
|
|
39
|
-
# return casted_transaction.Type[casted_transaction.transaction_subtype].label
|
|
40
|
-
# except Exception as e:
|
|
41
|
-
# return ""
|
|
42
|
-
class Meta:
|
|
43
|
-
model = Transaction
|
|
44
|
-
decorators = {
|
|
45
|
-
"total_value": wb_serializers.decorator(
|
|
46
|
-
decorator_type="text", position="left", value="{{_currency.symbol}}"
|
|
47
|
-
),
|
|
48
|
-
"total_value_gross": wb_serializers.decorator(
|
|
49
|
-
decorator_type="text", position="left", value="{{_currency.symbol}}"
|
|
50
|
-
),
|
|
51
|
-
"total_value_usd": wb_serializers.decorator(decorator_type="text", position="left", value="{{$}}"),
|
|
52
|
-
"total_value_gross_usd": wb_serializers.decorator(decorator_type="text", position="left", value="{{$}}"),
|
|
53
|
-
"total_value_fx_portfolio": wb_serializers.decorator(
|
|
54
|
-
position="left", value="{{_portfolio.currency_symbol}}"
|
|
55
|
-
),
|
|
56
|
-
"total_value_gross_fx_portfolio": wb_serializers.decorator(
|
|
57
|
-
position="left", value="{{_portfolio.currency_symbol}}"
|
|
58
|
-
),
|
|
59
|
-
# "total_value_fx_portfolio": wb_serializers.decorator(decorator_type="text", position="left", value="{{_portfolio.currency_symbol}}}"),
|
|
60
|
-
# "total_value_gross_fx_portfolio": wb_serializers.decorator(decorator_type="text", position="left", value="{{_portfolio.currency_symbol}}"),
|
|
61
|
-
}
|
|
62
|
-
fields = (
|
|
63
|
-
"id",
|
|
64
|
-
"transaction_type",
|
|
65
|
-
"transaction_url_type",
|
|
66
|
-
"transaction_underlying_type",
|
|
67
|
-
"portfolio",
|
|
68
|
-
"_portfolio",
|
|
69
|
-
"underlying_instrument",
|
|
70
|
-
"_underlying_instrument",
|
|
71
|
-
"transaction_date",
|
|
72
|
-
"book_date",
|
|
73
|
-
"value_date",
|
|
74
|
-
"currency",
|
|
75
|
-
"_currency",
|
|
76
|
-
"currency_fx_rate",
|
|
77
|
-
"total_value",
|
|
78
|
-
"total_value_fx_portfolio",
|
|
79
|
-
"total_value_gross",
|
|
80
|
-
"total_value_gross_fx_portfolio",
|
|
81
|
-
"external_id",
|
|
82
|
-
"comment",
|
|
83
|
-
"total_value_usd",
|
|
84
|
-
"total_value_gross_usd",
|
|
85
|
-
)
|