wbportfolio 1.54.17__py2.py3-none-any.whl → 1.54.18__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/import_export/handlers/orders.py +59 -0
- wbportfolio/import_export/handlers/trade.py +42 -70
- wbportfolio/models/orders/orders.py +2 -7
- {wbportfolio-1.54.17.dist-info → wbportfolio-1.54.18.dist-info}/METADATA +1 -1
- {wbportfolio-1.54.17.dist-info → wbportfolio-1.54.18.dist-info}/RECORD +7 -6
- {wbportfolio-1.54.17.dist-info → wbportfolio-1.54.18.dist-info}/WHEEL +0 -0
- {wbportfolio-1.54.17.dist-info → wbportfolio-1.54.18.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
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 not weighting:
|
|
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
|
+
target_portfolio = Portfolio(positions)
|
|
59
|
+
self.order_proposal.reset_orders(target_portfolio=target_portfolio)
|
|
@@ -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.order_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,41 +34,32 @@ 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
|
|
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:
|
|
@@ -160,39 +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
|
-
trades =
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if transaction_date := history.get("transaction_date"):
|
|
181
|
-
trades = trades.filter(transaction_date__lte=transaction_date)
|
|
182
|
-
elif book_date := history.get("book_date"):
|
|
183
|
-
trades = trades.filter(book_date__lte=book_date)
|
|
184
|
-
if underlying_instrument_data := history.get("underlying_instrument"):
|
|
185
|
-
if isinstance(underlying_instrument_data, dict):
|
|
186
|
-
trades = trades.filter(
|
|
187
|
-
**{f"underlying_instrument__{k}": v for k, v in underlying_instrument_data.items()}
|
|
188
|
-
)
|
|
189
|
-
else:
|
|
190
|
-
trades = trades.filter(underlying_instrument__id=underlying_instrument_data)
|
|
191
|
-
|
|
192
|
-
elif "underlying_instruments" in history:
|
|
193
|
-
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
|
+
)
|
|
194
170
|
else:
|
|
195
|
-
|
|
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")
|
|
196
177
|
return trades
|
|
197
178
|
|
|
198
179
|
def _post_processing_objects(
|
|
@@ -201,18 +182,12 @@ class TradeImportHandler(ImportExportHandler):
|
|
|
201
182
|
modified_objs: list[models.Model],
|
|
202
183
|
unmodified_objs: list[models.Model],
|
|
203
184
|
):
|
|
204
|
-
from wbportfolio.models.orders.order_proposals import replay_as_task
|
|
205
|
-
|
|
206
185
|
for instrument in set(
|
|
207
186
|
map(lambda x: x.underlying_instrument, filter(lambda t: t.is_customer_trade, created_objs + modified_objs))
|
|
208
187
|
):
|
|
209
188
|
if instrument.instrument_type.key == "product":
|
|
210
189
|
update_outstanding_shares_as_task.delay(instrument.id)
|
|
211
190
|
|
|
212
|
-
# if the trade import relates to a order proposal, we reset the TP after the import to ensure it contains the deleted positions (often forgotten by user)
|
|
213
|
-
for changed_order_proposal in self.order_proposals:
|
|
214
|
-
replay_as_task.delay(changed_order_proposal.id)
|
|
215
|
-
|
|
216
191
|
def _post_processing_updated_object(self, _object):
|
|
217
192
|
if _object.marked_for_deletion:
|
|
218
193
|
_object.marked_for_deletion = False
|
|
@@ -231,8 +206,5 @@ class TradeImportHandler(ImportExportHandler):
|
|
|
231
206
|
self.import_source.log += (
|
|
232
207
|
f"{trade.transaction_date:%d.%m.%Y}: {trade.shares} {trade.bank} ==> Marked for deletion"
|
|
233
208
|
)
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
else:
|
|
237
|
-
trade.marked_for_deletion = True
|
|
238
|
-
trade.save()
|
|
209
|
+
trade.marked_for_deletion = True
|
|
210
|
+
trade.save()
|
|
@@ -9,13 +9,13 @@ from django.db.models import (
|
|
|
9
9
|
from ordered_model.models import OrderedModel
|
|
10
10
|
from wbcore.contrib.io.mixins import ImportMixin
|
|
11
11
|
|
|
12
|
-
from wbportfolio.import_export.handlers.
|
|
12
|
+
from wbportfolio.import_export.handlers.orders import OrderImportHandler
|
|
13
13
|
from wbportfolio.models.asset import AssetPosition
|
|
14
14
|
from wbportfolio.models.transactions.transactions import TransactionMixin
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class Order(TransactionMixin, ImportMixin, OrderedModel, models.Model):
|
|
18
|
-
import_export_handler_class =
|
|
18
|
+
import_export_handler_class = OrderImportHandler
|
|
19
19
|
|
|
20
20
|
ORDER_WEIGHTING_PRECISION = (
|
|
21
21
|
8 # we need to match the asset position weighting. Skfolio advices using a even smaller number (5)
|
|
@@ -145,11 +145,6 @@ class Order(TransactionMixin, ImportMixin, OrderedModel, models.Model):
|
|
|
145
145
|
self, "target_weight", round(self._effective_weight + self.weighting, self.ORDER_WEIGHTING_PRECISION)
|
|
146
146
|
)
|
|
147
147
|
|
|
148
|
-
@_target_weight.setter
|
|
149
|
-
def _target_weight(self, target_weight):
|
|
150
|
-
self.weighting = Decimal(target_weight) - self._effective_weight
|
|
151
|
-
self.order_type = self.get_type(self.weighting, self._previous_weight, self._target_weight)
|
|
152
|
-
|
|
153
148
|
@property
|
|
154
149
|
@admin.display(description="Target Shares")
|
|
155
150
|
def _target_shares(self) -> Decimal:
|
|
@@ -119,9 +119,10 @@ wbportfolio/import_export/handlers/adjustment.py,sha256=6bdTIYFmc8_HFxcdwtnYwglM
|
|
|
119
119
|
wbportfolio/import_export/handlers/asset_position.py,sha256=UZBDlEK5mxtGu9cZvwiQS6J8GNkMJqDZb7KZ3uwex0g,8698
|
|
120
120
|
wbportfolio/import_export/handlers/dividend.py,sha256=F0oLfNt2B_QQAjHBCRpxa5HSkfkAYdal_NjLJGtVckY,4408
|
|
121
121
|
wbportfolio/import_export/handlers/fees.py,sha256=BOFHAvSTlvVLaxnm6KD_fcza1TlPc02HOR9J0_jjswI,2495
|
|
122
|
+
wbportfolio/import_export/handlers/orders.py,sha256=gECIGItR63oNOWXKgImdjJv6drGgzJOcJG0bu5vygr4,2565
|
|
122
123
|
wbportfolio/import_export/handlers/portfolio_cash_flow.py,sha256=W7QPNqEvvsq0RS016EAFBp1ezvc6G9Rk-hviRZh8o6Y,2737
|
|
123
124
|
wbportfolio/import_export/handlers/register.py,sha256=sYyXkE8b1DPZ5monxylZn0kjxLVdNYYZR-p61dwEoDM,2271
|
|
124
|
-
wbportfolio/import_export/handlers/trade.py,sha256=
|
|
125
|
+
wbportfolio/import_export/handlers/trade.py,sha256=g2jAYYeuhZv_DuvM6zlROcq6rUlSbGaQ3tO4u6wkSRU,11140
|
|
125
126
|
wbportfolio/import_export/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
126
127
|
wbportfolio/import_export/parsers/default_mapping.py,sha256=KrO-X5CvQCeQoBYzFDxavoQGriyUSeI2QDx5ar_zo7A,1405
|
|
127
128
|
wbportfolio/import_export/parsers/jpmorgan/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -289,7 +290,7 @@ wbportfolio/models/mixins/instruments.py,sha256=SgBreTpa_X3uyCWo7t8B0VaTtl49IjmB
|
|
|
289
290
|
wbportfolio/models/mixins/liquidity_stress_test.py,sha256=iQVzT3QM7VtHnqfj9gT6KUIe4wC4MJXery-AXJHUYns,58820
|
|
290
291
|
wbportfolio/models/orders/__init__.py,sha256=EH9UacGR3npBMje5FGTeLOh1xqFBh9kc24WbGmBIA3g,69
|
|
291
292
|
wbportfolio/models/orders/order_proposals.py,sha256=U1vLwI_yS2E1TUs04NJGperusqmNyiE42XGlAVoPatk,40196
|
|
292
|
-
wbportfolio/models/orders/orders.py,sha256=
|
|
293
|
+
wbportfolio/models/orders/orders.py,sha256=Rl54ON-b8K277rKeXf53-PHn98nQFYBl4Q_I0Bo6WqE,9438
|
|
293
294
|
wbportfolio/models/reconciliations/__init__.py,sha256=MXH5fZIPGDRBgJkO6wVu_NLRs8fkP1im7G6d-h36lQY,127
|
|
294
295
|
wbportfolio/models/reconciliations/account_reconciliation_lines.py,sha256=QP6M7hMcyFbuXBa55Y-azui6Dl_WgbzMntEqWzQkbfM,7394
|
|
295
296
|
wbportfolio/models/reconciliations/account_reconciliations.py,sha256=rofSxetFfEJov6mPyoTvGxELA16HILyJZtQvm_kwYU0,4405
|
|
@@ -550,7 +551,7 @@ wbportfolio/viewsets/transactions/claim.py,sha256=Pb1WftoO-w-ZSTbLRhmQubhy7hgd68
|
|
|
550
551
|
wbportfolio/viewsets/transactions/fees.py,sha256=WT2bWWfgozz4_rpyTKX7dgBBTXD-gu0nlsd2Nk2Zh1Q,7028
|
|
551
552
|
wbportfolio/viewsets/transactions/mixins.py,sha256=WipvJoi5hylkpD0y9VATe30WAcwIHUIroVkK10FYw7k,636
|
|
552
553
|
wbportfolio/viewsets/transactions/trades.py,sha256=xBgOGaJ8aEg-2RxEJ4FDaBs4SGwuLasun3nhpis0WQY,12363
|
|
553
|
-
wbportfolio-1.54.
|
|
554
|
-
wbportfolio-1.54.
|
|
555
|
-
wbportfolio-1.54.
|
|
556
|
-
wbportfolio-1.54.
|
|
554
|
+
wbportfolio-1.54.18.dist-info/METADATA,sha256=az3x7f9w0PGxU-naki13snEuZMzaKqZhC6Dv82qwcJE,703
|
|
555
|
+
wbportfolio-1.54.18.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
|
|
556
|
+
wbportfolio-1.54.18.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
|
|
557
|
+
wbportfolio-1.54.18.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|