wbportfolio 1.54.17__py2.py3-none-any.whl → 1.54.19__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,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 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
+ 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.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()
@@ -91,14 +91,11 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
91
91
  ]
92
92
 
93
93
  def save(self, *args, **kwargs):
94
- if not self.trade_date and self.portfolio.assets.exists():
95
- self.trade_date = (self.portfolio.assets.latest("date").date + BDay(1)).date()
96
-
97
94
  # if a order proposal is created before the existing earliest order proposal, we automatically shift the linked instruments inception date to allow automatic NAV computation since the new inception date
98
95
  if not self.portfolio.order_proposals.filter(trade_date__lt=self.trade_date).exists():
99
- new_inception_date = (self.trade_date + BDay(1)).date()
100
- self.portfolio.instruments.filter(inception_date__gt=new_inception_date).update(
101
- inception_date=new_inception_date
96
+ # we need to set the inception date as the first order proposal trade date (and thus, the first position date). We expect a NAV at 100 then
97
+ self.portfolio.instruments.filter(inception_date__gt=self.trade_date).update(
98
+ inception_date=self.trade_date
102
99
  )
103
100
  super().save(*args, **kwargs)
104
101
 
@@ -157,6 +154,12 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
157
154
  return future_proposals.earliest("trade_date")
158
155
  return None
159
156
 
157
+ @property
158
+ def cash_component(self) -> Cash:
159
+ return Cash.objects.get_or_create(
160
+ currency=self.portfolio.currency, defaults={"is_cash": True, "name": self.portfolio.currency.title}
161
+ )[0]
162
+
160
163
  def get_orders(self):
161
164
  base_qs = self.orders.all().annotate(
162
165
  last_effective_date=Subquery(
@@ -215,7 +218,6 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
215
218
  ),
216
219
  default=models.F("effective_weight"),
217
220
  ),
218
- target_weight=models.F("effective_weight") + models.F("weighting"),
219
221
  )
220
222
  return orders.annotate(
221
223
  has_warnings=models.Case(
@@ -287,7 +289,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
287
289
  )
288
290
  )
289
291
  total_weighting += weighting
290
- if with_cash and (cash_weight := Decimal("1") - total_weighting):
292
+ if portfolio and with_cash and (cash_weight := Decimal("1") - total_weighting):
291
293
  cash_position = self.get_estimated_target_cash(target_cash_weight=cash_weight)
292
294
  positions.append(cash_position._build_dto())
293
295
  return PortfolioDTO(positions)
@@ -430,7 +432,6 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
430
432
  approve_automatically: bool = True,
431
433
  silent_exception: bool = False,
432
434
  force_reset_order: bool = False,
433
- broadcast_changes_at_date: bool = True,
434
435
  **reset_order_kwargs,
435
436
  ):
436
437
  if self.status == OrderProposal.Status.APPROVED:
@@ -453,7 +454,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
453
454
  if self.status == OrderProposal.Status.SUBMIT:
454
455
  logger.info("Approving order proposal ...")
455
456
  if approve_automatically and self.portfolio.can_be_rebalanced:
456
- self.approve(replay=False, broadcast_changes_at_date=broadcast_changes_at_date)
457
+ self.approve(replay=False)
457
458
 
458
459
  def replay(self, broadcast_changes_at_date: bool = True):
459
460
  last_order_proposal = self
@@ -461,11 +462,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
461
462
  while last_order_proposal and last_order_proposal.status == OrderProposal.Status.APPROVED:
462
463
  logger.info(f"Replaying order proposal {last_order_proposal}")
463
464
  if not last_order_proposal_created:
464
- last_order_proposal.approve_workflow(
465
- silent_exception=True,
466
- force_reset_order=True,
467
- broadcast_changes_at_date=broadcast_changes_at_date,
468
- )
465
+ last_order_proposal.approve_workflow(silent_exception=True, force_reset_order=True)
469
466
  last_order_proposal.save()
470
467
  if last_order_proposal.status != OrderProposal.Status.APPROVED:
471
468
  break
@@ -575,8 +572,6 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
575
572
  """
576
573
  # Retrieve orders with base information
577
574
  orders = self.get_orders()
578
- currency = self.portfolio.currency
579
-
580
575
  # Calculate the total target weight of all orders
581
576
  total_target_weight = orders.exclude(underlying_instrument__is_cash=True).aggregate(
582
577
  s=models.Sum("target_weight")
@@ -589,9 +584,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
589
584
  total_target_shares = Decimal(0)
590
585
 
591
586
  # Get or create a cash component for the portfolio's currency
592
- cash_component = Cash.objects.get_or_create(
593
- currency=currency, defaults={"is_cash": True, "name": currency.title}
594
- )[0]
587
+ cash_component = self.cash_component
595
588
  # If the portfolio is not only weighting-based, estimate the target shares for the cash component
596
589
  if not self.portfolio.only_weighting:
597
590
  # Estimate the target shares for the cash component
@@ -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)
@@ -80,7 +80,7 @@ class Order(TransactionMixin, ImportMixin, OrderedModel, models.Model):
80
80
 
81
81
  if not self.price:
82
82
  warnings.append(f"No price for {self.underlying_instrument.computed_str}")
83
- if abs(self._target_weight) < 1e-8:
83
+ if self._target_weight < 1e-8: # any value below -1e8 will be considered zero
84
84
  warnings.append(f"Negative target weight for {self.underlying_instrument.computed_str}")
85
85
  return warnings
86
86
 
@@ -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:
@@ -713,6 +713,7 @@ class Portfolio(DeleteToDisableMixin, WBModel):
713
713
  force_recompute_weighting: bool = False,
714
714
  evaluate_rebalancer: bool = True,
715
715
  changed_weights: dict[int, float] | None = None,
716
+ broadcast_changes_at_date: bool = True,
716
717
  **kwargs,
717
718
  ):
718
719
  logger.info(f"change at date for {self} at {val_date}")
@@ -754,14 +755,14 @@ class Portfolio(DeleteToDisableMixin, WBModel):
754
755
  if not self.initial_position_date or self.initial_position_date > val_date:
755
756
  self.initial_position_date = val_date
756
757
  self.save()
757
-
758
- self.handle_controlling_portfolio_change_at_date(
759
- val_date,
760
- recompute_weighting=recompute_weighting,
761
- force_recompute_weighting=force_recompute_weighting,
762
- changed_weights=changed_weights,
763
- **kwargs,
764
- )
758
+ if broadcast_changes_at_date:
759
+ self.handle_controlling_portfolio_change_at_date(
760
+ val_date,
761
+ recompute_weighting=recompute_weighting,
762
+ force_recompute_weighting=force_recompute_weighting,
763
+ changed_weights=changed_weights,
764
+ **kwargs,
765
+ )
765
766
 
766
767
  def handle_controlling_portfolio_change_at_date(self, val_date: date, **kwargs):
767
768
  if self.is_tracked:
@@ -866,7 +867,7 @@ class Portfolio(DeleteToDisableMixin, WBModel):
866
867
  if stop_at_rebalancing:
867
868
  break
868
869
  next_weights = {
869
- trade.underlying_instrument: float(trade._target_weight)
870
+ trade.underlying_instrument.id: float(trade._target_weight)
870
871
  for trade in last_order_proposal.get_orders()
871
872
  }
872
873
  positions.add((to_date, next_weights), is_estimated=False)
@@ -1048,7 +1049,6 @@ class Portfolio(DeleteToDisableMixin, WBModel):
1048
1049
  delete_leftovers: bool = False,
1049
1050
  force_save: bool = False,
1050
1051
  compute_metrics: bool = False,
1051
- broadcast_changes_at_date: bool = True,
1052
1052
  **kwargs,
1053
1053
  ):
1054
1054
  if positions:
@@ -1097,10 +1097,9 @@ class Portfolio(DeleteToDisableMixin, WBModel):
1097
1097
  basket_id=self.id,
1098
1098
  basket_content_type_id=ContentType.objects.get_for_model(Portfolio).id,
1099
1099
  )
1100
- if broadcast_changes_at_date:
1101
- for update_date, changed_weights in positions.get_weights().items():
1102
- kwargs.pop("changed_weights", None)
1103
- self.change_at_date(update_date, changed_weights=changed_weights, **kwargs)
1100
+ for update_date, changed_weights in positions.get_weights().items():
1101
+ kwargs.pop("changed_weights", None)
1102
+ self.change_at_date(update_date, changed_weights=changed_weights, **kwargs)
1104
1103
 
1105
1104
  @classmethod
1106
1105
  def _get_or_create_portfolio(cls, instrument_handler, portfolio_data):
@@ -474,11 +474,11 @@ class TestOrderProposal:
474
474
  instrument.portfolios.add(portfolio)
475
475
  tp = order_proposal_factory.create(portfolio=portfolio)
476
476
  instrument.refresh_from_db()
477
- assert instrument.inception_date == (tp.trade_date + BDay(1)).date()
477
+ assert instrument.inception_date == tp.trade_date
478
478
 
479
- tp2 = order_proposal_factory.create(portfolio=portfolio, trade_date=tp.trade_date - BDay(1))
479
+ tp2 = order_proposal_factory.create(portfolio=portfolio, trade_date=(tp.trade_date - BDay(1)).date())
480
480
  instrument.refresh_from_db()
481
- assert instrument.inception_date == (tp2.trade_date + BDay(1)).date()
481
+ assert instrument.inception_date == tp2.trade_date
482
482
 
483
483
  def test_get_round_lot_size(self, order_proposal, instrument):
484
484
  # without a round lot size, we expect no normalization of shares
@@ -484,9 +484,24 @@ class AssetPositionUnderlyingInstrumentChartViewSet(UserPortfolioRequestPermissi
484
484
  fig = make_subplots(specs=[[{"secondary_y": True}]])
485
485
  fig = get_default_timeserie_figure(fig)
486
486
  if queryset.exists():
487
- df_price = (pd.DataFrame(queryset.values("date", "price_fx_usd"))).groupby("date").first()
487
+ df_weight = pd.DataFrame(queryset.values("date", "weighting", "portfolio__name"))
488
+ df_weight = df_weight.where(pd.notnull(df_weight), 0)
489
+ df_weight = df_weight.groupby(["date", "portfolio__name"]).sum().reset_index()
490
+ min_date = df_weight["date"].min()
491
+ max_date = df_weight["date"].max()
492
+
493
+ df_price = (
494
+ pd.DataFrame(
495
+ self.instrument.prices.filter_only_valid_prices()
496
+ .annotate_base_data()
497
+ .filter(date__gte=min_date, date__lte=max_date)
498
+ .values_list("date", "net_value_usd"),
499
+ columns=["date", "price_fx_usd"],
500
+ )
501
+ .set_index("date")
502
+ .sort_index()
503
+ )
488
504
 
489
- df_price = df_price.where(pd.notnull(df_price), 0)
490
505
  fig.add_trace(
491
506
  go.Scatter(
492
507
  x=df_price.index, y=df_price.price_fx_usd, mode="lines", marker_color="green", name="Price"
@@ -91,7 +91,6 @@ class OrderOrderProposalModelViewSet(
91
91
  sum_target_total_value_fx_portfolio=Sum(F("target_total_value_fx_portfolio")),
92
92
  sum_effective_total_value_fx_portfolio=Sum(F("effective_total_value_fx_portfolio")),
93
93
  )
94
-
95
94
  # weights aggregates
96
95
  cash_sum_effective_weight = Decimal("1.0") - noncash_aggregates["sum_effective_weight"]
97
96
  cash_sum_target_cash_weight = Decimal("1.0") - noncash_aggregates["sum_target_weight"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbportfolio
3
- Version: 1.54.17
3
+ Version: 1.54.19
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=5EWm2ocqzc2PBzZRawGIMybkHH7ySjQyHDuGsGBqjIQ,3132
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
@@ -268,7 +269,7 @@ wbportfolio/models/asset.py,sha256=b0vPt4LwNrxcMiK7UmBKViYnbNNlZzPTagvU5vFuyrc,4
268
269
  wbportfolio/models/custodians.py,sha256=owTiS2Vm5CRKzh9M_P9GOVg-s-ndQ9UvRmw3yZP7cw0,3815
269
270
  wbportfolio/models/exceptions.py,sha256=3ix0tWUO-O6jpz8f07XIwycw2x3JFRoWzjwil8FVA2Q,52
270
271
  wbportfolio/models/indexes.py,sha256=gvW4K9U9Bj8BmVCqFYdWiXvDWhjHINRON8XhNsZUiQY,639
271
- wbportfolio/models/portfolio.py,sha256=MPJY56t-THsT9ym1Fe6k59yHxE6EuabCZQNUN_4t0a8,58405
272
+ wbportfolio/models/portfolio.py,sha256=b0YL4dytrEeHRt7VLn1uJYb_47vRKFYiq9Y_6JLrIJU,58419
272
273
  wbportfolio/models/portfolio_cash_flow.py,sha256=uElG7IJUBY8qvtrXftOoskX6EA-dKgEG1JJdvHeWV7g,7336
273
274
  wbportfolio/models/portfolio_cash_targets.py,sha256=WmgG-etPisZsh2yaFQpz7EkpvAudKBEzqPsO715w52U,1498
274
275
  wbportfolio/models/portfolio_relationship.py,sha256=ZGECiPZiLdlk4uSamOrEfuzO0hduK6OMKJLUSnh5_kc,5190
@@ -288,8 +289,8 @@ wbportfolio/models/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
288
289
  wbportfolio/models/mixins/instruments.py,sha256=SgBreTpa_X3uyCWo7t8B0VaTtl49IjmBMe4Pab6TjAM,6796
289
290
  wbportfolio/models/mixins/liquidity_stress_test.py,sha256=iQVzT3QM7VtHnqfj9gT6KUIe4wC4MJXery-AXJHUYns,58820
290
291
  wbportfolio/models/orders/__init__.py,sha256=EH9UacGR3npBMje5FGTeLOh1xqFBh9kc24WbGmBIA3g,69
291
- wbportfolio/models/orders/order_proposals.py,sha256=U1vLwI_yS2E1TUs04NJGperusqmNyiE42XGlAVoPatk,40196
292
- wbportfolio/models/orders/orders.py,sha256=hVVw7NAFmAFHosMMs39V9DjGmWyFC_msSxF8rpDDG60,9683
292
+ wbportfolio/models/orders/order_proposals.py,sha256=uAOPhCUPcCBkAwGPCS0b9JEpwLfnGdJfXM6z8btfzvg,39882
293
+ wbportfolio/models/orders/orders.py,sha256=5XKU5NNLlrB0ydQtVGm_woDagt_Qo7IMZhc7GcrQ_lI,9481
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
@@ -403,7 +404,7 @@ wbportfolio/tests/models/test_roles.py,sha256=4Cn7WyrA2ztJNeWLk5cy9kYo5XLWMbFSvo
403
404
  wbportfolio/tests/models/test_splits.py,sha256=ytKcHsI_90kj1L4s8It-KEcc24rkDcElxwQ8q0QxEvk,9689
404
405
  wbportfolio/tests/models/utils.py,sha256=ORNJq6NMo1Za22jGZXfTfKeNEnTRlfEt_8SJ6xLaQWg,325
405
406
  wbportfolio/tests/models/orders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
406
- wbportfolio/tests/models/orders/test_order_proposals.py,sha256=7OvZn9IhTNQ66lm2nLXhrdc9OzGuklrnbRgp2WeYQms,30802
407
+ wbportfolio/tests/models/orders/test_order_proposals.py,sha256=FJCP2Gj1eRQb1X7pqiWxGJk4a1qOdVTzE-aRL71fOYk,30773
407
408
  wbportfolio/tests/models/transactions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
408
409
  wbportfolio/tests/models/transactions/test_claim.py,sha256=NG3BKB-FVcIDgHSJHCjImxgMM3ISVUMl24xUPmEcPec,5570
409
410
  wbportfolio/tests/models/transactions/test_fees.py,sha256=tAp18x2wCNQr11LUnLtHNbBDbbX0v1DZnmW7i-cEi5Q,2423
@@ -423,7 +424,7 @@ wbportfolio/tests/viewsets/transactions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J
423
424
  wbportfolio/tests/viewsets/transactions/test_claims.py,sha256=QEZfMAW07dyoZ63t2umSwGOqvaTULfYfbN_F4ZoSAcw,6368
424
425
  wbportfolio/viewsets/__init__.py,sha256=1U1r0TNTUBsDyrZ8k9GbdG0SF0_O1RyORN9AaHJT71o,2436
425
426
  wbportfolio/viewsets/adjustments.py,sha256=ugbX4aFRCaD4Yj1hxL-VIPaNI7GF_wt0FrkN6mq1YjU,1524
426
- wbportfolio/viewsets/assets.py,sha256=MCE81rmDwbMGxO_LsD8AvU9tHWWgi-OkX5ecLFH-KGY,24634
427
+ wbportfolio/viewsets/assets.py,sha256=Pk565r7FOuJw7YsGg9L5SAWJqlg1lQpcjWQjnMYeH4Q,25259
427
428
  wbportfolio/viewsets/assets_and_net_new_money_progression.py,sha256=Jl4vEQP4N2OFL5IGBXoKcj-0qaPviU0I8npvQLw4Io0,4464
428
429
  wbportfolio/viewsets/custodians.py,sha256=CTFqkqVP1R3AV7lhdvcdICxB5DfwDYCyikNSI5kbYEo,2322
429
430
  wbportfolio/viewsets/esg.py,sha256=27MxxdXQH3Cq_1UEYmcrF7htUOg6i81fUpbVQXAAKJI,6985
@@ -532,7 +533,7 @@ wbportfolio/viewsets/configs/titles/roles.py,sha256=9LoJa3jgenXJ5UWRlIErTzdbjpSW
532
533
  wbportfolio/viewsets/configs/titles/trades.py,sha256=29XCLxvY0Xe3a2tjCno3tN2rRXCr9RWpbWnzurJfnYI,1986
533
534
  wbportfolio/viewsets/orders/__init__.py,sha256=N8v9jdEXryOzrLlc7ML3iBCO2lmNXph9_TWoQ7PTvi4,195
534
535
  wbportfolio/viewsets/orders/order_proposals.py,sha256=mw385zzU52nCq8p6xgionh43xLmOn5aX-2BPlCHnqlE,6214
535
- wbportfolio/viewsets/orders/orders.py,sha256=mekuUVkG9-tEtWGrIW2QfgTOuwOZWONiRnbztYUmcVM,10875
536
+ wbportfolio/viewsets/orders/orders.py,sha256=uqEE22CZkcatSWtZgoqskK_7tV8m_-rTfH75_jFb5Lc,10874
536
537
  wbportfolio/viewsets/orders/configs/__init__.py,sha256=5MU57JXiKi32_PicHtiNr7YHmMN020FrlF5NFJf_Wds,94
537
538
  wbportfolio/viewsets/orders/configs/buttons/__init__.py,sha256=EHzNmAfa0UQFITEF-wxj_s4wn3Y5DE3DCbEUmmvCTIs,106
538
539
  wbportfolio/viewsets/orders/configs/buttons/order_proposals.py,sha256=Q_7LrsuLzjSXCIoscFQXMMHl8cuCporDNM5k735W7d8,3584
@@ -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.19.dist-info/METADATA,sha256=M3wWcN_z7d5N3OaGBKLieTLkg2bJ_cL3KdXsBeFjPKk,703
555
+ wbportfolio-1.54.19.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
556
+ wbportfolio-1.54.19.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
557
+ wbportfolio-1.54.19.dist-info/RECORD,,