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.

@@ -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 OrderProposal, 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 order_proposal_id := data.pop("order_proposal_id", None):
46
- order_proposal = OrderProposal.objects.get(id=order_proposal_id)
47
- self.order_proposals.add(order_proposal)
48
- data["value_date"] = order_proposal.last_effective_date
49
- data["transaction_date"] = order_proposal.trade_date
50
- data["order_proposal"] = order_proposal
51
- data["portfolio"] = order_proposal.portfolio
52
- data["status"] = "DRAFT"
53
- else:
54
- if external_id_alternative := data.get("external_id_alternative", None):
55
- data["external_id_alternative"] = str(external_id_alternative)
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
- if currency_data := data.get("currency", None):
67
- data["currency"] = self.currency_handler.process_object(currency_data, read_only=True)[0]
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
- if register_data := data.get("register", None):
70
- data["register"] = self.register_handler.process_object(register_data)[0]
59
+ if register_data := data.get("register", None):
60
+ data["register"] = self.register_handler.process_object(register_data)[0]
71
61
 
72
- data["marked_for_deletion"] = data.get("marked_for_deletion", False)
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
- from wbportfolio.models.orders.order_proposals import OrderProposal
164
-
165
- if order_proposal_id := history.get("order_proposal_id"):
166
- # if a order proposal is provided, we delete the existing history first as otherwise, it would mess with the target weight computation
167
- order_proposal = OrderProposal.objects.get(id=order_proposal_id)
168
- order_proposal.trades.all().delete()
169
- order_proposal.reset_orders()
170
- trades = self.model.objects.none()
171
- else:
172
- trades = self.model.objects.filter(
173
- exclude_from_history=False,
174
- pending=False,
175
- transaction_subtype__in=[
176
- self.model.Type.SUBSCRIPTION,
177
- self.model.Type.REDEMPTION,
178
- ], # we cannot exclude marked for deleted trade because otherwise they are never consider in the history
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
- raise ValueError("We cannot estimate history without at least the underlying instrument")
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
- if trade.order_proposal:
235
- trade.delete()
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.trade import TradeImportHandler
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 = TradeImportHandler
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbportfolio
3
- Version: 1.54.17
3
+ Version: 1.54.18
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  License-File: LICENSE
6
6
  Requires-Dist: cryptography==3.4.*
@@ -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=X_P8T3ZBGYpPmKrNmgAL9HZ-yfdnIh23LupsLMDxJ4o,12814
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=hVVw7NAFmAFHosMMs39V9DjGmWyFC_msSxF8rpDDG60,9683
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.17.dist-info/METADATA,sha256=iv55pLD6PfL1brFSH400eFZJ1u_BieHNt_tO2nrQ3Us,703
554
- wbportfolio-1.54.17.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
555
- wbportfolio-1.54.17.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
556
- wbportfolio-1.54.17.dist-info/RECORD,,
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,,