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,6 +1,6 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
2
|
from contextlib import suppress
|
|
3
|
-
from datetime import datetime
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
4
|
from decimal import Decimal
|
|
5
5
|
from itertools import chain
|
|
6
6
|
from typing import Any, Dict, List, Optional
|
|
@@ -9,7 +9,8 @@ from django.core.exceptions import ObjectDoesNotExist
|
|
|
9
9
|
from django.db import models
|
|
10
10
|
from wbcore.contrib.authentication.authentication import User
|
|
11
11
|
from wbcore.contrib.currency.import_export.handlers import CurrencyImportHandler
|
|
12
|
-
from wbcore.contrib.
|
|
12
|
+
from wbcore.contrib.currency.models import Currency
|
|
13
|
+
from wbcore.contrib.io.exceptions import DeserializationError, SkipImportError
|
|
13
14
|
from wbcore.contrib.io.imports import ImportExportHandler
|
|
14
15
|
from wbcore.contrib.notifications.dispatch import send_notification
|
|
15
16
|
from wbfdm.import_export.handlers.instrument import InstrumentImportHandler
|
|
@@ -30,16 +31,24 @@ class AssetPositionImportHandler(ImportExportHandler):
|
|
|
30
31
|
self.instrument_price_handler = InstrumentPriceImportHandler(self.import_source)
|
|
31
32
|
self.currency_handler = CurrencyImportHandler(self.import_source)
|
|
32
33
|
|
|
33
|
-
def _deserialize(self, data: Dict[str, Any]):
|
|
34
|
+
def _deserialize(self, data: Dict[str, Any]): # noqa: C901
|
|
34
35
|
from wbportfolio.models import Portfolio
|
|
35
36
|
|
|
36
37
|
portfolio_data = data.pop("portfolio", None)
|
|
37
38
|
underlying_quote_data = data.pop("underlying_quote", data.pop("underlying_instrument", None))
|
|
38
39
|
if "currency" in data:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
currency = self.currency_handler.process_object(data["currency"], read_only=True, raise_exception=False)[0]
|
|
41
|
+
# we do not support GBX in our instrument table
|
|
42
|
+
if currency.key == "GBX":
|
|
43
|
+
currency = Currency.objects.get(key="GBP")
|
|
44
|
+
if "initial_price" in data:
|
|
45
|
+
data["initial_price"] /= 1000
|
|
46
|
+
data["currency"] = currency
|
|
42
47
|
data["date"] = datetime.strptime(data["date"], "%Y-%m-%d").date()
|
|
48
|
+
|
|
49
|
+
# ensure that the position falls into a weekday
|
|
50
|
+
if data["date"].weekday() == 5:
|
|
51
|
+
data["date"] -= timedelta(days=1)
|
|
43
52
|
if data.get("asset_valuation_date", None):
|
|
44
53
|
data["asset_valuation_date"] = datetime.strptime(data["asset_valuation_date"], "%Y-%m-%d").date()
|
|
45
54
|
else:
|
|
@@ -73,16 +82,19 @@ class AssetPositionImportHandler(ImportExportHandler):
|
|
|
73
82
|
data["initial_price"] = data["underlying_quote"].get_price(
|
|
74
83
|
data["date"], price_date_timedelta=self.MAX_PRICE_DATE_TIMEDELTA
|
|
75
84
|
)
|
|
76
|
-
except ValueError:
|
|
77
|
-
raise DeserializationError("Price not provided but can not be found automatically")
|
|
85
|
+
except ValueError as e:
|
|
86
|
+
raise DeserializationError("Price not provided but can not be found automatically") from e
|
|
78
87
|
|
|
79
88
|
# number type deserialization and sanitization
|
|
80
89
|
# ensure the provided Decimal field are of type Decimal
|
|
81
90
|
decimal_fields = ["initial_currency_fx_rate", "initial_price", "initial_shares", "weighting"]
|
|
82
91
|
for field in decimal_fields:
|
|
83
|
-
if
|
|
92
|
+
if (value := data.get(field, None)) is not None:
|
|
84
93
|
data[field] = Decimal(value)
|
|
85
94
|
|
|
95
|
+
if data["weighting"] == 0:
|
|
96
|
+
raise SkipImportError("exclude position whose weight is 0")
|
|
97
|
+
|
|
86
98
|
def _process_raw_data(self, data: Dict[str, Any]):
|
|
87
99
|
if prices := data.get("prices", None):
|
|
88
100
|
self.import_source.log += "Instrument Prices found: Importing"
|
|
@@ -146,7 +158,7 @@ class AssetPositionImportHandler(ImportExportHandler):
|
|
|
146
158
|
for position in leftovers_positions:
|
|
147
159
|
position.delete()
|
|
148
160
|
for val_date in sorted(dates):
|
|
149
|
-
trigger_portfolio_change_as_task.delay(portfolio.id, val_date,
|
|
161
|
+
trigger_portfolio_change_as_task.delay(portfolio.id, val_date, fix_quantization=True)
|
|
150
162
|
|
|
151
163
|
# check if portfolio as custodian
|
|
152
164
|
latest_date = max(dates)
|
|
@@ -19,7 +19,7 @@ class DividendImportHandler(ImportExportHandler):
|
|
|
19
19
|
self.currency_handler = CurrencyImportHandler(self.import_source)
|
|
20
20
|
|
|
21
21
|
def _deserialize(self, data):
|
|
22
|
-
data["
|
|
22
|
+
data["ex_date"] = datetime.strptime(data["ex_date"], "%Y-%m-%d").date()
|
|
23
23
|
data["value_date"] = datetime.strptime(data["value_date"], "%Y-%m-%d").date()
|
|
24
24
|
from wbportfolio.models import Portfolio
|
|
25
25
|
|
|
@@ -36,18 +36,18 @@ class DividendImportHandler(ImportExportHandler):
|
|
|
36
36
|
data["currency"] = self.currency_handler.process_object(data["currency"], read_only=True)[0]
|
|
37
37
|
|
|
38
38
|
for field in self.model._meta.get_fields():
|
|
39
|
-
if
|
|
39
|
+
if (value := data.get(field.name, None)) is not None and isinstance(field, models.DecimalField):
|
|
40
40
|
q = 1 / (math.pow(10, 4))
|
|
41
41
|
data[field.name] = Decimal(value).quantize(Decimal(str(q)))
|
|
42
42
|
|
|
43
43
|
def _get_instance(self, data, history=None, **kwargs):
|
|
44
44
|
self.import_source.log += "\nGet DividendTransaction Instance."
|
|
45
|
-
self.import_source.log += f"\nParameter: Portfolio={data['portfolio']} Underlying={data['underlying_instrument']} Date={data['
|
|
45
|
+
self.import_source.log += f"\nParameter: Portfolio={data['portfolio']} Underlying={data['underlying_instrument']} Date={data['ex_date']}"
|
|
46
46
|
dividends = history if history is not None else self.model.objects
|
|
47
47
|
|
|
48
48
|
dividends = dividends.filter(
|
|
49
49
|
portfolio=data["portfolio"],
|
|
50
|
-
|
|
50
|
+
ex_date=data["ex_date"],
|
|
51
51
|
value_date=data["value_date"],
|
|
52
52
|
underlying_instrument=data["underlying_instrument"],
|
|
53
53
|
price_gross=data["price_gross"],
|
|
@@ -63,10 +63,10 @@ class DividendImportHandler(ImportExportHandler):
|
|
|
63
63
|
def _get_history(self: models.Model, history: Dict[str, Any]) -> models.QuerySet:
|
|
64
64
|
from wbportfolio.models import Product
|
|
65
65
|
|
|
66
|
-
val_date = datetime.strptime(history["
|
|
66
|
+
val_date = datetime.strptime(history["value_date"], "%Y-%m-%d")
|
|
67
67
|
try:
|
|
68
68
|
product = Product.objects.get(**history.get("product", {}))
|
|
69
|
-
dividends = self.model.objects.filter(
|
|
69
|
+
dividends = self.model.objects.filter(ex_date__lte=val_date, portfolio=product.primary_portfolio)
|
|
70
70
|
if underlying_instrument_data := history.get("underlying_instrument"):
|
|
71
71
|
if isinstance(underlying_instrument_data, dict):
|
|
72
72
|
dividends = dividends.filter(
|
|
@@ -83,8 +83,8 @@ class DividendImportHandler(ImportExportHandler):
|
|
|
83
83
|
self.import_source.log += (
|
|
84
84
|
"It was a historical import and the following DividendTransaction have to be deleted:"
|
|
85
85
|
)
|
|
86
|
-
for dividend in history.order_by("
|
|
86
|
+
for dividend in history.order_by("value_date"):
|
|
87
87
|
self.import_source.log += (
|
|
88
|
-
f"\n{dividend.
|
|
88
|
+
f"\n{dividend.value_date:%d.%m.%Y}: {dividend.shares} {dividend.price} ==> Deleted"
|
|
89
89
|
)
|
|
90
90
|
dividend.delete()
|
|
@@ -3,7 +3,6 @@ from datetime import datetime
|
|
|
3
3
|
from wbcore.contrib.currency.import_export.handlers import CurrencyImportHandler
|
|
4
4
|
from wbcore.contrib.io.exceptions import DeserializationError
|
|
5
5
|
from wbcore.contrib.io.imports import ImportExportHandler
|
|
6
|
-
from wbfdm.models.instruments import Cash
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
class FeesImportHandler(ImportExportHandler):
|
|
@@ -14,31 +13,22 @@ class FeesImportHandler(ImportExportHandler):
|
|
|
14
13
|
self.currency_handler = CurrencyImportHandler(self.import_source)
|
|
15
14
|
|
|
16
15
|
def _deserialize(self, data):
|
|
17
|
-
data["
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if book_date_str := data.get("book_date", None):
|
|
22
|
-
data["book_date"] = datetime.strptime(book_date_str, "%Y-%m-%d").date()
|
|
23
|
-
|
|
24
|
-
from wbportfolio.models import Portfolio, Product
|
|
16
|
+
data["fee_date"] = datetime.strptime(
|
|
17
|
+
data.get("fee_date", data.pop("transaction_date", None)), "%Y-%m-%d"
|
|
18
|
+
).date()
|
|
19
|
+
from wbportfolio.models import Product
|
|
25
20
|
|
|
26
21
|
try:
|
|
27
|
-
|
|
28
|
-
if isinstance(
|
|
29
|
-
data["
|
|
22
|
+
product_data = data.pop("product", None)
|
|
23
|
+
if isinstance(product_data, dict):
|
|
24
|
+
data["product"] = Product.objects.get(**product_data)
|
|
30
25
|
else:
|
|
31
|
-
data["
|
|
32
|
-
except Product.DoesNotExist:
|
|
33
|
-
raise DeserializationError("There is no valid linked product for in this row.")
|
|
26
|
+
data["product"] = Product.objects.get(id=product_data)
|
|
27
|
+
except Product.DoesNotExist as e:
|
|
28
|
+
raise DeserializationError("There is no valid linked product for in this row.") from e
|
|
34
29
|
|
|
35
|
-
if "porfolio" in data:
|
|
36
|
-
data["portfolio"] = Portfolio.all_objects.get(id=data["portfolio"])
|
|
37
|
-
else:
|
|
38
|
-
data["portfolio"] = data["linked_product"].primary_portfolio
|
|
39
|
-
data["underlying_instrument"] = Cash.objects.filter(currency=data["portfolio"].currency).first()
|
|
40
30
|
if "currency" not in data:
|
|
41
|
-
data["currency"] = data["
|
|
31
|
+
data["currency"] = data["product"].currency
|
|
42
32
|
else:
|
|
43
33
|
data["currency"] = self.currency_handler.process_object(data["currency"], read_only=True)[0]
|
|
44
34
|
data["currency_fx_rate"] = 1.0
|
|
@@ -48,9 +38,9 @@ class FeesImportHandler(ImportExportHandler):
|
|
|
48
38
|
|
|
49
39
|
def _get_instance(self, data, history=None, **kwargs):
|
|
50
40
|
self.import_source.log += "\nGet Fees Instance."
|
|
51
|
-
self.import_source.log += f"\nParameter:
|
|
41
|
+
self.import_source.log += f"\nParameter: Product={data['product']} Date={data['fee_date']}"
|
|
52
42
|
fees = self.model.objects.filter(
|
|
53
|
-
|
|
43
|
+
product=data["product"],
|
|
54
44
|
fee_date=data["fee_date"],
|
|
55
45
|
transaction_subtype=data["transaction_subtype"],
|
|
56
46
|
calculated=data["calculated"],
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
from typing import Any, Dict
|
|
3
|
+
|
|
4
|
+
from django.db import models
|
|
5
|
+
from wbcore.contrib.io.exceptions import DeserializationError
|
|
6
|
+
from wbcore.contrib.io.imports import ImportExportHandler, ImportState
|
|
7
|
+
from wbcore.contrib.io.utils import nest_row
|
|
8
|
+
from wbfdm.import_export.handlers.instrument import InstrumentImportHandler
|
|
9
|
+
|
|
10
|
+
from wbportfolio.pms.typing import Portfolio, Position
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OrderImportHandler(ImportExportHandler):
|
|
14
|
+
MODEL_APP_LABEL: str = "wbportfolio.Order"
|
|
15
|
+
|
|
16
|
+
def __init__(self, *args, **kwargs):
|
|
17
|
+
super().__init__(*args, **kwargs)
|
|
18
|
+
self.instrument_handler = InstrumentImportHandler(self.import_source)
|
|
19
|
+
self.order_proposal = None
|
|
20
|
+
|
|
21
|
+
def process_object(
|
|
22
|
+
self,
|
|
23
|
+
data: Dict[str, Any],
|
|
24
|
+
**kwargs,
|
|
25
|
+
):
|
|
26
|
+
from wbportfolio.models import OrderProposal
|
|
27
|
+
|
|
28
|
+
data = nest_row(data)
|
|
29
|
+
underlying_instrument = self.instrument_handler.process_object(
|
|
30
|
+
data["underlying_instrument"], only_security=False, read_only=True
|
|
31
|
+
)[0]
|
|
32
|
+
self.order_proposal = OrderProposal.objects.get(id=data.pop("order_proposal_id"))
|
|
33
|
+
weighting = data.get("target_weight", data.get("weighting"))
|
|
34
|
+
shares = data.get("target_shares", data.get("shares", 0))
|
|
35
|
+
if weighting is None:
|
|
36
|
+
raise DeserializationError("We couldn't figure out the target weight column")
|
|
37
|
+
position_dto = Position(
|
|
38
|
+
underlying_instrument=underlying_instrument.id,
|
|
39
|
+
instrument_type=underlying_instrument.instrument_type.id,
|
|
40
|
+
weighting=Decimal(weighting),
|
|
41
|
+
shares=Decimal(shares),
|
|
42
|
+
currency=underlying_instrument.currency,
|
|
43
|
+
date=self.order_proposal.trade_date,
|
|
44
|
+
is_cash=underlying_instrument.is_cash,
|
|
45
|
+
)
|
|
46
|
+
return position_dto, ImportState.CREATED
|
|
47
|
+
|
|
48
|
+
def _get_history(self, history: Dict[str, Any]) -> models.QuerySet:
|
|
49
|
+
from wbportfolio.models.orders.order_proposals import OrderProposal
|
|
50
|
+
|
|
51
|
+
if order_proposal_id := history.get("order_proposal_id"):
|
|
52
|
+
# if a order proposal is provided, we delete the existing history first as otherwise, it would mess with the target weight computation
|
|
53
|
+
order_proposal = OrderProposal.objects.get(id=order_proposal_id)
|
|
54
|
+
order_proposal.orders.all().delete()
|
|
55
|
+
return self.model.objects.none()
|
|
56
|
+
|
|
57
|
+
def _post_processing_objects(self, positions: list[Position], *args, **kwargs):
|
|
58
|
+
total_weight = sum(map(lambda p: p.weighting, positions))
|
|
59
|
+
if cash_weight := Decimal("1") - total_weight:
|
|
60
|
+
cash_component = self.order_proposal.cash_component
|
|
61
|
+
positions.append(
|
|
62
|
+
Position(
|
|
63
|
+
underlying_instrument=cash_component.id,
|
|
64
|
+
instrument_type=cash_component.instrument_type.id,
|
|
65
|
+
weighting=cash_weight,
|
|
66
|
+
currency=cash_component.currency,
|
|
67
|
+
date=self.order_proposal.trade_date,
|
|
68
|
+
is_cash=cash_component.is_cash,
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
self.order_proposal.reset_orders(target_portfolio=Portfolio(positions))
|
|
@@ -25,7 +25,6 @@ class TradeImportHandler(ImportExportHandler):
|
|
|
25
25
|
self.instrument_handler = InstrumentImportHandler(self.import_source)
|
|
26
26
|
self.register_handler = RegisterImportHandler(self.import_source)
|
|
27
27
|
self.currency_handler = CurrencyImportHandler(self.import_source)
|
|
28
|
-
self.trade_proposals = set()
|
|
29
28
|
|
|
30
29
|
def _data_changed(self, _object, change_data: Dict[str, Any], initial_data: Dict[str, Any], **kwargs):
|
|
31
30
|
if (new_register := change_data.get("register")) and (current_register := _object.register):
|
|
@@ -35,67 +34,59 @@ class TradeImportHandler(ImportExportHandler):
|
|
|
35
34
|
return super()._data_changed(_object, change_data, initial_data, **kwargs)
|
|
36
35
|
|
|
37
36
|
def _deserialize(self, data: Dict[str, Any]):
|
|
38
|
-
from wbportfolio.models import Product
|
|
37
|
+
from wbportfolio.models import Product
|
|
39
38
|
|
|
40
39
|
if underlying_instrument := data.get("underlying_instrument", None):
|
|
41
40
|
data["underlying_instrument"] = self.instrument_handler.process_object(
|
|
42
41
|
underlying_instrument, only_security=False, read_only=True
|
|
43
42
|
)[0]
|
|
44
43
|
|
|
45
|
-
if
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
data["
|
|
49
|
-
|
|
50
|
-
data["
|
|
51
|
-
|
|
52
|
-
data["
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if transaction_date_str := data.get("transaction_date", None):
|
|
57
|
-
data["transaction_date"] = datetime.strptime(transaction_date_str, "%Y-%m-%d").date()
|
|
58
|
-
if value_date_str := data.get("value_date", None):
|
|
59
|
-
data["value_date"] = datetime.strptime(value_date_str, "%Y-%m-%d").date()
|
|
60
|
-
if book_date_str := data.get("book_date", None):
|
|
61
|
-
data["book_date"] = datetime.strptime(book_date_str, "%Y-%m-%d").date()
|
|
62
|
-
data["portfolio"] = Portfolio._get_or_create_portfolio(
|
|
63
|
-
self.instrument_handler, data.get("portfolio", data["underlying_instrument"])
|
|
64
|
-
)
|
|
44
|
+
if external_id_alternative := data.get("external_id_alternative", None):
|
|
45
|
+
data["external_id_alternative"] = str(external_id_alternative)
|
|
46
|
+
if transaction_date_str := data.get("transaction_date", None):
|
|
47
|
+
data["transaction_date"] = datetime.strptime(transaction_date_str, "%Y-%m-%d").date()
|
|
48
|
+
if value_date_str := data.get("value_date", None):
|
|
49
|
+
data["value_date"] = datetime.strptime(value_date_str, "%Y-%m-%d").date()
|
|
50
|
+
if book_date_str := data.get("book_date", None):
|
|
51
|
+
data["book_date"] = datetime.strptime(book_date_str, "%Y-%m-%d").date()
|
|
52
|
+
data["portfolio"] = Portfolio._get_or_create_portfolio(
|
|
53
|
+
self.instrument_handler, data.get("portfolio", data["underlying_instrument"])
|
|
54
|
+
)
|
|
65
55
|
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
if currency_data := data.get("currency", None):
|
|
57
|
+
data["currency"] = self.currency_handler.process_object(currency_data, read_only=True)[0]
|
|
68
58
|
|
|
69
|
-
|
|
70
|
-
|
|
59
|
+
if register_data := data.get("register", None):
|
|
60
|
+
data["register"] = self.register_handler.process_object(register_data)[0]
|
|
71
61
|
|
|
72
|
-
|
|
62
|
+
data["marked_for_deletion"] = data.get("marked_for_deletion", False)
|
|
73
63
|
if underlying_instrument := data.get("underlying_instrument"):
|
|
74
64
|
if nominal := data.pop("nominal", None):
|
|
75
65
|
try:
|
|
76
66
|
product = Product.objects.get(id=underlying_instrument.id)
|
|
77
67
|
data["shares"] = nominal / product.share_price
|
|
78
|
-
except Product.DoesNotExist:
|
|
68
|
+
except Product.DoesNotExist as e:
|
|
79
69
|
raise DeserializationError(
|
|
80
70
|
"We cannot compute the number of shares from the nominal value as we cannot find the product share price."
|
|
81
|
-
)
|
|
71
|
+
) from e
|
|
82
72
|
else:
|
|
83
73
|
raise DeserializationError("We couldn't find a valid underlying instrument this row.")
|
|
84
74
|
|
|
85
75
|
for field in self.model._meta.get_fields():
|
|
86
|
-
if
|
|
76
|
+
if (value := data.get(field.name, None)) is not None and isinstance(field, models.DecimalField):
|
|
87
77
|
q = (
|
|
88
78
|
1 / (math.pow(10, 4))
|
|
89
79
|
) # we need that convertion mechanism otherwise there is floating point approximation error while casting to decimal and get_instance does not work as expected
|
|
90
80
|
data[field.name] = Decimal(value).quantize(Decimal(str(q)))
|
|
81
|
+
if (target_weight := data.pop("target_weight", None)) is not None:
|
|
82
|
+
data["_target_weight"] = target_weight
|
|
91
83
|
|
|
92
84
|
def _create_instance(self, data: Dict[str, Any], **kwargs) -> models.Model:
|
|
93
85
|
if "transaction_date" not in data: # we might get only book date and not transaction date
|
|
94
86
|
data["transaction_date"] = data["book_date"]
|
|
95
|
-
|
|
96
87
|
return self.model.objects.create(**data, import_source=self.import_source)
|
|
97
88
|
|
|
98
|
-
def _get_instance(self, data: Dict[str, Any], history: Optional[models.QuerySet] = None, **kwargs) -> models.Model:
|
|
89
|
+
def _get_instance(self, data: Dict[str, Any], history: Optional[models.QuerySet] = None, **kwargs) -> models.Model: # noqa: C901
|
|
99
90
|
self.import_source.log += "\nGet Trade Instance."
|
|
100
91
|
if transaction_date := data.get("transaction_date"):
|
|
101
92
|
dates_lookup = {"transaction_date": transaction_date}
|
|
@@ -108,25 +99,23 @@ class TradeImportHandler(ImportExportHandler):
|
|
|
108
99
|
if history.exists():
|
|
109
100
|
queryset = history
|
|
110
101
|
else:
|
|
111
|
-
queryset = self.model.objects.filter(marked_for_deletion=False)
|
|
102
|
+
queryset = self.model.objects.filter(marked_for_deletion=False).exclude(id__in=self.processed_ids)
|
|
112
103
|
|
|
113
104
|
queryset = queryset.filter(
|
|
114
105
|
models.Q(underlying_instrument=data["underlying_instrument"]) & models.Q(**dates_lookup)
|
|
115
106
|
)
|
|
116
107
|
if "shares" in data:
|
|
117
108
|
queryset = queryset.filter(shares=data["shares"])
|
|
118
|
-
|
|
119
109
|
if _id := data.get("id", None):
|
|
120
110
|
self.import_source.log += f"ID {_id} provided -> Load CustomerTrade"
|
|
121
111
|
return self.model.objects.get(id=_id)
|
|
122
112
|
# We need to check for external identifiers
|
|
123
113
|
if external_id := data.get("external_id"):
|
|
124
114
|
self.import_source.log += f"\nExternal Identifier used: {external_id}"
|
|
125
|
-
|
|
126
|
-
if
|
|
115
|
+
external_id_queryset = queryset.filter(external_id=external_id)
|
|
116
|
+
if external_id_queryset.count() == 1:
|
|
127
117
|
self.import_source.log += f"External ID {external_id} provided -> Load CustomerTrade"
|
|
128
|
-
return
|
|
129
|
-
|
|
118
|
+
return external_id_queryset.first()
|
|
130
119
|
if portfolio := data.get("portfolio", None):
|
|
131
120
|
queryset = queryset.filter(portfolio=portfolio)
|
|
132
121
|
if queryset.exists():
|
|
@@ -152,6 +141,7 @@ class TradeImportHandler(ImportExportHandler):
|
|
|
152
141
|
if queryset.exists():
|
|
153
142
|
# We try to filter by price as well
|
|
154
143
|
trade = queryset.first()
|
|
144
|
+
|
|
155
145
|
if queryset.count() == 1:
|
|
156
146
|
self.import_source.log += f"\nOne Trade found: {trade}"
|
|
157
147
|
if queryset.count() > 1:
|
|
@@ -160,35 +150,30 @@ class TradeImportHandler(ImportExportHandler):
|
|
|
160
150
|
self.import_source.log += "\nNo trade was successfully matched."
|
|
161
151
|
|
|
162
152
|
def _get_history(self, history: Dict[str, Any]) -> models.QuerySet:
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if
|
|
177
|
-
trades = trades.filter(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if underlying_instrument_data := history.get("underlying_instrument"):
|
|
181
|
-
if isinstance(underlying_instrument_data, dict):
|
|
182
|
-
trades = trades.filter(
|
|
183
|
-
**{f"underlying_instrument__{k}": v for k, v in underlying_instrument_data.items()}
|
|
184
|
-
)
|
|
185
|
-
else:
|
|
186
|
-
trades = trades.filter(underlying_instrument__id=underlying_instrument_data)
|
|
187
|
-
|
|
188
|
-
elif "underlying_instruments" in history:
|
|
189
|
-
trades = trades.filter(underlying_instrument__id__in=history["underlying_instruments"])
|
|
153
|
+
trades = self.model.objects.filter(
|
|
154
|
+
exclude_from_history=False,
|
|
155
|
+
pending=False,
|
|
156
|
+
transaction_subtype__in=[
|
|
157
|
+
self.model.Type.SUBSCRIPTION,
|
|
158
|
+
self.model.Type.REDEMPTION,
|
|
159
|
+
], # we cannot exclude marked for deleted trade because otherwise they are never consider in the history
|
|
160
|
+
)
|
|
161
|
+
if transaction_date := history.get("transaction_date"):
|
|
162
|
+
trades = trades.filter(transaction_date__lte=transaction_date)
|
|
163
|
+
elif book_date := history.get("book_date"):
|
|
164
|
+
trades = trades.filter(book_date__lte=book_date)
|
|
165
|
+
if underlying_instrument_data := history.get("underlying_instrument"):
|
|
166
|
+
if isinstance(underlying_instrument_data, dict):
|
|
167
|
+
trades = trades.filter(
|
|
168
|
+
**{f"underlying_instrument__{k}": v for k, v in underlying_instrument_data.items()}
|
|
169
|
+
)
|
|
190
170
|
else:
|
|
191
|
-
|
|
171
|
+
trades = trades.filter(underlying_instrument__id=underlying_instrument_data)
|
|
172
|
+
|
|
173
|
+
elif "underlying_instruments" in history:
|
|
174
|
+
trades = trades.filter(underlying_instrument__id__in=history["underlying_instruments"])
|
|
175
|
+
else:
|
|
176
|
+
raise ValueError("We cannot estimate history without at least the underlying instrument")
|
|
192
177
|
return trades
|
|
193
178
|
|
|
194
179
|
def _post_processing_objects(
|
|
@@ -203,12 +188,6 @@ class TradeImportHandler(ImportExportHandler):
|
|
|
203
188
|
if instrument.instrument_type.key == "product":
|
|
204
189
|
update_outstanding_shares_as_task.delay(instrument.id)
|
|
205
190
|
|
|
206
|
-
# if the trade import relates to a trade proposal, we reset the TP after the import to ensure it contains the deleted positions (often forgotten by user)
|
|
207
|
-
for changed_trade_proposal in self.trade_proposals:
|
|
208
|
-
changed_trade_proposal.reset_trades(
|
|
209
|
-
target_portfolio=changed_trade_proposal._build_dto().convert_to_portfolio()
|
|
210
|
-
)
|
|
211
|
-
|
|
212
191
|
def _post_processing_updated_object(self, _object):
|
|
213
192
|
if _object.marked_for_deletion:
|
|
214
193
|
_object.marked_for_deletion = False
|
|
@@ -227,8 +206,5 @@ class TradeImportHandler(ImportExportHandler):
|
|
|
227
206
|
self.import_source.log += (
|
|
228
207
|
f"{trade.transaction_date:%d.%m.%Y}: {trade.shares} {trade.bank} ==> Marked for deletion"
|
|
229
208
|
)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
else:
|
|
233
|
-
trade.marked_for_deletion = True
|
|
234
|
-
trade.save()
|
|
209
|
+
trade.marked_for_deletion = True
|
|
210
|
+
trade.save()
|
|
@@ -7,7 +7,7 @@ def parse(import_source):
|
|
|
7
7
|
if (
|
|
8
8
|
(data_backend := import_source.source.data_backend)
|
|
9
9
|
and (backend_class := data_backend.backend_class)
|
|
10
|
-
and (default_mapping :=
|
|
10
|
+
and (default_mapping := backend_class.DEFAULT_MAPPING)
|
|
11
11
|
):
|
|
12
12
|
df = pd.read_json(import_source.file, orient="records")
|
|
13
13
|
if not df.empty:
|
|
@@ -11,8 +11,8 @@ from wbportfolio.models import Product, Trade
|
|
|
11
11
|
def file_name_parse(file_name):
|
|
12
12
|
dates = re.findall("([0-9]{8})", file_name)
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
if len(dates) <= 0:
|
|
15
|
+
raise ValueError("No dates found in the filename")
|
|
16
16
|
parts_dict = {"valuation_date": datetime.datetime.strptime(dates[0], "%Y%m%d").date()}
|
|
17
17
|
|
|
18
18
|
isin = re.findall("([A-Z]{2}[A-Z0-9]{9}[0-9]{1})", file_name)
|
|
@@ -14,8 +14,8 @@ logger = logging.getLogger("importers.parsers.jpmorgan.fee")
|
|
|
14
14
|
def file_name_parse(file_name):
|
|
15
15
|
dates = re.findall("([0-9]{8})", file_name)
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
if len(dates) != 1:
|
|
18
|
+
raise ValueError("Not exactly 1 date found in the filename")
|
|
19
19
|
return {"valuation_date": datetime.datetime.strptime(dates[0], "%Y%m%d").date()}
|
|
20
20
|
|
|
21
21
|
|
|
@@ -34,8 +34,8 @@ def parse(import_source):
|
|
|
34
34
|
isin = fee_data["ISIN"]
|
|
35
35
|
fee_date = fee_data["Date"]
|
|
36
36
|
base_data = {
|
|
37
|
-
"
|
|
38
|
-
"
|
|
37
|
+
"product": {"isin": isin},
|
|
38
|
+
"fee_date": fee_date.strftime("%Y-%m-%d"),
|
|
39
39
|
"calculated": False,
|
|
40
40
|
}
|
|
41
41
|
data.append(
|