wbportfolio 1.54.18__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.

@@ -55,5 +55,17 @@ class OrderImportHandler(ImportExportHandler):
55
55
  return self.model.objects.none()
56
56
 
57
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)
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))
@@ -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
@@ -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
 
@@ -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.18
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,7 +119,7 @@ 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
+ wbportfolio/import_export/handlers/orders.py,sha256=5EWm2ocqzc2PBzZRawGIMybkHH7ySjQyHDuGsGBqjIQ,3132
123
123
  wbportfolio/import_export/handlers/portfolio_cash_flow.py,sha256=W7QPNqEvvsq0RS016EAFBp1ezvc6G9Rk-hviRZh8o6Y,2737
124
124
  wbportfolio/import_export/handlers/register.py,sha256=sYyXkE8b1DPZ5monxylZn0kjxLVdNYYZR-p61dwEoDM,2271
125
125
  wbportfolio/import_export/handlers/trade.py,sha256=g2jAYYeuhZv_DuvM6zlROcq6rUlSbGaQ3tO4u6wkSRU,11140
@@ -269,7 +269,7 @@ wbportfolio/models/asset.py,sha256=b0vPt4LwNrxcMiK7UmBKViYnbNNlZzPTagvU5vFuyrc,4
269
269
  wbportfolio/models/custodians.py,sha256=owTiS2Vm5CRKzh9M_P9GOVg-s-ndQ9UvRmw3yZP7cw0,3815
270
270
  wbportfolio/models/exceptions.py,sha256=3ix0tWUO-O6jpz8f07XIwycw2x3JFRoWzjwil8FVA2Q,52
271
271
  wbportfolio/models/indexes.py,sha256=gvW4K9U9Bj8BmVCqFYdWiXvDWhjHINRON8XhNsZUiQY,639
272
- wbportfolio/models/portfolio.py,sha256=MPJY56t-THsT9ym1Fe6k59yHxE6EuabCZQNUN_4t0a8,58405
272
+ wbportfolio/models/portfolio.py,sha256=b0YL4dytrEeHRt7VLn1uJYb_47vRKFYiq9Y_6JLrIJU,58419
273
273
  wbportfolio/models/portfolio_cash_flow.py,sha256=uElG7IJUBY8qvtrXftOoskX6EA-dKgEG1JJdvHeWV7g,7336
274
274
  wbportfolio/models/portfolio_cash_targets.py,sha256=WmgG-etPisZsh2yaFQpz7EkpvAudKBEzqPsO715w52U,1498
275
275
  wbportfolio/models/portfolio_relationship.py,sha256=ZGECiPZiLdlk4uSamOrEfuzO0hduK6OMKJLUSnh5_kc,5190
@@ -289,8 +289,8 @@ wbportfolio/models/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
289
289
  wbportfolio/models/mixins/instruments.py,sha256=SgBreTpa_X3uyCWo7t8B0VaTtl49IjmBMe4Pab6TjAM,6796
290
290
  wbportfolio/models/mixins/liquidity_stress_test.py,sha256=iQVzT3QM7VtHnqfj9gT6KUIe4wC4MJXery-AXJHUYns,58820
291
291
  wbportfolio/models/orders/__init__.py,sha256=EH9UacGR3npBMje5FGTeLOh1xqFBh9kc24WbGmBIA3g,69
292
- wbportfolio/models/orders/order_proposals.py,sha256=U1vLwI_yS2E1TUs04NJGperusqmNyiE42XGlAVoPatk,40196
293
- wbportfolio/models/orders/orders.py,sha256=Rl54ON-b8K277rKeXf53-PHn98nQFYBl4Q_I0Bo6WqE,9438
292
+ wbportfolio/models/orders/order_proposals.py,sha256=uAOPhCUPcCBkAwGPCS0b9JEpwLfnGdJfXM6z8btfzvg,39882
293
+ wbportfolio/models/orders/orders.py,sha256=5XKU5NNLlrB0ydQtVGm_woDagt_Qo7IMZhc7GcrQ_lI,9481
294
294
  wbportfolio/models/reconciliations/__init__.py,sha256=MXH5fZIPGDRBgJkO6wVu_NLRs8fkP1im7G6d-h36lQY,127
295
295
  wbportfolio/models/reconciliations/account_reconciliation_lines.py,sha256=QP6M7hMcyFbuXBa55Y-azui6Dl_WgbzMntEqWzQkbfM,7394
296
296
  wbportfolio/models/reconciliations/account_reconciliations.py,sha256=rofSxetFfEJov6mPyoTvGxELA16HILyJZtQvm_kwYU0,4405
@@ -404,7 +404,7 @@ wbportfolio/tests/models/test_roles.py,sha256=4Cn7WyrA2ztJNeWLk5cy9kYo5XLWMbFSvo
404
404
  wbportfolio/tests/models/test_splits.py,sha256=ytKcHsI_90kj1L4s8It-KEcc24rkDcElxwQ8q0QxEvk,9689
405
405
  wbportfolio/tests/models/utils.py,sha256=ORNJq6NMo1Za22jGZXfTfKeNEnTRlfEt_8SJ6xLaQWg,325
406
406
  wbportfolio/tests/models/orders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
407
- wbportfolio/tests/models/orders/test_order_proposals.py,sha256=7OvZn9IhTNQ66lm2nLXhrdc9OzGuklrnbRgp2WeYQms,30802
407
+ wbportfolio/tests/models/orders/test_order_proposals.py,sha256=FJCP2Gj1eRQb1X7pqiWxGJk4a1qOdVTzE-aRL71fOYk,30773
408
408
  wbportfolio/tests/models/transactions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
409
409
  wbportfolio/tests/models/transactions/test_claim.py,sha256=NG3BKB-FVcIDgHSJHCjImxgMM3ISVUMl24xUPmEcPec,5570
410
410
  wbportfolio/tests/models/transactions/test_fees.py,sha256=tAp18x2wCNQr11LUnLtHNbBDbbX0v1DZnmW7i-cEi5Q,2423
@@ -424,7 +424,7 @@ wbportfolio/tests/viewsets/transactions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J
424
424
  wbportfolio/tests/viewsets/transactions/test_claims.py,sha256=QEZfMAW07dyoZ63t2umSwGOqvaTULfYfbN_F4ZoSAcw,6368
425
425
  wbportfolio/viewsets/__init__.py,sha256=1U1r0TNTUBsDyrZ8k9GbdG0SF0_O1RyORN9AaHJT71o,2436
426
426
  wbportfolio/viewsets/adjustments.py,sha256=ugbX4aFRCaD4Yj1hxL-VIPaNI7GF_wt0FrkN6mq1YjU,1524
427
- wbportfolio/viewsets/assets.py,sha256=MCE81rmDwbMGxO_LsD8AvU9tHWWgi-OkX5ecLFH-KGY,24634
427
+ wbportfolio/viewsets/assets.py,sha256=Pk565r7FOuJw7YsGg9L5SAWJqlg1lQpcjWQjnMYeH4Q,25259
428
428
  wbportfolio/viewsets/assets_and_net_new_money_progression.py,sha256=Jl4vEQP4N2OFL5IGBXoKcj-0qaPviU0I8npvQLw4Io0,4464
429
429
  wbportfolio/viewsets/custodians.py,sha256=CTFqkqVP1R3AV7lhdvcdICxB5DfwDYCyikNSI5kbYEo,2322
430
430
  wbportfolio/viewsets/esg.py,sha256=27MxxdXQH3Cq_1UEYmcrF7htUOg6i81fUpbVQXAAKJI,6985
@@ -533,7 +533,7 @@ wbportfolio/viewsets/configs/titles/roles.py,sha256=9LoJa3jgenXJ5UWRlIErTzdbjpSW
533
533
  wbportfolio/viewsets/configs/titles/trades.py,sha256=29XCLxvY0Xe3a2tjCno3tN2rRXCr9RWpbWnzurJfnYI,1986
534
534
  wbportfolio/viewsets/orders/__init__.py,sha256=N8v9jdEXryOzrLlc7ML3iBCO2lmNXph9_TWoQ7PTvi4,195
535
535
  wbportfolio/viewsets/orders/order_proposals.py,sha256=mw385zzU52nCq8p6xgionh43xLmOn5aX-2BPlCHnqlE,6214
536
- wbportfolio/viewsets/orders/orders.py,sha256=mekuUVkG9-tEtWGrIW2QfgTOuwOZWONiRnbztYUmcVM,10875
536
+ wbportfolio/viewsets/orders/orders.py,sha256=uqEE22CZkcatSWtZgoqskK_7tV8m_-rTfH75_jFb5Lc,10874
537
537
  wbportfolio/viewsets/orders/configs/__init__.py,sha256=5MU57JXiKi32_PicHtiNr7YHmMN020FrlF5NFJf_Wds,94
538
538
  wbportfolio/viewsets/orders/configs/buttons/__init__.py,sha256=EHzNmAfa0UQFITEF-wxj_s4wn3Y5DE3DCbEUmmvCTIs,106
539
539
  wbportfolio/viewsets/orders/configs/buttons/order_proposals.py,sha256=Q_7LrsuLzjSXCIoscFQXMMHl8cuCporDNM5k735W7d8,3584
@@ -551,7 +551,7 @@ wbportfolio/viewsets/transactions/claim.py,sha256=Pb1WftoO-w-ZSTbLRhmQubhy7hgd68
551
551
  wbportfolio/viewsets/transactions/fees.py,sha256=WT2bWWfgozz4_rpyTKX7dgBBTXD-gu0nlsd2Nk2Zh1Q,7028
552
552
  wbportfolio/viewsets/transactions/mixins.py,sha256=WipvJoi5hylkpD0y9VATe30WAcwIHUIroVkK10FYw7k,636
553
553
  wbportfolio/viewsets/transactions/trades.py,sha256=xBgOGaJ8aEg-2RxEJ4FDaBs4SGwuLasun3nhpis0WQY,12363
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,,
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,,