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
|
@@ -10,6 +10,7 @@ files generated by other spreadsheets.
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
import re
|
|
13
|
+
from ast import literal_eval
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class Table:
|
|
@@ -185,7 +186,7 @@ class SYLK:
|
|
|
185
186
|
self.cury = int(val)
|
|
186
187
|
|
|
187
188
|
elif ftd == "K":
|
|
188
|
-
val =
|
|
189
|
+
val = literal_eval(self.escape(val))
|
|
189
190
|
# if type(val) == int:
|
|
190
191
|
# if self.currenttype == "date":
|
|
191
192
|
# # value is offset in days from datebase
|
|
@@ -208,20 +209,20 @@ class SYLK:
|
|
|
208
209
|
self.printformats.append((format, self.knownformats[format]))
|
|
209
210
|
else:
|
|
210
211
|
# hack to guess type...
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (
|
|
212
|
+
has_y = "y" in format
|
|
213
|
+
has_d = "d" in format
|
|
214
|
+
has_h = "h" in format
|
|
215
|
+
has_z = "0" in format
|
|
216
|
+
has_p = "." in format
|
|
217
|
+
if (has_d or has_y) and has_h:
|
|
217
218
|
dtype = "datetime"
|
|
218
|
-
elif
|
|
219
|
+
elif has_d or has_y:
|
|
219
220
|
dtype = "date"
|
|
220
|
-
elif
|
|
221
|
+
elif has_h:
|
|
221
222
|
dtype = "time"
|
|
222
|
-
elif
|
|
223
|
+
elif has_p and has_z:
|
|
223
224
|
dtype = "float"
|
|
224
|
-
elif
|
|
225
|
+
elif has_z:
|
|
225
226
|
dtype = "int"
|
|
226
227
|
else:
|
|
227
228
|
dtype = "string"
|
|
@@ -17,7 +17,7 @@ def parse_transaction_reference(trans_ref: str) -> tuple[str, str, bool]:
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def assemble_transaction_reference(data: Dict[str, Any]) -> str:
|
|
20
|
-
transaction_ref = data["
|
|
20
|
+
transaction_ref = data["external_id_alternative"]
|
|
21
21
|
register_reference = data["register__register_reference"]
|
|
22
22
|
if data.get("TRANSFER_REGISTER", None):
|
|
23
23
|
register_reference = data["TRANSFER_REGISTER"]
|
|
@@ -29,7 +29,7 @@ def assemble_transaction_reference(data: Dict[str, Any]) -> str:
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def create_transaction_reference(customer_trade: "Trade") -> str:
|
|
32
|
-
transaction_id = customer_trade.
|
|
32
|
+
transaction_id = customer_trade.external_id_alternative
|
|
33
33
|
outlet_id = customer_trade.register.clearing_reference
|
|
34
34
|
credit_debit = "C" if customer_trade.initial_shares > 0 else "D"
|
|
35
35
|
|
|
@@ -10,8 +10,10 @@ def file_name_parse(file_name):
|
|
|
10
10
|
dates = re.findall(r"([0-9]{4}-[0-9]{2}-[0-9]{2})", file_name)
|
|
11
11
|
isin = re.findall(r"\.([a-zA-Z0-9]*)_", file_name)
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
if len(dates) != 2:
|
|
14
|
+
raise ValueError("Not 2 dates found in the filename")
|
|
15
|
+
if len(isin) != 1:
|
|
16
|
+
raise ValueError("Not exactly 1 isin found in the filename")
|
|
15
17
|
|
|
16
18
|
return {
|
|
17
19
|
"isin": isin[0],
|
|
@@ -14,8 +14,8 @@ logger = logging.getLogger("importers.parsers.jp_morgan.strategy")
|
|
|
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
|
|
|
@@ -23,7 +23,7 @@ def parse(import_source):
|
|
|
23
23
|
data = list()
|
|
24
24
|
prices = list()
|
|
25
25
|
df_dict = pd.read_excel(BytesIO(import_source.file.read()), engine="openpyxl", sheet_name=None)
|
|
26
|
-
for
|
|
26
|
+
for df in df_dict.values():
|
|
27
27
|
xx, yy = np.where(df == "Ticker")
|
|
28
28
|
if len(xx) == 1 and len(yy) == 1:
|
|
29
29
|
df_info = df.iloc[: xx[0] - 1, :].transpose()
|
|
@@ -73,8 +73,8 @@ def parse(import_source):
|
|
|
73
73
|
"exchange": exchange,
|
|
74
74
|
"asset_type": "equity",
|
|
75
75
|
"currency__key": position["currency__key"],
|
|
76
|
-
"initial_currency_fx_rate": round(position["initial_currency_fx_rate"],
|
|
77
|
-
"weighting": round(position["weighting"],
|
|
76
|
+
"initial_currency_fx_rate": round(position["initial_currency_fx_rate"], 14),
|
|
77
|
+
"weighting": round(position["weighting"], 8),
|
|
78
78
|
"initial_price": round(position["initial_price"], 6),
|
|
79
79
|
"date": valuation_date.strftime("%Y-%m-%d"),
|
|
80
80
|
}
|
|
@@ -20,7 +20,8 @@ product_mapping = {
|
|
|
20
20
|
|
|
21
21
|
def file_name_parse(file_name):
|
|
22
22
|
identifier = re.findall("([0-9]{4}).*", file_name)
|
|
23
|
-
|
|
23
|
+
if len(identifier) != 1:
|
|
24
|
+
raise ValueError("Not exactly 1 identifier found in the filename")
|
|
24
25
|
return identifier[0]
|
|
25
26
|
|
|
26
27
|
|
|
@@ -7,9 +7,10 @@ import re
|
|
|
7
7
|
def file_name_parse(file_name):
|
|
8
8
|
dates = re.findall(r"([0-9]{4}-[0-9]{2}-[0-9]{2})", file_name)
|
|
9
9
|
isin = re.findall(r"\.([a-zA-Z0-9]*)_", file_name)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
if len(dates) != 2:
|
|
11
|
+
raise ValueError("Not 2 dates found in the filename")
|
|
12
|
+
if len(isin) != 1:
|
|
13
|
+
raise ValueError("Not exactly 1 isin found in the filename")
|
|
13
14
|
|
|
14
15
|
return {
|
|
15
16
|
"isin": isin[0],
|
|
@@ -4,14 +4,14 @@ import pandas as pd
|
|
|
4
4
|
|
|
5
5
|
from wbportfolio.models import Fees
|
|
6
6
|
|
|
7
|
-
BASE_MAPPING = {"managementFee": "total_value", "performanceFee": "total_value", "date": "
|
|
7
|
+
BASE_MAPPING = {"managementFee": "total_value", "performanceFee": "total_value", "date": "fee_date"}
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def parse(import_source):
|
|
11
11
|
def _process_df(df, product_isin):
|
|
12
12
|
df = df.rename(columns=BASE_MAPPING).dropna(how="all", axis=1)
|
|
13
13
|
df = df.drop(columns=df.columns.difference(BASE_MAPPING.values()))
|
|
14
|
-
df["
|
|
14
|
+
df["product"] = [{"isin": product_isin}] * df.shape[0]
|
|
15
15
|
return df
|
|
16
16
|
|
|
17
17
|
content = json.load(import_source.file)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from django.utils.dateparse import parse_date
|
|
6
|
+
|
|
7
|
+
BASE_MAPPING = {
|
|
8
|
+
"instrument": "underlying_instrument__name",
|
|
9
|
+
"ric": "underlying_instrument__refinitiv_identifier_code",
|
|
10
|
+
"bbTicker": "underlying_instrument__ticker",
|
|
11
|
+
"isin": "underlying_instrument__isin",
|
|
12
|
+
"assetClass": "underlying_instrument__instrument_type",
|
|
13
|
+
"direction": "transaction_subtype",
|
|
14
|
+
"currency": "currency__key",
|
|
15
|
+
"quantityTraded": "shares",
|
|
16
|
+
"localPrice": "price",
|
|
17
|
+
"fxMultiplier": "currency_fx_rate",
|
|
18
|
+
"tradeDate": "book_date",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def parse(import_source):
|
|
23
|
+
content = json.load(import_source.file)
|
|
24
|
+
data = []
|
|
25
|
+
if (rebalances := content.get("rebalances", None)) and (isin := content.get("isin", None)):
|
|
26
|
+
for rebalance_data in rebalances:
|
|
27
|
+
rebalancing_date = parse_date(rebalance_data["rebalanceDate"])
|
|
28
|
+
df = pd.DataFrame(rebalance_data["items"]).replace([np.inf, -np.inf, np.nan], None)
|
|
29
|
+
df = df.rename(columns=BASE_MAPPING)
|
|
30
|
+
df = df.drop(columns=df.columns.difference(BASE_MAPPING.values()))
|
|
31
|
+
df["underlying_instrument__instrument_type"] = df["underlying_instrument__instrument_type"].str.lower()
|
|
32
|
+
df["transaction_date"] = rebalancing_date.strftime("%Y-%m-%d")
|
|
33
|
+
df["portfolio__isin"] = isin
|
|
34
|
+
df.loc[df["book_date"].isnull(), "book_date"] = df.loc[df["book_date"].isnull(), "transaction_date"]
|
|
35
|
+
df["currency_fx_rate"] = 1 / df["currency_fx_rate"]
|
|
36
|
+
df.loc[df["price"].isnull() & (df["underlying_instrument__instrument_type"] == "cash"), "price"] = 1.0
|
|
37
|
+
df["underlying_instrument__currency__key"] = df["currency__key"]
|
|
38
|
+
data.extend(df.to_dict(orient="records"))
|
|
39
|
+
return {"data": data}
|
|
@@ -3,15 +3,15 @@ from typing import Dict
|
|
|
3
3
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
import pandas as pd
|
|
6
|
-
from
|
|
6
|
+
from slugify import slugify
|
|
7
7
|
|
|
8
8
|
from wbportfolio.models import Trade
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def parse_row(obj: Dict,
|
|
11
|
+
def parse_row(obj: Dict, negate_shares: bool = False) -> Dict:
|
|
12
12
|
isin = obj["underlying_instrument__isin"]
|
|
13
13
|
shares = obj["shares"]
|
|
14
|
-
if
|
|
14
|
+
if negate_shares:
|
|
15
15
|
shares = -1 * shares
|
|
16
16
|
return {
|
|
17
17
|
"underlying_instrument": {"isin": isin, "instrument_type": "product"},
|
|
@@ -30,6 +30,9 @@ def parse(import_source):
|
|
|
30
30
|
xx, yy = np.where(df == "Trade Date")
|
|
31
31
|
df = df.iloc[xx[0] :, yy[0] :]
|
|
32
32
|
df = df.rename(columns=df.iloc[0]).drop(df.index[0]).dropna(how="all")
|
|
33
|
+
negate_shares = "net-quantity" in list(
|
|
34
|
+
map(lambda c: slugify(c), df.columns)
|
|
35
|
+
) # we slugified the column to be more robust
|
|
33
36
|
df = df.rename(columns=lambda x: x.lower())
|
|
34
37
|
df = df.rename(
|
|
35
38
|
columns={
|
|
@@ -50,6 +53,5 @@ def parse(import_source):
|
|
|
50
53
|
df.loc[df["custodian"].isnull(), "custodian"] = "N/A"
|
|
51
54
|
data = list()
|
|
52
55
|
for d in df.to_dict("records"):
|
|
53
|
-
data.append(parse_row(d,
|
|
54
|
-
|
|
56
|
+
data.append(parse_row(d, negate_shares=negate_shares))
|
|
55
57
|
return {"data": data}
|
|
@@ -7,7 +7,8 @@ import xlrd
|
|
|
7
7
|
def file_name_parse(file_name):
|
|
8
8
|
isin = re.findall("([A-Z]{2}[A-Z0-9]{9}[0-9]{1})", file_name)
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
if len(isin) != 1:
|
|
11
|
+
raise ValueError("Not exactly 1 isin found in the filename")
|
|
11
12
|
|
|
12
13
|
return {"isin": isin[0]}
|
|
13
14
|
|
|
@@ -40,7 +41,7 @@ def parse(import_source):
|
|
|
40
41
|
for row in range(first_row, last_row, 1):
|
|
41
42
|
initial_shares = round(equity_sheet.cell_value(row, 8), 4)
|
|
42
43
|
close = round(equity_sheet.cell_value(row, 9), 4)
|
|
43
|
-
initial_currency_fx_rate = round(equity_sheet.cell_value(row, 11),
|
|
44
|
+
initial_currency_fx_rate = round(equity_sheet.cell_value(row, 11), 14)
|
|
44
45
|
|
|
45
46
|
bbg = equity_sheet.cell_value(row, 3)
|
|
46
47
|
ric = equity_sheet.cell_value(row, 2)
|
|
@@ -11,7 +11,8 @@ from wbportfolio.import_export.utils import get_file_extension
|
|
|
11
11
|
def file_name_parse(file_name):
|
|
12
12
|
isin = re.findall("([A-Z]{2}[A-Z0-9]{9}[0-9]{1})", file_name)
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
if len(isin) != 1:
|
|
15
|
+
raise ValueError("Not exactly 1 isin found in the filename")
|
|
15
16
|
|
|
16
17
|
return {
|
|
17
18
|
"isin": isin[0],
|
|
@@ -11,10 +11,9 @@ FIELD_MAP = {
|
|
|
11
11
|
"Trade_Date": "book_date",
|
|
12
12
|
"Value_Date": "value_date",
|
|
13
13
|
"Price": "price",
|
|
14
|
-
"Quantity": "total_value",
|
|
15
14
|
"Currency": "currency__key",
|
|
16
15
|
"Portfolio_Identifier": "underlying_instrument__identifier",
|
|
17
|
-
"External_Reference": "
|
|
16
|
+
"External_Reference": "external_id_alternative",
|
|
18
17
|
"Booking_Comment": "comment",
|
|
19
18
|
}
|
|
20
19
|
|
|
@@ -36,8 +35,8 @@ def parse(import_source):
|
|
|
36
35
|
data = []
|
|
37
36
|
if not df.empty:
|
|
38
37
|
df = df.rename(columns=FIELD_MAP)
|
|
38
|
+
df["shares"] = df["Quantity"] / df["price"]
|
|
39
39
|
df = df.drop(columns=df.columns.difference(FIELD_MAP.values()))
|
|
40
|
-
df["shares"] = df["total_value"] / df["price"]
|
|
41
40
|
df["transaction_date"] = pd.to_datetime(df["transaction_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
|
|
42
41
|
df["book_date"] = pd.to_datetime(df["book_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
|
|
43
42
|
df["value_date"] = pd.to_datetime(df["value_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
|
|
@@ -6,12 +6,10 @@ import pandas as pd
|
|
|
6
6
|
from wbportfolio.models import FeeProductPercentage, Fees, Product
|
|
7
7
|
|
|
8
8
|
FIELD_MAP = {
|
|
9
|
-
"Transaction_Date": "
|
|
10
|
-
"Trade_Date": "book_date",
|
|
11
|
-
"Value_Date": "value_date",
|
|
9
|
+
"Transaction_Date": "fee_date",
|
|
12
10
|
"Quantity": "total_value",
|
|
13
11
|
"Currency": "currency__key",
|
|
14
|
-
"Portfolio_Identifier": "
|
|
12
|
+
"Portfolio_Identifier": "product",
|
|
15
13
|
"Booking_Comment": "comment",
|
|
16
14
|
}
|
|
17
15
|
|
|
@@ -25,21 +23,14 @@ def parse(import_source):
|
|
|
25
23
|
data = []
|
|
26
24
|
if not df.empty:
|
|
27
25
|
df = df.rename(columns=FIELD_MAP)
|
|
28
|
-
df
|
|
29
|
-
df["
|
|
30
|
-
df["book_date"] = pd.to_datetime(df["book_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
|
|
31
|
-
df["value_date"] = pd.to_datetime(df["value_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
|
|
32
|
-
df["portfolio"] = df.linked_product.apply(lambda x: x.primary_portfolio if x else None)
|
|
26
|
+
df["product"] = df["product"].apply(lambda x: Product.objects.filter(identifier=x).first())
|
|
27
|
+
df["fee_date"] = pd.to_datetime(df["fee_date"], dayfirst=True)
|
|
33
28
|
df["base_management_fees"] = df.apply(
|
|
34
|
-
lambda row: float(
|
|
35
|
-
row["linked_product"].get_fees_percent(row["transaction_date"], FeeProductPercentage.Type.MANAGEMENT)
|
|
36
|
-
),
|
|
29
|
+
lambda row: float(row["product"].get_fees_percent(row["fee_date"], FeeProductPercentage.Type.MANAGEMENT)),
|
|
37
30
|
axis=1,
|
|
38
31
|
)
|
|
39
32
|
df["base_bank_fees"] = df.apply(
|
|
40
|
-
lambda row: float(
|
|
41
|
-
row["linked_product"].get_fees_percent(row["transaction_date"], FeeProductPercentage.Type.BANK)
|
|
42
|
-
),
|
|
33
|
+
lambda row: float(row["product"].get_fees_percent(row["fee_date"], FeeProductPercentage.Type.BANK)),
|
|
43
34
|
axis=1,
|
|
44
35
|
)
|
|
45
36
|
df.total_value = -df.total_value
|
|
@@ -63,8 +54,8 @@ def parse(import_source):
|
|
|
63
54
|
df = pd.concat([df_management, df_bank], axis=0)
|
|
64
55
|
df = df.drop(columns=df.columns.difference(["transaction_subtype", *FIELD_MAP.values()]))
|
|
65
56
|
|
|
66
|
-
df["
|
|
67
|
-
df = df.dropna(subset=["
|
|
57
|
+
df["product"] = df["product"].apply(lambda x: x.id)
|
|
58
|
+
df = df.dropna(subset=["product"])
|
|
68
59
|
|
|
69
60
|
for row in df.to_dict("records"):
|
|
70
61
|
# The fee are sometime aggregated. The aggregate date range information is given in the comment. We therefore try to extract it and duplicate the averaged fees
|
|
@@ -72,14 +63,15 @@ def parse(import_source):
|
|
|
72
63
|
from_date = datetime.strptime(match.group(1), "%d.%m.%Y").date()
|
|
73
64
|
to_date = datetime.strptime(match.group(2), "%d.%m.%Y").date()
|
|
74
65
|
else:
|
|
75
|
-
from_date = (row["
|
|
76
|
-
to_date = row["
|
|
66
|
+
from_date = (row["fee_date"] - pd.tseries.offsets.BDay(1)).date()
|
|
67
|
+
to_date = row["fee_date"]
|
|
77
68
|
|
|
78
69
|
dates = pd.date_range(from_date, to_date, freq="B", inclusive="right")
|
|
79
70
|
for ts in pd.date_range(from_date, to_date, freq="B", inclusive="right"):
|
|
80
71
|
row_copy = row.copy()
|
|
81
|
-
row_copy["
|
|
72
|
+
row_copy["fee_date"] = ts.strftime("%Y-%m-%d")
|
|
82
73
|
row_copy["total_value"] = row["total_value"] / len(dates)
|
|
74
|
+
del row_copy["comment"]
|
|
83
75
|
data.append(row_copy)
|
|
84
76
|
|
|
85
77
|
return {"data": data}
|
|
@@ -5,13 +5,11 @@ from wbportfolio.models import Fees
|
|
|
5
5
|
from .utils import get_perf_fee_isin
|
|
6
6
|
|
|
7
7
|
FIELD_MAP = {
|
|
8
|
-
"
|
|
9
|
-
"EndOfDay": "value_date",
|
|
8
|
+
"Transaction_Date": "fee_date",
|
|
10
9
|
"AccrValueInstrCcy": "total_value",
|
|
11
10
|
"FxRate": "currency_fx_rate",
|
|
12
11
|
"TradingCurrency": "currency__key",
|
|
13
|
-
"#ClientName": "
|
|
14
|
-
"InstrumentName": "comment",
|
|
12
|
+
"#ClientName": "product",
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
PERF_FEES_INSTRUMENT_ISIN = "CH0040602242"
|
|
@@ -27,9 +25,8 @@ def parse(import_source):
|
|
|
27
25
|
df = df.drop(columns=df.columns.difference(FIELD_MAP.values()))
|
|
28
26
|
df.total_value = -df.total_value
|
|
29
27
|
df["transaction_subtype"] = Fees.Type.MANAGEMENT
|
|
30
|
-
df["
|
|
31
|
-
df
|
|
32
|
-
df = df.
|
|
33
|
-
df.linked_product = df.linked_product.apply(lambda x: {"identifier": x})
|
|
28
|
+
df["fee_date"] = pd.to_datetime(df["fee_date"], dayfirst=True).dt.strftime("%Y-%m-%d")
|
|
29
|
+
df = df.dropna(subset=["product"])
|
|
30
|
+
df.product = df["product"].apply(lambda x: {"identifier": x})
|
|
34
31
|
data = df.to_dict("records")
|
|
35
32
|
return {"data": data}
|
|
@@ -10,7 +10,10 @@ def parse(import_source):
|
|
|
10
10
|
with suppress(KeyError):
|
|
11
11
|
series_data = json.loads(import_source.file.read())["payload"]["series"]
|
|
12
12
|
for series in series_data:
|
|
13
|
-
|
|
13
|
+
try:
|
|
14
|
+
isin = series["key"]["priceIdentifier"]
|
|
15
|
+
except KeyError:
|
|
16
|
+
isin = series["priceIdentifier"]
|
|
14
17
|
if Product.objects.filter(isin=isin).exists(): # ensure the timeseries contain data for products we handle
|
|
15
18
|
for point in series["points"]:
|
|
16
19
|
data.append(
|
|
@@ -10,16 +10,16 @@ from wbportfolio.models import Trade
|
|
|
10
10
|
fake = Faker()
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
class
|
|
13
|
+
class OrderProposalTradeResource(FilterModelResource):
|
|
14
14
|
"""
|
|
15
|
-
Trade Resource class to use to import trade from the
|
|
15
|
+
Trade Resource class to use to import trade from the order proposal
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
18
|
DUMMY_FIELD_MAP = {
|
|
19
19
|
"underlying_instrument__isin": lambda: rstr.xeger("([A-Z]{2}[A-Z0-9]{9}[0-9]{1})"),
|
|
20
20
|
"underlying_instrument__ticker": "AAA",
|
|
21
21
|
"underlying_instrument__name": "stock name",
|
|
22
|
-
"weighting":
|
|
22
|
+
"weighting": 0.015,
|
|
23
23
|
"shares": 1000.2536,
|
|
24
24
|
"comment": lambda: fake.sentence(),
|
|
25
25
|
"order": 1,
|
|
@@ -24,7 +24,9 @@ def convert_string_to_number(string):
|
|
|
24
24
|
return 0.0
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def parse_date(date, formats=
|
|
27
|
+
def parse_date(date, formats: list | None = None):
|
|
28
|
+
if formats is None:
|
|
29
|
+
formats = []
|
|
28
30
|
if isinstance(date, int) or isinstance(date, float):
|
|
29
31
|
return xldate_as_datetime(int(date), 0).date()
|
|
30
32
|
if isinstance(date, str):
|
|
@@ -21,7 +21,7 @@ claims AS (
|
|
|
21
21
|
JOIN wbcrm_account AS account
|
|
22
22
|
ON claim.account_id = account.id
|
|
23
23
|
JOIN wbportfolio_trade AS trade
|
|
24
|
-
ON claim.trade_id = trade.
|
|
24
|
+
ON claim.trade_id = trade.id
|
|
25
25
|
WHERE claim.status = 'APPROVED'
|
|
26
26
|
),
|
|
27
27
|
-- CTE for claims, that fetches them with their shares and price at transaction date
|
|
@@ -35,7 +35,7 @@ claims_with_nav AS (
|
|
|
35
35
|
trade.marked_as_internal as marked_as_internal
|
|
36
36
|
FROM wbportfolio_claim AS claims
|
|
37
37
|
JOIN wbportfolio_trade AS trade
|
|
38
|
-
ON claims.trade_id = trade.
|
|
38
|
+
ON claims.trade_id = trade.id
|
|
39
39
|
LEFT JOIN wbcrm_account AS accounts
|
|
40
40
|
ON claims.account_id = accounts.id
|
|
41
41
|
LEFT JOIN LATERAL (
|
|
@@ -4,7 +4,7 @@ import numpy as np
|
|
|
4
4
|
import pandas as pd
|
|
5
5
|
from django.db.models import QuerySet
|
|
6
6
|
from wbfdm.contrib.metric.backends.base import AbstractBackend, Metric
|
|
7
|
-
from wbfdm.contrib.metric.exceptions import
|
|
7
|
+
from wbfdm.contrib.metric.exceptions import MetricInvalidParameterError
|
|
8
8
|
|
|
9
9
|
from wbportfolio.models import AssetPosition, Portfolio
|
|
10
10
|
|
|
@@ -62,7 +62,7 @@ class PortfolioMetricBaseBackend(AbstractBackend[Portfolio]):
|
|
|
62
62
|
try:
|
|
63
63
|
return qs.latest("date").date
|
|
64
64
|
except AssetPosition.DoesNotExist:
|
|
65
|
-
raise
|
|
65
|
+
raise MetricInvalidParameterError() from None
|
|
66
66
|
|
|
67
67
|
def get_queryset(self) -> QuerySet[Portfolio]:
|
|
68
68
|
product_portfolios = super().get_queryset().filter_active_and_tracked()
|
|
@@ -45,7 +45,7 @@ class Migration(migrations.Migration):
|
|
|
45
45
|
migrations.AddConstraint(
|
|
46
46
|
model_name="fees",
|
|
47
47
|
constraint=models.UniqueConstraint(
|
|
48
|
-
fields=("
|
|
48
|
+
fields=("product", "fee_date", "transaction_subtype", "calculated"), name="unique_fees"
|
|
49
49
|
),
|
|
50
50
|
),
|
|
51
51
|
]
|