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
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from django.shortcuts import get_object_or_404
|
|
4
|
+
from django.utils.translation import gettext_lazy as _
|
|
5
|
+
from wbcore.contrib.color.enums import WBColor
|
|
6
|
+
from wbcore.enums import Unit
|
|
7
|
+
from wbcore.metadata.configs import display as dp
|
|
8
|
+
from wbcore.metadata.configs.display.instance_display.shortcuts import (
|
|
9
|
+
Display,
|
|
10
|
+
create_simple_display,
|
|
11
|
+
)
|
|
12
|
+
from wbcore.metadata.configs.display.instance_display.utils import repeat_field
|
|
13
|
+
from wbcore.metadata.configs.display.view_config import DisplayViewConfig
|
|
14
|
+
|
|
15
|
+
from wbportfolio.models import Order, OrderProposal
|
|
16
|
+
|
|
17
|
+
ORDER_STATUS_LEGENDS = dp.Legend(
|
|
18
|
+
key="has_warnings",
|
|
19
|
+
items=[
|
|
20
|
+
dp.LegendItem(icon=WBColor.YELLOW_DARK.value, label=_("Warning"), value=True),
|
|
21
|
+
],
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
ORDER_STATUS_FORMATTING = dp.Formatting(
|
|
25
|
+
column="has_warnings",
|
|
26
|
+
formatting_rules=[
|
|
27
|
+
dp.FormattingRule(
|
|
28
|
+
style={"backgroundColor": WBColor.YELLOW_DARK.value},
|
|
29
|
+
condition=("==", True),
|
|
30
|
+
)
|
|
31
|
+
],
|
|
32
|
+
)
|
|
33
|
+
ORDER_TYPE_FORMATTING_RULES = [
|
|
34
|
+
dp.FormattingRule(
|
|
35
|
+
style={"color": WBColor.RED_DARK.value, "fontWeight": "bold"},
|
|
36
|
+
condition=("==", Order.Type.SELL.name),
|
|
37
|
+
),
|
|
38
|
+
dp.FormattingRule(
|
|
39
|
+
style={"color": WBColor.RED.value, "fontWeight": "bold"},
|
|
40
|
+
condition=("==", Order.Type.DECREASE.name),
|
|
41
|
+
),
|
|
42
|
+
dp.FormattingRule(
|
|
43
|
+
style={"color": WBColor.GREEN.value, "fontWeight": "bold"},
|
|
44
|
+
condition=("==", Order.Type.INCREASE.name),
|
|
45
|
+
),
|
|
46
|
+
dp.FormattingRule(
|
|
47
|
+
style={"color": WBColor.GREEN_DARK.value, "fontWeight": "bold"},
|
|
48
|
+
condition=("==", Order.Type.BUY.name),
|
|
49
|
+
),
|
|
50
|
+
dp.FormattingRule(
|
|
51
|
+
style={"color": WBColor.GREY.value, "fontWeight": "bold"},
|
|
52
|
+
condition=("==", Order.Type.NO_CHANGE.name),
|
|
53
|
+
),
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
VALUE_FORMATTING_RULES = [
|
|
57
|
+
dp.FormattingRule(
|
|
58
|
+
style={"color": WBColor.RED_DARK.value, "fontWeight": "bold"},
|
|
59
|
+
condition=("<", 0),
|
|
60
|
+
),
|
|
61
|
+
dp.FormattingRule(
|
|
62
|
+
style={"color": WBColor.GREEN_DARK.value, "fontWeight": "bold"},
|
|
63
|
+
condition=(">", 0),
|
|
64
|
+
),
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class OrderOrderProposalDisplayConfig(DisplayViewConfig):
|
|
69
|
+
def get_list_display(self) -> Optional[dp.ListDisplay]:
|
|
70
|
+
order_proposal = get_object_or_404(OrderProposal, pk=self.view.kwargs.get("order_proposal_id", None))
|
|
71
|
+
fields = [
|
|
72
|
+
dp.Field(
|
|
73
|
+
label="Instrument",
|
|
74
|
+
open_by_default=False,
|
|
75
|
+
key=None,
|
|
76
|
+
children=[
|
|
77
|
+
dp.Field(key="underlying_instrument", label="Name", width=Unit.PIXEL(250)),
|
|
78
|
+
dp.Field(key="underlying_instrument_isin", label="ISIN", width=Unit.PIXEL(125)),
|
|
79
|
+
dp.Field(key="underlying_instrument_ticker", label="Ticker", width=Unit.PIXEL(100), show="open"),
|
|
80
|
+
dp.Field(
|
|
81
|
+
key="underlying_instrument_refinitiv_identifier_code",
|
|
82
|
+
label="RIC",
|
|
83
|
+
width=Unit.PIXEL(100),
|
|
84
|
+
show="open",
|
|
85
|
+
),
|
|
86
|
+
dp.Field(
|
|
87
|
+
key="underlying_instrument_instrument_type",
|
|
88
|
+
label="Asset Class",
|
|
89
|
+
width=Unit.PIXEL(125),
|
|
90
|
+
show="open",
|
|
91
|
+
),
|
|
92
|
+
dp.Field(
|
|
93
|
+
key="underlying_instrument_exchange", label="Exchange", width=Unit.PIXEL(125), show="open"
|
|
94
|
+
),
|
|
95
|
+
],
|
|
96
|
+
),
|
|
97
|
+
dp.Field(
|
|
98
|
+
label="Weight",
|
|
99
|
+
open_by_default=False,
|
|
100
|
+
key=None,
|
|
101
|
+
children=[
|
|
102
|
+
dp.Field(key="effective_weight", label="Effective Weight", show="open", width=Unit.PIXEL(150)),
|
|
103
|
+
dp.Field(key="target_weight", label="Target Weight", show="open", width=Unit.PIXEL(150)),
|
|
104
|
+
dp.Field(
|
|
105
|
+
key="weighting",
|
|
106
|
+
label="Delta Weight",
|
|
107
|
+
formatting_rules=VALUE_FORMATTING_RULES,
|
|
108
|
+
width=Unit.PIXEL(150),
|
|
109
|
+
),
|
|
110
|
+
],
|
|
111
|
+
),
|
|
112
|
+
]
|
|
113
|
+
if not order_proposal.portfolio.only_weighting:
|
|
114
|
+
fields.append(
|
|
115
|
+
dp.Field(
|
|
116
|
+
label="Shares",
|
|
117
|
+
open_by_default=False,
|
|
118
|
+
key=None,
|
|
119
|
+
children=[
|
|
120
|
+
dp.Field(key="effective_shares", label="Effective Shares", show="open", width=Unit.PIXEL(150)),
|
|
121
|
+
dp.Field(key="target_shares", label="Target Shares", show="open", width=Unit.PIXEL(150)),
|
|
122
|
+
dp.Field(
|
|
123
|
+
key="shares",
|
|
124
|
+
label="Shares",
|
|
125
|
+
formatting_rules=VALUE_FORMATTING_RULES,
|
|
126
|
+
width=Unit.PIXEL(150),
|
|
127
|
+
),
|
|
128
|
+
],
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
fields.append(
|
|
132
|
+
dp.Field(
|
|
133
|
+
label="Total Value",
|
|
134
|
+
open_by_default=False,
|
|
135
|
+
key=None,
|
|
136
|
+
children=[
|
|
137
|
+
dp.Field(
|
|
138
|
+
key="effective_total_value_fx_portfolio",
|
|
139
|
+
label="Effective Total Value",
|
|
140
|
+
show="open",
|
|
141
|
+
width=Unit.PIXEL(150),
|
|
142
|
+
),
|
|
143
|
+
dp.Field(
|
|
144
|
+
key="target_total_value_fx_portfolio",
|
|
145
|
+
label="Target Total Value",
|
|
146
|
+
show="open",
|
|
147
|
+
width=Unit.PIXEL(150),
|
|
148
|
+
),
|
|
149
|
+
dp.Field(
|
|
150
|
+
key="total_value_fx_portfolio",
|
|
151
|
+
label="Total Value",
|
|
152
|
+
formatting_rules=VALUE_FORMATTING_RULES,
|
|
153
|
+
width=Unit.PIXEL(150),
|
|
154
|
+
),
|
|
155
|
+
],
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
fields.append(
|
|
159
|
+
dp.Field(
|
|
160
|
+
label="Information",
|
|
161
|
+
open_by_default=False,
|
|
162
|
+
key=None,
|
|
163
|
+
children=[
|
|
164
|
+
dp.Field(
|
|
165
|
+
key="order_type",
|
|
166
|
+
label="Direction",
|
|
167
|
+
formatting_rules=ORDER_TYPE_FORMATTING_RULES,
|
|
168
|
+
width=Unit.PIXEL(125),
|
|
169
|
+
),
|
|
170
|
+
dp.Field(
|
|
171
|
+
key="desired_target_weight", label="Desired Target Weight", show="open", width=Unit.PIXEL(100)
|
|
172
|
+
),
|
|
173
|
+
dp.Field(key="daily_return", label="Daily Return", show="open", width=Unit.PIXEL(100)),
|
|
174
|
+
dp.Field(key="currency_fx_rate", label="FX Rate", show="open", width=Unit.PIXEL(100)),
|
|
175
|
+
dp.Field(key="price", label="Price", show="open", width=Unit.PIXEL(100)),
|
|
176
|
+
dp.Field(key="order", label="Order", show="open", width=Unit.PIXEL(50)),
|
|
177
|
+
dp.Field(key="comment", label="Comment", show="open", width=Unit.PIXEL(250)),
|
|
178
|
+
],
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
execution_fields = [
|
|
182
|
+
dp.Field(
|
|
183
|
+
label="Instruction",
|
|
184
|
+
open_by_default=False,
|
|
185
|
+
key=None,
|
|
186
|
+
children=[
|
|
187
|
+
dp.Field(key="execution_instruction", label="Type", width=Unit.PIXEL(125)),
|
|
188
|
+
dp.Field(
|
|
189
|
+
key="execution_instruction_parameters_repr",
|
|
190
|
+
label="Parameters",
|
|
191
|
+
width=Unit.PIXEL(125),
|
|
192
|
+
show="open",
|
|
193
|
+
),
|
|
194
|
+
],
|
|
195
|
+
)
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
if order_proposal.execution_status:
|
|
199
|
+
execution_fields.extend(
|
|
200
|
+
[
|
|
201
|
+
dp.Field(key="execution_status", label="Status", width=Unit.PIXEL(100)),
|
|
202
|
+
dp.Field(key="execution_comment", label="Comment", width=Unit.PIXEL(150), show="open"),
|
|
203
|
+
dp.Field(
|
|
204
|
+
label="Trade",
|
|
205
|
+
open_by_default=False,
|
|
206
|
+
key=None,
|
|
207
|
+
children=[
|
|
208
|
+
dp.Field(key="execution_date", label="Date", width=Unit.PIXEL(100), show="open"),
|
|
209
|
+
dp.Field(key="execution_price", label="Price", width=Unit.PIXEL(100), show="open"),
|
|
210
|
+
dp.Field(key="execution_traded_shares", label="Shares", width=Unit.PIXEL(100)),
|
|
211
|
+
],
|
|
212
|
+
),
|
|
213
|
+
]
|
|
214
|
+
)
|
|
215
|
+
fields.append(dp.Field(label="Execution", open_by_default=False, key=None, children=execution_fields))
|
|
216
|
+
return dp.ListDisplay(
|
|
217
|
+
fields=fields,
|
|
218
|
+
legends=[ORDER_STATUS_LEGENDS],
|
|
219
|
+
formatting=[ORDER_STATUS_FORMATTING],
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def get_instance_display(self) -> Display:
|
|
223
|
+
order_proposal = get_object_or_404(OrderProposal, pk=self.view.kwargs.get("order_proposal_id", None))
|
|
224
|
+
|
|
225
|
+
fields = [
|
|
226
|
+
["company", "security", "underlying_instrument"],
|
|
227
|
+
["effective_weight", "target_weight", "weighting"],
|
|
228
|
+
]
|
|
229
|
+
if not order_proposal.portfolio.only_weighting:
|
|
230
|
+
fields.append(["effective_shares", "target_shares", "shares"])
|
|
231
|
+
fields.append([repeat_field(3, "comment")])
|
|
232
|
+
return create_simple_display(fields)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from django.shortcuts import get_object_or_404
|
|
2
|
+
from rest_framework.reverse import reverse
|
|
3
|
+
from wbcore.metadata.configs.endpoints import EndpointViewConfig
|
|
4
|
+
|
|
5
|
+
from wbportfolio.models import OrderProposal
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OrderProposalEndpointConfig(EndpointViewConfig):
|
|
9
|
+
def get_delete_endpoint(self, **kwargs):
|
|
10
|
+
if pk := self.view.kwargs.get("pk", None):
|
|
11
|
+
order_proposal = get_object_or_404(OrderProposal, pk=pk)
|
|
12
|
+
if order_proposal.status in [OrderProposal.Status.DRAFT, OrderProposal.Status.DENIED]:
|
|
13
|
+
return super().get_endpoint()
|
|
14
|
+
return None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class OrderProposalPortfolioEndpointConfig(OrderProposalEndpointConfig):
|
|
18
|
+
def get_endpoint(self, **kwargs):
|
|
19
|
+
return reverse(
|
|
20
|
+
"wbportfolio:portfolio-orderproposal-list", args=[self.view.kwargs["portfolio_id"]], request=self.request
|
|
21
|
+
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from rest_framework.reverse import reverse
|
|
2
|
+
from wbcore.metadata.configs.endpoints import EndpointViewConfig
|
|
3
|
+
|
|
4
|
+
from wbportfolio.models import OrderProposal
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class OrderOrderProposalEndpointConfig(EndpointViewConfig):
|
|
8
|
+
def get_endpoint(self, **kwargs):
|
|
9
|
+
if order_proposal_id := self.view.kwargs.get("order_proposal_id", None):
|
|
10
|
+
order_proposal = OrderProposal.objects.get(id=order_proposal_id)
|
|
11
|
+
if order_proposal.status == OrderProposal.Status.DRAFT:
|
|
12
|
+
return reverse(
|
|
13
|
+
"wbportfolio:orderproposal-order-list",
|
|
14
|
+
args=[self.view.kwargs["order_proposal_id"]],
|
|
15
|
+
request=self.request,
|
|
16
|
+
)
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
def get_update_endpoint(self, **kwargs):
|
|
20
|
+
if order_proposal_id := self.view.kwargs.get("order_proposal_id", None):
|
|
21
|
+
order_proposal = OrderProposal.objects.get(id=order_proposal_id)
|
|
22
|
+
if order_proposal.status == OrderProposal.Status.DRAFT or order_proposal.can_be_confirmed:
|
|
23
|
+
return reverse(
|
|
24
|
+
"wbportfolio:orderproposal-order-list",
|
|
25
|
+
args=[self.view.kwargs["order_proposal_id"]],
|
|
26
|
+
request=self.request,
|
|
27
|
+
)
|
|
28
|
+
return None
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
from datetime import date
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
|
|
5
|
+
from django.contrib.messages import error, warning
|
|
6
|
+
from django.shortcuts import get_object_or_404
|
|
7
|
+
from django.utils.functional import cached_property
|
|
8
|
+
from pandas._libs.tslibs.offsets import BDay
|
|
9
|
+
from rest_framework import status
|
|
10
|
+
from rest_framework.decorators import action
|
|
11
|
+
from rest_framework.response import Response
|
|
12
|
+
from wbcompliance.viewsets.risk_management.mixins import RiskCheckViewSetMixin
|
|
13
|
+
from wbcore import serializers as wb_serializers
|
|
14
|
+
from wbcore import viewsets
|
|
15
|
+
from wbcore.metadata.configs.display.instance_display import (
|
|
16
|
+
Display,
|
|
17
|
+
create_simple_display,
|
|
18
|
+
)
|
|
19
|
+
from wbcore.permissions.permissions import InternalUserPermissionMixin
|
|
20
|
+
from wbcore.utils.views import CloneMixin
|
|
21
|
+
|
|
22
|
+
from wbportfolio.models import AssetPosition, Order, OrderProposal
|
|
23
|
+
from wbportfolio.models.orders.order_proposals import (
|
|
24
|
+
execute_orders_as_task,
|
|
25
|
+
push_model_change_as_task,
|
|
26
|
+
replay_as_task,
|
|
27
|
+
)
|
|
28
|
+
from wbportfolio.serializers import (
|
|
29
|
+
OrderProposalModelSerializer,
|
|
30
|
+
OrderProposalRepresentationSerializer,
|
|
31
|
+
ReadOnlyOrderProposalModelSerializer,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
from ...filters.orders import OrderProposalFilterSet
|
|
35
|
+
from ...order_routing import ExecutionStatus, RoutingException
|
|
36
|
+
from ...permissions import IsPortfolioManager
|
|
37
|
+
from ..mixins import UserPortfolioRequestPermissionMixin
|
|
38
|
+
from .configs import (
|
|
39
|
+
OrderProposalButtonConfig,
|
|
40
|
+
OrderProposalDisplayConfig,
|
|
41
|
+
OrderProposalEndpointConfig,
|
|
42
|
+
OrderProposalPortfolioEndpointConfig,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class OrderProposalRepresentationViewSet(InternalUserPermissionMixin, viewsets.RepresentationViewSet):
|
|
47
|
+
IDENTIFIER = "wbportfolio:trade"
|
|
48
|
+
queryset = OrderProposal.objects.all()
|
|
49
|
+
serializer_class = OrderProposalRepresentationSerializer
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class OrderProposalModelViewSet(CloneMixin, RiskCheckViewSetMixin, InternalUserPermissionMixin, viewsets.ModelViewSet):
|
|
53
|
+
IDENTIFIER = "wbportfolio:order"
|
|
54
|
+
ordering_fields = ("trade_date",)
|
|
55
|
+
ordering = ("-trade_date",)
|
|
56
|
+
search_fields = ("comment",)
|
|
57
|
+
filterset_fields = {"trade_date": ["exact", "gte", "lte"], "status": ["exact"]}
|
|
58
|
+
|
|
59
|
+
queryset = OrderProposal.objects.select_related("rebalancing_model", "portfolio")
|
|
60
|
+
serializer_class = OrderProposalModelSerializer
|
|
61
|
+
filterset_class = OrderProposalFilterSet
|
|
62
|
+
display_config_class = OrderProposalDisplayConfig
|
|
63
|
+
button_config_class = OrderProposalButtonConfig
|
|
64
|
+
endpoint_config_class = OrderProposalEndpointConfig
|
|
65
|
+
|
|
66
|
+
def get_serializer_class(self):
|
|
67
|
+
if self.new_mode or (
|
|
68
|
+
"pk" in self.kwargs and (obj := self.get_object()) and obj.status == OrderProposal.Status.DRAFT
|
|
69
|
+
):
|
|
70
|
+
return OrderProposalModelSerializer
|
|
71
|
+
return ReadOnlyOrderProposalModelSerializer
|
|
72
|
+
|
|
73
|
+
# 2 methods to parametrize the clone button functionality
|
|
74
|
+
def get_clone_button_serializer_class(self, instance):
|
|
75
|
+
class CloneSerializer(wb_serializers.Serializer):
|
|
76
|
+
clone_date = wb_serializers.DateField(
|
|
77
|
+
default=(instance.trade_date + BDay(1)).date(), label="Trade Date"
|
|
78
|
+
) # we need to change the field name from the trade proposa fields, otherwise fontend conflicts
|
|
79
|
+
clone_comment = wb_serializers.TextField(label="Comment")
|
|
80
|
+
|
|
81
|
+
return CloneSerializer
|
|
82
|
+
|
|
83
|
+
def get_clone_button_instance_display(self) -> Display:
|
|
84
|
+
return create_simple_display(
|
|
85
|
+
[
|
|
86
|
+
["clone_comment"],
|
|
87
|
+
["clone_date"],
|
|
88
|
+
]
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def add_messages(self, request, instance: OrderProposal | None = None, **kwargs):
|
|
92
|
+
if instance:
|
|
93
|
+
if instance.status == OrderProposal.Status.PENDING and instance.has_non_successful_checks:
|
|
94
|
+
warning(
|
|
95
|
+
request,
|
|
96
|
+
"This order proposal cannot be approved because there is unsuccessful pre-trade checks. Please rectify accordingly and resubmit a valid order proposal",
|
|
97
|
+
)
|
|
98
|
+
if (
|
|
99
|
+
instance.status == OrderProposal.Status.EXECUTION
|
|
100
|
+
and instance.orders.exclude(shares=0, weighting=0)
|
|
101
|
+
.filter(execution_status=Order.ExecutionStatus.FAILED)
|
|
102
|
+
.exists()
|
|
103
|
+
):
|
|
104
|
+
warning(request, "Some orders failed confirmation. Check the list for further details.")
|
|
105
|
+
if instance.execution_status in [
|
|
106
|
+
ExecutionStatus.REJECTED,
|
|
107
|
+
ExecutionStatus.FAILED,
|
|
108
|
+
ExecutionStatus.UNKNOWN,
|
|
109
|
+
]:
|
|
110
|
+
warning(
|
|
111
|
+
request,
|
|
112
|
+
f"The execution status is {ExecutionStatus[instance.execution_status].label}. Detail: {instance.execution_comment}",
|
|
113
|
+
)
|
|
114
|
+
elif instance.can_be_executed and instance.approver == request.user.profile:
|
|
115
|
+
warning(
|
|
116
|
+
request,
|
|
117
|
+
"As the approver of these orders, you are not authorized to execute them yourself. Please assign execution to another qualified individual.",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def _get_risk_checks_button_title(cls) -> str:
|
|
122
|
+
return "Pre-Trade Checks"
|
|
123
|
+
|
|
124
|
+
@action(detail=True, methods=["PATCH"], permission_classes=[IsPortfolioManager])
|
|
125
|
+
def reset(self, request, pk=None):
|
|
126
|
+
order_proposal = get_object_or_404(OrderProposal, pk=pk)
|
|
127
|
+
use_desired_target_weight = request.GET.get("use_desired_target_weight") == "true"
|
|
128
|
+
if order_proposal.status == OrderProposal.Status.DRAFT:
|
|
129
|
+
order_proposal.orders.all().update(weighting=0)
|
|
130
|
+
order_proposal.reset_orders(use_desired_target_weight=use_desired_target_weight)
|
|
131
|
+
return Response({"send": True})
|
|
132
|
+
return Response({"status": "Order Proposal is not Draft"}, status=status.HTTP_400_BAD_REQUEST)
|
|
133
|
+
|
|
134
|
+
@action(detail=True, methods=["PATCH"], permission_classes=[IsPortfolioManager])
|
|
135
|
+
def normalize(self, request, pk=None):
|
|
136
|
+
order_proposal = get_object_or_404(OrderProposal, pk=pk)
|
|
137
|
+
total_cash_weight = Decimal(request.data.get("total_cash_weight", Decimal("0.0")))
|
|
138
|
+
if order_proposal.status == OrderProposal.Status.DRAFT:
|
|
139
|
+
order_proposal.normalize_orders(total_cash_weight)
|
|
140
|
+
return Response({"send": True})
|
|
141
|
+
return Response({"status": "Order Proposal is not Draft"}, status=status.HTTP_400_BAD_REQUEST)
|
|
142
|
+
|
|
143
|
+
@action(detail=True, methods=["PATCH"], permission_classes=[IsPortfolioManager])
|
|
144
|
+
def replay(self, request, pk=None):
|
|
145
|
+
order_proposal = get_object_or_404(OrderProposal, pk=pk)
|
|
146
|
+
if order_proposal.portfolio.is_manageable:
|
|
147
|
+
replay_as_task.delay(order_proposal.id, user_id=self.request.user.id)
|
|
148
|
+
return Response({"send": True})
|
|
149
|
+
return Response({"status": "Order Proposal is not Draft"}, status=status.HTTP_400_BAD_REQUEST)
|
|
150
|
+
|
|
151
|
+
@action(detail=True, methods=["PATCH"], permission_classes=[IsPortfolioManager])
|
|
152
|
+
def pushmodelchange(self, request, pk=None):
|
|
153
|
+
order_proposal = get_object_or_404(OrderProposal, pk=pk)
|
|
154
|
+
only_for_portfolio_ids = list(
|
|
155
|
+
map(lambda o: int(o), filter(lambda r: r, request.data.get("only_for_portfolio_ids", "").split(",")))
|
|
156
|
+
)
|
|
157
|
+
approve_automatically = request.data.get("approve_automatically") == "true"
|
|
158
|
+
if order_proposal.status == OrderProposal.Status.APPROVED and order_proposal.portfolio.is_model:
|
|
159
|
+
push_model_change_as_task.delay(
|
|
160
|
+
order_proposal.id,
|
|
161
|
+
request.user.id,
|
|
162
|
+
only_for_portfolio_ids=only_for_portfolio_ids,
|
|
163
|
+
approve_automatically=approve_automatically,
|
|
164
|
+
)
|
|
165
|
+
return Response({"send": True})
|
|
166
|
+
return Response(
|
|
167
|
+
{"status": "Order Proposal needs to be approved and linked to be a model portfolio"},
|
|
168
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
@action(detail=True, methods=["PATCH"], permission_classes=[IsPortfolioManager])
|
|
172
|
+
def execute(self, request, pk=None):
|
|
173
|
+
order_proposal = get_object_or_404(OrderProposal, pk=pk)
|
|
174
|
+
if order_proposal.can_execute(request.user):
|
|
175
|
+
prioritize_target_weight = request.data.get("prioritize_target_weight") == "true"
|
|
176
|
+
order_proposal.execution_status = ExecutionStatus.PENDING
|
|
177
|
+
order_proposal.execution_comment = "Waiting for custodian confirmation"
|
|
178
|
+
order_proposal.save()
|
|
179
|
+
execute_orders_as_task.delay(order_proposal.id, prioritize_target_weight=prioritize_target_weight)
|
|
180
|
+
return Response({"send": True})
|
|
181
|
+
return Response({"status": "Order Proposal is not Draft"}, status=status.HTTP_400_BAD_REQUEST)
|
|
182
|
+
|
|
183
|
+
@action(detail=True, methods=["PATCH"], permission_classes=[IsPortfolioManager])
|
|
184
|
+
def cancelexecution(self, request, pk=None):
|
|
185
|
+
order_proposal = get_object_or_404(OrderProposal, pk=pk)
|
|
186
|
+
if order_proposal.execution_status and order_proposal.execution_status != ExecutionStatus.CANCELLED:
|
|
187
|
+
try:
|
|
188
|
+
if not order_proposal.cancel_rebalancing():
|
|
189
|
+
warning(
|
|
190
|
+
request,
|
|
191
|
+
"We could not cancel the rebalancing. It is probably already executed. Please refresh status or check with an administrator.",
|
|
192
|
+
)
|
|
193
|
+
except (RoutingException, ValueError) as e:
|
|
194
|
+
error(request, f"Could not cancel orders proposal {order_proposal}: {str(e)}")
|
|
195
|
+
return Response({"send": True})
|
|
196
|
+
return Response(
|
|
197
|
+
{"status": "Order Proposal is not in an execution phase, therefore, it cannot be cancelled."},
|
|
198
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
@action(detail=True, methods=["PATCH"], permission_classes=[IsPortfolioManager])
|
|
202
|
+
def updateexecutionstatus(self, request, pk=None):
|
|
203
|
+
order_proposal = get_object_or_404(OrderProposal, pk=pk)
|
|
204
|
+
if order_proposal.execution_status:
|
|
205
|
+
try:
|
|
206
|
+
if not order_proposal.custodian_router:
|
|
207
|
+
raise RoutingException(
|
|
208
|
+
"There is no custodian router for this portfolio. Please check with an administrator."
|
|
209
|
+
)
|
|
210
|
+
order_proposal.refresh_execution_status()
|
|
211
|
+
except (RoutingException, ValueError) as e:
|
|
212
|
+
error(request, f"Could not update rebalancing status: {str(e)}")
|
|
213
|
+
return Response(
|
|
214
|
+
{
|
|
215
|
+
"send": True,
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
return Response(
|
|
219
|
+
{"status": "Order Proposal is not in an execution phase, therefore, its status cannot be fetched."},
|
|
220
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
@action(detail=True, methods=["PATCH"], permission_classes=[IsPortfolioManager])
|
|
224
|
+
def refreshreturn(self, request, pk=None):
|
|
225
|
+
order_proposal = get_object_or_404(OrderProposal, pk=pk)
|
|
226
|
+
order_proposal.refresh_returns()
|
|
227
|
+
return Response(
|
|
228
|
+
{"status": "Returns were refreshed with success"},
|
|
229
|
+
status=status.HTTP_200_OK,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
@action(detail=True, methods=["PATCH"], permission_classes=[IsPortfolioManager])
|
|
233
|
+
def refreshpretradechecks(self, request, pk=None):
|
|
234
|
+
order_proposal = get_object_or_404(OrderProposal, pk=pk)
|
|
235
|
+
if order_proposal.status == OrderProposal.Status.DRAFT:
|
|
236
|
+
order_proposal.evaluate_pretrade_checks()
|
|
237
|
+
return Response(
|
|
238
|
+
{"status": "Evaluate pretrade checks"},
|
|
239
|
+
status=status.HTTP_200_OK,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class OrderProposalPortfolioModelViewSet(UserPortfolioRequestPermissionMixin, OrderProposalModelViewSet):
|
|
244
|
+
endpoint_config_class = OrderProposalPortfolioEndpointConfig
|
|
245
|
+
|
|
246
|
+
@cached_property
|
|
247
|
+
def default_trade_date(self) -> date | None:
|
|
248
|
+
with suppress(AssetPosition.DoesNotExist):
|
|
249
|
+
return (self.portfolio.assets.latest("date").date + BDay(1)).date()
|
|
250
|
+
|
|
251
|
+
def get_queryset(self):
|
|
252
|
+
return OrderProposal.objects.filter(portfolio=self.kwargs["portfolio_id"])
|