wbportfolio 1.54.5__py2.py3-none-any.whl → 1.54.7__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.

@@ -112,10 +112,10 @@ class AssetPositionIterator:
112
112
  return fx_rate
113
113
 
114
114
  def _get_price(self, val_date: date, instrument: Instrument) -> float | None:
115
- with suppress(KeyError):
116
- if (_p := self._prices[val_date][instrument.id]) is not None:
117
- return _p
118
- return None
115
+ try:
116
+ return self._prices[val_date][instrument.id]
117
+ except KeyError:
118
+ return None
119
119
 
120
120
  def _dict_to_model(self, val_date: date, instrument_id: int, weighting: float, **kwargs) -> "AssetPosition":
121
121
  underlying_quote = self._get_instrument(instrument_id)
@@ -585,7 +585,7 @@ class AssetPosition(ImportMixin, models.Model):
585
585
 
586
586
  if not getattr(self, "currency", None):
587
587
  self.currency = self.underlying_quote.currency
588
- if not self.underlying_quote_price and infer_underlying_quote_price:
588
+ if not self.underlying_quote_price and (infer_underlying_quote_price or not self.initial_price):
589
589
  try:
590
590
  # We get only the instrument price (and don't create it) because we don't want to create product instrument price on asset position propagation
591
591
  # Instead, we decided to opt for a post_save based system that will assign the missing position price when a price is created
@@ -88,22 +88,28 @@ def get_returns(
88
88
  # .astype(float)
89
89
  # .sort_index()
90
90
  # )
91
- kwargs = dict(from_date=from_date, to_date=to_date, values=[MarketData.CLOSE])
91
+ kwargs = dict(from_date=from_date, to_date=to_date, values=[MarketData.CLOSE], apply_fx_rate=False)
92
92
  if to_currency:
93
93
  kwargs["target_currency"] = to_currency.key
94
- prices_df = pd.DataFrame(Instrument.objects.filter(id__in=instrument_ids).dl.market_data(**kwargs))
95
- if prices_df.empty:
94
+ df = pd.DataFrame(Instrument.objects.filter(id__in=instrument_ids).dl.market_data(**kwargs))
95
+ if df.empty:
96
96
  raise InvalidAnalyticPortfolio()
97
- prices_df = prices_df[["instrument_id", "close", "valuation_date"]].pivot(
97
+ fx_rate_df = df[["instrument_id", "fx_rate", "valuation_date"]].pivot(
98
+ index="valuation_date", columns="instrument_id", values="fx_rate"
99
+ )
100
+
101
+ prices_df = df[["instrument_id", "close", "valuation_date"]].pivot(
98
102
  index="valuation_date", columns="instrument_id", values="close"
99
103
  )
100
- ts = pd.bdate_range(prices_df.index.min(), prices_df.index.max(), freq="B")
101
- prices_df = prices_df.reindex(ts)
104
+ price_fx_portfolio_df = fx_rate_df * prices_df
105
+
106
+ ts = pd.bdate_range(price_fx_portfolio_df.index.min(), price_fx_portfolio_df.index.max(), freq="B")
107
+ price_fx_portfolio_df = price_fx_portfolio_df.reindex(ts)
102
108
  if ffill_returns:
103
- prices_df = prices_df.ffill()
104
- prices_df.index = pd.to_datetime(prices_df.index)
105
- returns = prices_to_returns(prices_df, drop_inceptions_nan=False, fill_nan=ffill_returns)
106
- return {ts.date(): row for ts, row in prices_df.replace(np.nan, None).to_dict("index").items()}, returns.replace(
109
+ price_fx_portfolio_df = price_fx_portfolio_df.ffill()
110
+ price_fx_portfolio_df.index = pd.to_datetime(price_fx_portfolio_df.index)
111
+ returns = prices_to_returns(price_fx_portfolio_df, drop_inceptions_nan=False, fill_nan=ffill_returns)
112
+ return {dt: row for dt, row in prices_df.replace(np.nan, None).to_dict("index").items()}, returns.replace(
107
113
  [np.inf, -np.inf, np.nan], 0
108
114
  )
109
115
 
@@ -702,6 +708,7 @@ class Portfolio(DeleteToDisableMixin, WBModel):
702
708
  force_recompute_weighting: bool = False,
703
709
  evaluate_rebalancer: bool = True,
704
710
  changed_weights: dict[int, float] | None = None,
711
+ **kwargs,
705
712
  ):
706
713
  logger.info(f"change at date for {self} at {val_date}")
707
714
 
@@ -743,23 +750,29 @@ class Portfolio(DeleteToDisableMixin, WBModel):
743
750
  self.initial_position_date = val_date
744
751
  self.save()
745
752
 
746
- self.handle_controlling_portfolio_change_at_date(val_date)
753
+ self.handle_controlling_portfolio_change_at_date(
754
+ val_date,
755
+ recompute_weighting=recompute_weighting,
756
+ force_recompute_weighting=force_recompute_weighting,
757
+ changed_weights=changed_weights,
758
+ **kwargs,
759
+ )
747
760
 
748
- def handle_controlling_portfolio_change_at_date(self, val_date: date):
761
+ def handle_controlling_portfolio_change_at_date(self, val_date: date, **kwargs):
749
762
  if self.is_tracked:
750
763
  for rel in PortfolioPortfolioThroughModel.objects.filter(
751
764
  dependency_portfolio=self,
752
765
  type=PortfolioPortfolioThroughModel.Type.PRIMARY,
753
766
  portfolio__is_lookthrough=True,
754
767
  ):
755
- rel.portfolio.compute_lookthrough(val_date)
768
+ rel.portfolio.compute_lookthrough(val_date, **kwargs)
756
769
  for rel in PortfolioPortfolioThroughModel.objects.filter(
757
770
  dependency_portfolio=self, type=PortfolioPortfolioThroughModel.Type.MODEL
758
771
  ):
759
772
  rel.portfolio.evaluate_rebalancing(val_date)
760
773
  for dependent_portfolio in self.get_child_portfolios(val_date):
761
- dependent_portfolio.change_at_date(val_date)
762
- dependent_portfolio.handle_controlling_portfolio_change_at_date(val_date)
774
+ dependent_portfolio.change_at_date(val_date, **kwargs)
775
+ dependent_portfolio.handle_controlling_portfolio_change_at_date(val_date, **kwargs)
763
776
 
764
777
  def evaluate_rebalancing(self, val_date: date):
765
778
  if hasattr(self, "automatic_rebalancer"):
@@ -952,7 +965,7 @@ class Portfolio(DeleteToDisableMixin, WBModel):
952
965
  assets = list(self.assets.filter(date=val_date))
953
966
  return assets
954
967
 
955
- def compute_lookthrough(self, from_date: date, to_date: date | None = None):
968
+ def compute_lookthrough(self, from_date: date, to_date: date | None = None, **kwargs):
956
969
  if not self.primary_portfolio or not self.is_lookthrough:
957
970
  raise ValueError(
958
971
  "Lookthrough position can only be computed on lookthrough portfolio with a primary portfolio"
@@ -968,7 +981,7 @@ class Portfolio(DeleteToDisableMixin, WBModel):
968
981
  positions.add(
969
982
  list(self.primary_portfolio.get_lookthrough_positions(from_date, portfolio_total_asset_value)),
970
983
  )
971
- self.bulk_create_positions(positions, delete_leftovers=True, compute_metrics=True)
984
+ self.bulk_create_positions(positions, delete_leftovers=True, **kwargs)
972
985
 
973
986
  def update_preferred_classification_per_instrument(self):
974
987
  # Function to automatically assign Preferred instrument based on the assets' underlying instruments of the
@@ -1030,7 +1043,7 @@ class Portfolio(DeleteToDisableMixin, WBModel):
1030
1043
  positions: AssetPositionIterator,
1031
1044
  delete_leftovers: bool = False,
1032
1045
  force_save: bool = False,
1033
- compute_metrics: bool = True,
1046
+ compute_metrics: bool = False,
1034
1047
  broadcast_changes_at_date: bool = True,
1035
1048
  **kwargs,
1036
1049
  ):
@@ -361,7 +361,7 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
361
361
  )
362
362
  trade.price = trade.get_price()
363
363
  # if we cannot automatically find a price, we consider the stock is invalid and we sell it
364
- if trade.price is None:
364
+ if not trade.price:
365
365
  trade.price = Decimal("0.0")
366
366
  trade.weighting = -trade_dto.effective_weight
367
367
 
@@ -403,9 +403,6 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
403
403
  while last_trade_proposal and last_trade_proposal.status == TradeProposal.Status.APPROVED:
404
404
  if not last_trade_proposal_created:
405
405
  logger.info(f"Replaying trade proposal {last_trade_proposal}")
406
- last_trade_proposal.portfolio.assets.filter(
407
- date=self.trade_date
408
- ).all().delete() # we delete the existing position and we reapply the trade proposal
409
406
  last_trade_proposal.approve_workflow(
410
407
  silent_exception=True,
411
408
  force_reset_trade=force_reset_trade,
@@ -442,6 +439,13 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
442
439
  broadcast_changes_at_date=broadcast_changes_at_date,
443
440
  evaluate_rebalancer=False,
444
441
  )
442
+ for draft_tp in TradeProposal.objects.filter(
443
+ portfolio=self.portfolio,
444
+ trade_date__gt=last_trade_proposal.trade_date,
445
+ trade_date__lte=next_trade_date,
446
+ status=TradeProposal.Status.DRAFT,
447
+ ):
448
+ draft_tp.reset_trades()
445
449
  if overriding_trade_proposal:
446
450
  last_trade_proposal_created = True
447
451
  last_trade_proposal = overriding_trade_proposal
@@ -574,7 +574,7 @@ class Trade(TransactionMixin, ImportMixin, OrderedModel, models.Model):
574
574
  self.portfolio = self.trade_proposal.portfolio
575
575
  self.transaction_date = self.trade_proposal.trade_date
576
576
  self.value_date = self.trade_proposal.last_effective_date
577
- if self.price is None:
577
+ if not self.price:
578
578
  # we try to get the price if not provided directly from the underlying instrument
579
579
  self.price = self.get_price()
580
580
  if self.trade_proposal and not self.portfolio.only_weighting:
@@ -647,9 +647,11 @@ class Trade(TransactionMixin, ImportMixin, OrderedModel, models.Model):
647
647
  asset.pre_save()
648
648
  return asset
649
649
 
650
- def get_price(self) -> Decimal | None:
651
- with suppress(ValueError):
652
- return Decimal.from_float(self.underlying_instrument.get_price(self.transaction_date))
650
+ def get_price(self) -> Decimal:
651
+ try:
652
+ return self.underlying_instrument.get_price(self.transaction_date)
653
+ except ValueError:
654
+ return Decimal("0")
653
655
 
654
656
  def delete(self, **kwargs):
655
657
  pre_collection.send(sender=self.__class__, instance=self)
@@ -315,9 +315,20 @@ class TradeTradeProposalModelSerializer(TradeModelSerializer):
315
315
  )
316
316
 
317
317
  status = wb_serializers.ChoiceField(default=Trade.Status.DRAFT, choices=Trade.Status.choices)
318
- weighting = wb_serializers.DecimalField(max_digits=7, decimal_places=6)
319
- target_weight = wb_serializers.DecimalField(max_digits=7, decimal_places=6, required=False, default=0)
320
- effective_weight = wb_serializers.DecimalField(read_only=True, max_digits=7, decimal_places=6, default=0)
318
+
319
+ target_weight = wb_serializers.DecimalField(
320
+ max_digits=Trade.TRADE_WEIGHTING_PRECISION + 1,
321
+ decimal_places=Trade.TRADE_WEIGHTING_PRECISION,
322
+ required=False,
323
+ default=0,
324
+ )
325
+ effective_weight = wb_serializers.DecimalField(
326
+ read_only=True,
327
+ max_digits=Trade.TRADE_WEIGHTING_PRECISION + 1,
328
+ decimal_places=Trade.TRADE_WEIGHTING_PRECISION,
329
+ default=0,
330
+ )
331
+
321
332
  effective_shares = wb_serializers.DecimalField(read_only=True, max_digits=16, decimal_places=6, default=0)
322
333
  target_shares = wb_serializers.DecimalField(read_only=True, max_digits=16, decimal_places=6, default=0)
323
334
 
@@ -282,7 +282,13 @@ class TestPortfolioModel(PortfolioTestMixin):
282
282
  dependent_portfolio.depends_on.add(base_portfolio)
283
283
  base_portfolio.change_at_date(weekday)
284
284
 
285
- mock_compute_lookthrough.assert_called_once_with(dependent_portfolio, weekday)
285
+ mock_compute_lookthrough.assert_called_once_with(
286
+ dependent_portfolio,
287
+ weekday,
288
+ recompute_weighting=False,
289
+ force_recompute_weighting=False,
290
+ changed_weights=None,
291
+ )
286
292
 
287
293
  def test_is_active_at_date(
288
294
  self,
@@ -1083,7 +1089,7 @@ class TestPortfolioModel(PortfolioTestMixin):
1083
1089
  a1 = asset_position_factory.build(date=weekday, portfolio=portfolio, underlying_instrument=i1)
1084
1090
 
1085
1091
  # check initial creation
1086
- portfolio.bulk_create_positions(AssetPositionIterator(portfolio).add([a1]))
1092
+ portfolio.bulk_create_positions(AssetPositionIterator(portfolio).add([a1]), compute_metrics=True)
1087
1093
  assert AssetPosition.objects.get(portfolio=portfolio, date=weekday).weighting == a1.weighting
1088
1094
  assert AssetPosition.objects.get(portfolio=portfolio, date=weekday).underlying_instrument == i1
1089
1095
 
@@ -7,7 +7,7 @@ import pytest
7
7
  from faker import Faker
8
8
  from pandas._libs.tslibs.offsets import BDay, BusinessMonthEnd
9
9
 
10
- from wbportfolio.models import Portfolio, RebalancingModel, TradeProposal
10
+ from wbportfolio.models import Portfolio, RebalancingModel, Trade, TradeProposal
11
11
  from wbportfolio.pms.typing import Portfolio as PortfolioDTO
12
12
  from wbportfolio.pms.typing import Position
13
13
 
@@ -273,7 +273,7 @@ class TestTradeProposal:
273
273
 
274
274
  # build the target portfolio
275
275
  target_portfolio = PortfolioDTO(
276
- positions=(
276
+ [
277
277
  Position(
278
278
  underlying_instrument=i2.id,
279
279
  date=trade_proposal.trade_date,
@@ -286,7 +286,7 @@ class TestTradeProposal:
286
286
  weighting=Decimal("0.6"),
287
287
  price=float(p3.net_value),
288
288
  ),
289
- )
289
+ ]
290
290
  )
291
291
 
292
292
  # Reset trades
@@ -304,7 +304,7 @@ class TestTradeProposal:
304
304
 
305
305
  # build the target portfolio
306
306
  new_target_portfolio = PortfolioDTO(
307
- positions=(
307
+ [
308
308
  Position(
309
309
  underlying_instrument=i1.id,
310
310
  date=trade_proposal.trade_date,
@@ -323,7 +323,7 @@ class TestTradeProposal:
323
323
  weighting=Decimal("0.5"),
324
324
  price=float(p3.net_value),
325
325
  ),
326
- )
326
+ ]
327
327
  )
328
328
 
329
329
  trade_proposal.reset_trades(target_portfolio=new_target_portfolio)
@@ -626,5 +626,31 @@ class TestTradeProposal:
626
626
  assert msft_a3.weighting == pytest.approx(target_weight, abs=Decimal("1e-6"))
627
627
  assert apple_a3.weighting == pytest.approx(target_weight, abs=Decimal("1e-6"))
628
628
 
629
- def test_invalid_future_trade_proposal(self, trade_proposal):
630
- pass
629
+ def test_replay_reset_draft_trade_proposal(
630
+ self, instrument, instrument_price_factory, trade_factory, trade_proposal_factory
631
+ ):
632
+ trade_proposal = trade_proposal_factory.create(
633
+ status=TradeProposal.Status.DRAFT, trade_date=date.today() - BDay(2)
634
+ )
635
+ instrument_price_factory.create(instrument=instrument, date=date.today() - BDay(2))
636
+ instrument_price_factory.create(instrument=instrument, date=date.today() - BDay(1))
637
+ instrument_price_factory.create(instrument=instrument, date=date.today())
638
+ trade = trade_factory.create(
639
+ underlying_instrument=instrument,
640
+ trade_proposal=trade_proposal,
641
+ weighting=1,
642
+ status=TradeProposal.Status.DRAFT,
643
+ )
644
+ trade_proposal.submit()
645
+ trade_proposal.approve(replay=False)
646
+ trade_proposal.save()
647
+
648
+ draft_tp = trade_proposal_factory.create(portfolio=trade_proposal.portfolio, trade_date=date.today() - BDay(1))
649
+ assert not Trade.objects.filter(trade_proposal=draft_tp).exists()
650
+
651
+ trade_proposal.replay()
652
+
653
+ assert Trade.objects.filter(trade_proposal=draft_tp).count() == 1
654
+ assert Trade.objects.get(
655
+ trade_proposal=draft_tp, underlying_instrument=trade.underlying_instrument
656
+ ).weighting == Decimal("0")
@@ -99,8 +99,7 @@ class TradeProposalModelViewSet(CloneMixin, RiskCheckViewSetMixin, InternalUserP
99
99
  def reset(self, request, pk=None):
100
100
  trade_proposal = get_object_or_404(TradeProposal, pk=pk)
101
101
  if trade_proposal.status == TradeProposal.Status.DRAFT:
102
- trade_proposal.trades.all().delete()
103
- trade_proposal.reset_trades(force_reset_trade=True)
102
+ trade_proposal.reset_trades()
104
103
  return Response({"send": True})
105
104
  return Response({"status": "Trade proposal is not Draft"}, status=status.HTTP_400_BAD_REQUEST)
106
105
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbportfolio
3
- Version: 1.54.5
3
+ Version: 1.54.7
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  License-File: LICENSE
6
6
  Requires-Dist: cryptography==3.4.*
@@ -253,11 +253,11 @@ wbportfolio/migrations/0080_alter_trade_drift_factor_alter_trade_weighting.py,sh
253
253
  wbportfolio/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
254
254
  wbportfolio/models/__init__.py,sha256=HSpa5xwh_MHQaBpNrq9E0CbdEE5Iq-pDLIsPzZ-TRTg,904
255
255
  wbportfolio/models/adjustments.py,sha256=osXWkJZOiansPWYPyHtl7Z121zDWi7u1YMtrBQtbHVo,10272
256
- wbportfolio/models/asset.py,sha256=RySxJzqQqXTuPOFKDVWC_S-5SDGir-EjrWne8fsns-I,45691
256
+ wbportfolio/models/asset.py,sha256=b0vPt4LwNrxcMiK7UmBKViYnbNNlZzPTagvU5vFuyrc,45685
257
257
  wbportfolio/models/custodians.py,sha256=owTiS2Vm5CRKzh9M_P9GOVg-s-ndQ9UvRmw3yZP7cw0,3815
258
258
  wbportfolio/models/exceptions.py,sha256=3ix0tWUO-O6jpz8f07XIwycw2x3JFRoWzjwil8FVA2Q,52
259
259
  wbportfolio/models/indexes.py,sha256=gvW4K9U9Bj8BmVCqFYdWiXvDWhjHINRON8XhNsZUiQY,639
260
- wbportfolio/models/portfolio.py,sha256=Jp6MGZten0kKzNUmzD_2_NVSPWUY17L4eF3y8bZwynU,57506
260
+ wbportfolio/models/portfolio.py,sha256=8cnnvIWKsBIp1WSfhFHaSB4OLCuFZ3lwJ9koZxIY40s,58080
261
261
  wbportfolio/models/portfolio_cash_flow.py,sha256=uElG7IJUBY8qvtrXftOoskX6EA-dKgEG1JJdvHeWV7g,7336
262
262
  wbportfolio/models/portfolio_cash_targets.py,sha256=WmgG-etPisZsh2yaFQpz7EkpvAudKBEzqPsO715w52U,1498
263
263
  wbportfolio/models/portfolio_relationship.py,sha256=ZGECiPZiLdlk4uSamOrEfuzO0hduK6OMKJLUSnh5_kc,5190
@@ -284,8 +284,8 @@ wbportfolio/models/transactions/claim.py,sha256=SF2FlwG6SRVmA_hT0NbXah5-fYejccWK
284
284
  wbportfolio/models/transactions/dividends.py,sha256=mmOdGWR35yndUMoCuG24Y6BdtxDhSk2gMQ-8LVguqzg,1890
285
285
  wbportfolio/models/transactions/fees.py,sha256=wJtlzbBCAq1UHvv0wqWTE2BEjCF5RMtoaSDS3kODFRo,7112
286
286
  wbportfolio/models/transactions/rebalancing.py,sha256=rwePcmTZOYgfSWnBQcBrZ3DQHRJ3w17hdO_hgrRbbhI,7696
287
- wbportfolio/models/transactions/trade_proposals.py,sha256=FnEZZ7csJMRxGYrUQl0LIDlasEM4An_1gyJhGytn3WE,38104
288
- wbportfolio/models/transactions/trades.py,sha256=7btwshl10-XVOrTYF9PXNyfuHGOttA11D0hkvOLEhgs,34126
287
+ wbportfolio/models/transactions/trade_proposals.py,sha256=2EGi03APBC9QWU99LVRA1e-_-o8AUtEW332Iuxvb-_E,38214
288
+ wbportfolio/models/transactions/trades.py,sha256=vTCoSBAmd6FPFyDRm5fqTqbxwzcH3ApWn0Dz2tkCCR8,34132
289
289
  wbportfolio/models/transactions/transactions.py,sha256=XTcUeMUfkf5XTSZaR2UAyGqCVkOhQYk03_vzHLIgf8Q,3807
290
290
  wbportfolio/pms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
291
291
  wbportfolio/pms/typing.py,sha256=BV4dzazNHdfpfLV99bLVyYGcETmbQSnFV6ipc4fNKfg,8470
@@ -350,7 +350,7 @@ wbportfolio/serializers/transactions/claim.py,sha256=kC4E2RZRrpd9i8tGfoiV-gpWDk3
350
350
  wbportfolio/serializers/transactions/dividends.py,sha256=ADXf9cXe8rq55lC_a8vIzViGLmQ-yDXkgR54k2m-N0w,1814
351
351
  wbportfolio/serializers/transactions/fees.py,sha256=3mBzs6vdfST9roeQB-bmLJhipY7i5jBtAXjoTTE-GOg,2388
352
352
  wbportfolio/serializers/transactions/trade_proposals.py,sha256=2oX04DSyiQ5C0-XkB7c0_wDJ-yC0niRgC72pXPPa_Xc,4014
353
- wbportfolio/serializers/transactions/trades.py,sha256=YVccQpP480P4-0uVaRfnmpPFoIdW2U0c92kJBR_fPLo,16889
353
+ wbportfolio/serializers/transactions/trades.py,sha256=IpyvsMo1laDJbYfYUVcO3t2yMGLQooUDJr_gGbgRLcw,17021
354
354
  wbportfolio/static/wbportfolio/css/macro_review.css,sha256=FAVVO8nModxwPXcTKpcfzVxBGPZGJVK1Xn-0dkSfGyc,233
355
355
  wbportfolio/static/wbportfolio/markdown/documentation/account_holding_reconciliation.md,sha256=MabxOvOne8s5gl6osDoow6-3ghaXLAYg9THWpvy6G5I,921
356
356
  wbportfolio/static/wbportfolio/markdown/documentation/aggregate_asset_position_liquidity.md,sha256=HEgXB7uqmqfty-GBCCXYxrAN-teqmxWuqDLK_liKWVc,1090
@@ -380,7 +380,7 @@ wbportfolio/tests/models/test_merge.py,sha256=sdsjiZsmR6vsUKwTa5kkvL6QTeAZqtd_EP
380
380
  wbportfolio/tests/models/test_portfolio_cash_flow.py,sha256=X8dsXexsb1b0lBiuGzu40ps_Az_1UmmKT0eo1vbXH94,5792
381
381
  wbportfolio/tests/models/test_portfolio_cash_targets.py,sha256=q8QWAwt-kKRkLC0E05GyRhF_TTQXIi8bdHjXVU0fCV0,965
382
382
  wbportfolio/tests/models/test_portfolio_swing_pricings.py,sha256=kr2AOcQkyg2pX3ULjU-o9ye-NVpjMrrfoe-DVbYCbjs,1656
383
- wbportfolio/tests/models/test_portfolios.py,sha256=h9iaSyZiEAfksCM6sFnQk2gci1UMth3dcmEabNhlwr0,53395
383
+ wbportfolio/tests/models/test_portfolios.py,sha256=oSlB1KdSHdR5lXm6G7wpjMjRgM79OrHM2FY_PHhna58,53570
384
384
  wbportfolio/tests/models/test_product_groups.py,sha256=AcdxhurV-n_bBuUsfD1GqVtwLFcs7VI2CRrwzsIUWbU,3337
385
385
  wbportfolio/tests/models/test_products.py,sha256=IcBzw9hrGiWFMRwPBTMukCMWrhqnjOVA2hhb90xYOW8,9580
386
386
  wbportfolio/tests/models/test_roles.py,sha256=4Cn7WyrA2ztJNeWLk5cy9kYo5XLWMbFSvo1O-9JYxeA,3323
@@ -390,7 +390,7 @@ wbportfolio/tests/models/transactions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCe
390
390
  wbportfolio/tests/models/transactions/test_claim.py,sha256=NG3BKB-FVcIDgHSJHCjImxgMM3ISVUMl24xUPmEcPec,5570
391
391
  wbportfolio/tests/models/transactions/test_fees.py,sha256=tAp18x2wCNQr11LUnLtHNbBDbbX0v1DZnmW7i-cEi5Q,2423
392
392
  wbportfolio/tests/models/transactions/test_rebalancing.py,sha256=fZ5tx6kByEGXD6nhapYdvk9HOjYlmjhU2w6KlQJ6QE4,4061
393
- wbportfolio/tests/models/transactions/test_trade_proposals.py,sha256=jnE5Ie0g05TVkuSE9uKsqfidYDU2ZN3FLg1aveLzj9c,28558
393
+ wbportfolio/tests/models/transactions/test_trade_proposals.py,sha256=_EH7N2-oTqgz13NGSahD1d0rIiu93Ce2rcA9A8gUlSg,29800
394
394
  wbportfolio/tests/models/transactions/test_trades.py,sha256=vqvOqUY_uXvBp8YOKR0Wq9ycA2oeeEBhO3dzV7sbXEU,9863
395
395
  wbportfolio/tests/pms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
396
396
  wbportfolio/tests/pms/test_analytics.py,sha256=WHicJBjAjpIRL1-AW2nZ4VD9oJRpMoeH6V1Qx2D95-w,1178
@@ -520,9 +520,9 @@ wbportfolio/viewsets/transactions/claim.py,sha256=Pb1WftoO-w-ZSTbLRhmQubhy7hgd68
520
520
  wbportfolio/viewsets/transactions/fees.py,sha256=WT2bWWfgozz4_rpyTKX7dgBBTXD-gu0nlsd2Nk2Zh1Q,7028
521
521
  wbportfolio/viewsets/transactions/mixins.py,sha256=WipvJoi5hylkpD0y9VATe30WAcwIHUIroVkK10FYw7k,636
522
522
  wbportfolio/viewsets/transactions/rebalancing.py,sha256=6rIrdK0rtKL1afJ-tYfAGdQVTN2MH1kG_yCeVkmyK8k,1263
523
- wbportfolio/viewsets/transactions/trade_proposals.py,sha256=eDEIEUyMP70oEdwscnE8oR7pBStr7IjHNFMxu2JbJlY,6232
523
+ wbportfolio/viewsets/transactions/trade_proposals.py,sha256=kQCojTNKBEyn2NcenL3a9auzBH4sIgLEx8rLAYCGLGg,6161
524
524
  wbportfolio/viewsets/transactions/trades.py,sha256=GHOw5jtcqoaHiRrxxxL29c9405QiPisEn4coGELKDrE,22146
525
- wbportfolio-1.54.5.dist-info/METADATA,sha256=Xk4aluvUoetd-3x6_M3IK2zuIvQsItNvg3713TNUA98,702
526
- wbportfolio-1.54.5.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
527
- wbportfolio-1.54.5.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
528
- wbportfolio-1.54.5.dist-info/RECORD,,
525
+ wbportfolio-1.54.7.dist-info/METADATA,sha256=3cBCW9p4yeH2L3_e9ruCBUDkvWkzcOnj4-SQeK6fUi0,702
526
+ wbportfolio-1.54.7.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
527
+ wbportfolio-1.54.7.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
528
+ wbportfolio-1.54.7.dist-info/RECORD,,