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

@@ -511,7 +511,6 @@ class AssetPosition(ImportMixin, models.Model):
511
511
  self.initial_shares = new_weighting * self.get_portfolio_total_asset_value()
512
512
  else:
513
513
  self.initial_shares = (new_weighting / self.weighting) * self.initial_shares
514
- self.save()
515
514
 
516
515
  def get_portfolio_total_asset_value(self) -> Decimal:
517
516
  return self.portfolio.get_total_asset_value(self.date)
@@ -80,8 +80,6 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
80
80
  if not self.trade_date and self.portfolio.assets.exists():
81
81
  self.trade_date = (self.portfolio.assets.latest("date").date + BDay(1)).date()
82
82
  super().save(*args, **kwargs)
83
- if self.status == TradeProposal.Status.APPROVED:
84
- self.portfolio.change_at_date(self.trade_date)
85
83
 
86
84
  @property
87
85
  def checked_object(self):
@@ -272,35 +270,43 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
272
270
 
273
271
  def replay(self):
274
272
  last_trade_proposal = self
273
+ last_trade_proposal_created = False
275
274
  while last_trade_proposal and last_trade_proposal.status == TradeProposal.Status.APPROVED:
276
- logger.info(f"Replaying trade proposal {last_trade_proposal}")
277
- last_trade_proposal.portfolio.assets.filter(
278
- date=last_trade_proposal.trade_date
279
- ).delete() # we delete the existing position and we reapply the trade proposal
280
- if last_trade_proposal.status == TradeProposal.Status.APPROVED:
281
- logger.info("Reverting trade proposal ...")
282
- last_trade_proposal.revert()
283
- if last_trade_proposal.status == TradeProposal.Status.DRAFT:
284
- if self.rebalancing_model: # if there is no position (for any reason) or we the trade proposal has a rebalancer model attached (trades are computed based on an aglo), we reapply this trade proposal
285
- logger.info(f"Resetting trades from rebalancer model {self.rebalancing_model} ...")
286
- with suppress(
287
- ValidationError
288
- ): # we silent any validation error while setting proposal, because if this happens, we assume the current trade proposal state if valid and we continue to batch compute
289
- self.reset_trades()
290
- logger.info("Submitting trade proposal ...")
291
- last_trade_proposal.submit()
292
- if last_trade_proposal.status == TradeProposal.Status.SUBMIT:
293
- logger.info("Approving trade proposal ...")
294
- last_trade_proposal.approve(replay=False)
295
- last_trade_proposal.save()
275
+ if not last_trade_proposal_created:
276
+ logger.info(f"Replaying trade proposal {last_trade_proposal}")
277
+ last_trade_proposal.portfolio.assets.filter(
278
+ date=last_trade_proposal.trade_date
279
+ ).delete() # we delete the existing position and we reapply the trade proposal
280
+ if last_trade_proposal.status == TradeProposal.Status.APPROVED:
281
+ logger.info("Reverting trade proposal ...")
282
+ last_trade_proposal.revert()
283
+ if last_trade_proposal.status == TradeProposal.Status.DRAFT:
284
+ if self.rebalancing_model: # if there is no position (for any reason) or we the trade proposal has a rebalancer model attached (trades are computed based on an aglo), we reapply this trade proposal
285
+ logger.info(f"Resetting trades from rebalancer model {self.rebalancing_model} ...")
286
+ with suppress(
287
+ ValidationError
288
+ ): # we silent any validation error while setting proposal, because if this happens, we assume the current trade proposal state if valid and we continue to batch compute
289
+ self.reset_trades()
290
+ logger.info("Submitting trade proposal ...")
291
+ last_trade_proposal.submit()
292
+ if last_trade_proposal.status == TradeProposal.Status.SUBMIT:
293
+ logger.info("Approving trade proposal ...")
294
+ last_trade_proposal.approve(replay=False)
295
+ last_trade_proposal.save()
296
296
  next_trade_proposal = last_trade_proposal.next_trade_proposal
297
+
297
298
  next_trade_date = (
298
299
  next_trade_proposal.trade_date - timedelta(days=1) if next_trade_proposal else date.today()
299
300
  )
300
301
  overriding_trade_proposal = last_trade_proposal.portfolio.batch_portfolio(
301
302
  last_trade_proposal.trade_date, next_trade_date
302
303
  )
303
- last_trade_proposal = overriding_trade_proposal or next_trade_proposal
304
+ if overriding_trade_proposal:
305
+ last_trade_proposal_created = True
306
+ last_trade_proposal = overriding_trade_proposal
307
+ else:
308
+ last_trade_proposal_created = False
309
+ last_trade_proposal = next_trade_proposal
304
310
 
305
311
  def get_estimated_shares(self, weight: Decimal, underlying_quote: Instrument) -> Decimal:
306
312
  """
@@ -398,11 +404,14 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
398
404
  },
399
405
  )
400
406
  def submit(self, by=None, description=None, **kwargs):
401
- self.trades.update(comment="", status=Trade.Status.DRAFT)
402
407
  self.reset_trades(target_portfolio=self._build_dto().convert_to_portfolio())
408
+ trades = []
403
409
  for trade in self.trades.all():
404
- trade.submit()
405
- trade.save()
410
+ trade.status = Trade.Status.SUBMIT
411
+ trade.comment = ""
412
+ trades.append(trade)
413
+
414
+ Trade.objects.bulk_update(trades, ["status", "comment"])
406
415
 
407
416
  # If we estimate cash on this trade proposal, we make sure to create the corresponding cash component
408
417
  cash_target_cash_weight, cash_target_cash_shares = self.get_estimated_target_cash(self.portfolio.currency)
@@ -475,11 +484,16 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
475
484
  # We validate trade which will create or update the initial asset positions
476
485
  if not self.portfolio.can_be_rebalanced:
477
486
  raise ValueError("Non-Rebalanceable portfolio cannot be traded manually.")
478
- self.trades.update(status=Trade.Status.SUBMIT)
479
- self.portfolio.assets.filter(date=self.trade_date).delete() # we delete position to avoid having leftovers
487
+ trades = []
488
+ assets = []
480
489
  for trade in self.trades.all():
481
- trade.execute()
482
- trade.save()
490
+ with suppress(ValueError):
491
+ assets.append(trade.to_asset())
492
+ trade.status = Trade.Status.EXECUTED
493
+ trades.append(trade)
494
+
495
+ Trade.objects.bulk_update(trades, ["status"])
496
+ self.portfolio.bulk_create_positions(assets, evaluate_rebalancer=False)
483
497
  if replay and self.portfolio.is_manageable:
484
498
  replay_as_task.delay(self.id)
485
499
 
@@ -584,10 +598,22 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
584
598
  def revert(self, **kwargs):
585
599
  with suppress(KeyError):
586
600
  del self.__dict__["validated_trading_service"]
587
- for trade in self.trades.filter(status=Trade.Status.EXECUTED):
588
- trade.revert()
589
- trade.save()
590
- # replay_as_task.delay(self.id)
601
+ trades = []
602
+ assets = []
603
+ for trade in self.trades.all():
604
+ trade.status = Trade.Status.DRAFT
605
+ trades.append(trade)
606
+ with suppress(AssetPosition.DoesNotExist):
607
+ asset = AssetPosition.unannotated_objects.get(
608
+ underlying_quote=trade.underlying_instrument,
609
+ portfolio=trade.portfolio,
610
+ date=trade.transaction_date,
611
+ is_estimated=False,
612
+ )
613
+ asset.set_weighting(asset.weighting - trade.weighting)
614
+ assets.append(asset)
615
+ Trade.objects.bulk_update(trades, ["status"])
616
+ self.portfolio.bulk_create_positions(assets, evaluate_rebalancer=False)
591
617
 
592
618
  def can_revert(self):
593
619
  errors = dict()
@@ -299,25 +299,47 @@ class Trade(ShareMixin, Transaction, OrderedModel, WBModel):
299
299
  )
300
300
  },
301
301
  )
302
+ def to_asset(self) -> AssetPosition:
303
+ last_underlying_quote_price = self.last_underlying_quote_price
304
+ if not last_underlying_quote_price:
305
+ raise ValueError("No price found")
306
+ asset = AssetPosition(
307
+ underlying_quote=self.underlying_instrument,
308
+ portfolio_created=None,
309
+ portfolio=self.portfolio,
310
+ date=self.transaction_date,
311
+ initial_currency_fx_rate=self.currency_fx_rate,
312
+ weighting=self._target_weight,
313
+ initial_price=self.last_underlying_quote_price.net_value,
314
+ initial_shares=None,
315
+ underlying_quote_price=self.last_underlying_quote_price,
316
+ asset_valuation_date=self.transaction_date,
317
+ currency=self.currency,
318
+ is_estimated=False,
319
+ )
320
+ asset.set_weighting(self._target_weight)
321
+ asset.pre_save()
322
+ return asset
323
+
302
324
  def execute(self, **kwargs):
303
- if self.last_underlying_quote_price:
304
- asset, created = AssetPosition.unannotated_objects.update_or_create(
305
- underlying_quote=self.underlying_instrument,
306
- portfolio_created=None,
307
- portfolio=self.portfolio,
308
- date=self.transaction_date,
325
+ with suppress(ValueError):
326
+ asset = self.to_asset()
327
+ AssetPosition.unannotated_objects.update_or_create(
328
+ underlying_quote=asset.underlying_quote,
329
+ portfolio_created=asset.portfolio_created,
330
+ portfolio=asset.portfolio,
331
+ date=asset.date,
309
332
  defaults={
310
- "initial_currency_fx_rate": self.currency_fx_rate,
311
- "weighting": self._target_weight,
312
- "initial_price": self.last_underlying_quote_price.net_value,
313
- "initial_shares": None,
314
- "underlying_quote_price": self.last_underlying_quote_price,
315
- "asset_valuation_date": self.transaction_date,
316
- "currency": self.currency,
317
- "is_estimated": False,
333
+ "initial_currency_fx_rate": asset.initial_currency_fx_rate,
334
+ "initial_price": asset.initial_price,
335
+ "initial_shares": asset.initial_shares,
336
+ "underlying_quote_price": asset.underlying_quote_price,
337
+ "asset_valuation_date": asset.asset_valuation_date,
338
+ "currency": asset.currency,
339
+ "is_estimated": asset.is_estimated,
340
+ "weighting": asset.weighting,
318
341
  },
319
342
  )
320
- asset.set_weighting(self._target_weight)
321
343
 
322
344
  def can_execute(self):
323
345
  if not self.last_underlying_quote_price:
@@ -402,6 +424,7 @@ class Trade(ShareMixin, Transaction, OrderedModel, WBModel):
402
424
  is_estimated=False,
403
425
  )
404
426
  asset.set_weighting(asset.weighting - self.weighting)
427
+ asset.save()
405
428
 
406
429
  @property
407
430
  def product(self):
@@ -1,6 +1,7 @@
1
1
  from decimal import Decimal
2
2
 
3
3
  from django.core.exceptions import ObjectDoesNotExist
4
+ from wbfdm.models import InstrumentPrice
4
5
 
5
6
  from wbportfolio.models import Trade
6
7
  from wbportfolio.pms.typing import Portfolio, Position
@@ -20,7 +21,7 @@ class CompositeRebalancing(AbstractRebalancingModel):
20
21
  """
21
22
  try:
22
23
  latest_trade_proposal = self.portfolio.trade_proposals.filter(
23
- status="APPROVED", trade_date__lte=self.trade_date
24
+ status="APPROVED", trade_date__lt=self.trade_date
24
25
  ).latest("trade_date")
25
26
  return {
26
27
  v["underlying_instrument"]: v["target_weight"]
@@ -33,7 +34,10 @@ class CompositeRebalancing(AbstractRebalancingModel):
33
34
  return dict()
34
35
 
35
36
  def is_valid(self) -> bool:
36
- return len(self.base_assets.keys()) > 0
37
+ return (
38
+ len(self.base_assets.keys()) > 0
39
+ and InstrumentPrice.objects.filter(date=self.trade_date, instrument__in=self.base_assets.keys()).exists()
40
+ )
37
41
 
38
42
  def get_target_portfolio(self) -> Portfolio:
39
43
  positions = []
@@ -1,5 +1,7 @@
1
1
  from decimal import Decimal
2
2
 
3
+ from wbfdm.models import InstrumentPrice
4
+
3
5
  from wbportfolio.pms.typing import Portfolio
4
6
  from wbportfolio.rebalancing.base import AbstractRebalancingModel
5
7
  from wbportfolio.rebalancing.decorators import register
@@ -7,8 +9,17 @@ from wbportfolio.rebalancing.decorators import register
7
9
 
8
10
  @register("Equally Weighted Rebalancing")
9
11
  class EquallyWeightedRebalancing(AbstractRebalancingModel):
12
+ def __init__(self, *args, **kwargs):
13
+ super().__init__(*args, **kwargs)
14
+ self.assets = self.portfolio.assets.filter(date=self.last_effective_date)
15
+
10
16
  def is_valid(self) -> bool:
11
- return self.portfolio.assets.filter(date=self.last_effective_date).exists()
17
+ return (
18
+ self.assets.exists()
19
+ and InstrumentPrice.objects.filter(
20
+ date=self.trade_date, instrument__in=self.assets.values("underlying_quote")
21
+ ).exists()
22
+ )
12
23
 
13
24
  def get_target_portfolio(self) -> Portfolio:
14
25
  positions = []
@@ -1,3 +1,5 @@
1
+ from wbfdm.models import InstrumentPrice
2
+
1
3
  from wbportfolio.pms.typing import Portfolio
2
4
  from wbportfolio.rebalancing.base import AbstractRebalancingModel
3
5
  from wbportfolio.rebalancing.decorators import register
@@ -18,11 +20,15 @@ class ModelPortfolioRebalancing(AbstractRebalancingModel):
18
20
  return model_portfolio_rel.dependency_portfolio
19
21
 
20
22
  def is_valid(self) -> bool:
21
- return (
22
- self.model_portfolio.assets.filter(date=self.last_effective_date).exists()
23
- if self.model_portfolio
24
- else False
25
- )
23
+ if model_portfolio := self.model_portfolio:
24
+ assets = model_portfolio.get_positions(self.last_effective_date)
25
+ return (
26
+ assets.exists()
27
+ and InstrumentPrice.objects.filter(
28
+ date=self.trade_date, instrument__in=assets.values("underlying_quote")
29
+ ).exists()
30
+ )
31
+ return False
26
32
 
27
33
  def get_target_portfolio(self) -> Portfolio:
28
34
  positions = []
@@ -11,11 +11,15 @@ from wbportfolio.models import AssetPosition, TradeProposal
11
11
 
12
12
  @pytest.mark.django_db
13
13
  class TestRebalancingModel:
14
- def test_get_target_portfolio(self, rebalancing_model, portfolio, weekday, asset_position_factory):
14
+ def test_get_target_portfolio(
15
+ self, rebalancing_model, portfolio, weekday, asset_position_factory, instrument_price_factory
16
+ ):
17
+ trade_date = (weekday + BDay(1)).date()
15
18
  with pytest.raises(ValidationError): # trigger value error because rebalancing not valid (no position yet)
16
- rebalancing_model.get_target_portfolio(portfolio, (weekday + BDay(1)).date(), weekday)
19
+ rebalancing_model.get_target_portfolio(portfolio, trade_date, weekday)
17
20
  a1 = asset_position_factory(weighting=0.7, portfolio=portfolio, date=weekday)
18
21
  a2 = asset_position_factory(weighting=0.3, portfolio=portfolio, date=weekday)
22
+ instrument_price_factory.create(instrument=a1.underlying_quote, date=trade_date)
19
23
  target_portfolio = rebalancing_model.get_target_portfolio(portfolio, (weekday + BDay(1)).date(), weekday)
20
24
  target_positions = target_portfolio.positions_map
21
25
  assert target_positions[a1.underlying_instrument.id].weighting == 0.5
@@ -16,9 +16,11 @@ class TestEquallyWeightedRebalancing:
16
16
 
17
17
  return EquallyWeightedRebalancing(portfolio, (weekday + BDay(1)).date(), weekday)
18
18
 
19
- def test_is_valid(self, portfolio, weekday, model, asset_position_factory):
19
+ def test_is_valid(self, portfolio, weekday, model, asset_position_factory, instrument_price_factory):
20
20
  assert not model.is_valid()
21
- asset_position_factory.create(portfolio=model.portfolio, date=model.last_effective_date)
21
+ a = asset_position_factory.create(portfolio=model.portfolio, date=model.last_effective_date)
22
+ assert not model.is_valid()
23
+ instrument_price_factory.create(instrument=a.underlying_quote, date=model.trade_date)
22
24
  assert model.is_valid()
23
25
 
24
26
  def test_get_target_portfolio(self, portfolio, weekday, model, asset_position_factory):
@@ -38,7 +40,7 @@ class TestModelPortfolioRebalancing:
38
40
 
39
41
  return ModelPortfolioRebalancing(portfolio, (weekday + BDay(1)).date(), weekday)
40
42
 
41
- def test_is_valid(self, portfolio, weekday, model, asset_position_factory):
43
+ def test_is_valid(self, portfolio, weekday, model, asset_position_factory, instrument_price_factory):
42
44
  assert not model.is_valid()
43
45
  asset_position_factory.create(portfolio=model.portfolio, date=model.last_effective_date)
44
46
  assert not model.is_valid()
@@ -48,7 +50,9 @@ class TestModelPortfolioRebalancing:
48
50
  dependency_portfolio=model_portfolio,
49
51
  type=PortfolioPortfolioThroughModel.Type.MODEL,
50
52
  )
51
- asset_position_factory.create(portfolio=model.model_portfolio, date=model.last_effective_date)
53
+ a = asset_position_factory.create(portfolio=model.model_portfolio, date=model.last_effective_date)
54
+ assert not model.is_valid()
55
+ instrument_price_factory.create(instrument=a.underlying_quote, date=model.trade_date)
52
56
  assert model.is_valid()
53
57
 
54
58
  def test_get_target_portfolio(self, portfolio, weekday, model, asset_position_factory):
@@ -76,15 +80,15 @@ class TestCompositeRebalancing:
76
80
 
77
81
  return CompositeRebalancing(portfolio, (weekday + BDay(1)).date(), weekday)
78
82
 
79
- def test_is_valid(self, portfolio, weekday, model, asset_position_factory):
83
+ def test_is_valid(self, portfolio, weekday, model, asset_position_factory, instrument_price_factory):
80
84
  assert not model.is_valid()
81
85
 
82
86
  trade_proposal = TradeProposalFactory.create(
83
- portfolio=model.portfolio, trade_date=model.trade_date, status=TradeProposal.Status.APPROVED
87
+ portfolio=model.portfolio, trade_date=model.last_effective_date, status=TradeProposal.Status.APPROVED
84
88
  )
85
- TradeFactory.create(
89
+ t1 = TradeFactory.create(
86
90
  portfolio=model.portfolio,
87
- transaction_date=model.trade_date,
91
+ transaction_date=model.last_effective_date,
88
92
  transaction_subtype=Trade.Type.BUY,
89
93
  trade_proposal=trade_proposal,
90
94
  weighting=0.7,
@@ -92,23 +96,25 @@ class TestCompositeRebalancing:
92
96
  )
93
97
  TradeFactory.create(
94
98
  portfolio=model.portfolio,
95
- transaction_date=model.trade_date,
99
+ transaction_date=model.last_effective_date,
96
100
  transaction_subtype=Trade.Type.BUY,
97
101
  trade_proposal=trade_proposal,
98
102
  weighting=0.3,
99
103
  status=Trade.Status.EXECUTED,
100
104
  )
105
+ assert not model.is_valid()
106
+ instrument_price_factory.create(instrument=t1.underlying_instrument, date=model.trade_date)
101
107
  assert model.is_valid()
102
108
 
103
109
  def test_get_target_portfolio(self, portfolio, weekday, model, asset_position_factory):
104
110
  trade_proposal = TradeProposalFactory.create(
105
- portfolio=model.portfolio, trade_date=model.trade_date, status=TradeProposal.Status.APPROVED
111
+ portfolio=model.portfolio, trade_date=model.last_effective_date, status=TradeProposal.Status.APPROVED
106
112
  )
107
113
  asset_position_factory(portfolio=portfolio, date=model.last_effective_date) # noise
108
114
  asset_position_factory(portfolio=portfolio, date=model.last_effective_date) # noise
109
115
  t1 = TradeFactory.create(
110
116
  portfolio=model.portfolio,
111
- transaction_date=model.trade_date,
117
+ transaction_date=model.last_effective_date,
112
118
  transaction_subtype=Trade.Type.BUY,
113
119
  trade_proposal=trade_proposal,
114
120
  weighting=0.8,
@@ -116,7 +122,7 @@ class TestCompositeRebalancing:
116
122
  )
117
123
  t2 = TradeFactory.create(
118
124
  portfolio=model.portfolio,
119
- transaction_date=model.trade_date,
125
+ transaction_date=model.last_effective_date,
120
126
  transaction_subtype=Trade.Type.BUY,
121
127
  trade_proposal=trade_proposal,
122
128
  weighting=0.2,
@@ -205,13 +205,11 @@ class TopDownPortfolioCompositionPandasDisplayConfig(DisplayViewConfig):
205
205
  effective_column_label += f" ({self.view.last_effective_date:%Y-%m-%d})"
206
206
  return dp.ListDisplay(
207
207
  fields=[
208
- dp.Field(key="instrument", label="Instrument"),
208
+ dp.Field(key="instrument", label="Instrument", pinned="left"),
209
209
  dp.Field(key="rebalancing_weights", label=rebalancing_column_label),
210
210
  dp.Field(key="effective_weights", label=effective_column_label),
211
211
  ],
212
212
  tree=True,
213
- tree_group_pinned="left",
214
213
  tree_group_field="instrument",
215
- tree_group_label="Instrument",
216
214
  tree_group_parent_pointer="parent_row_id",
217
215
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbportfolio
3
- Version: 1.49.5
3
+ Version: 1.49.6
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  License-File: LICENSE
6
6
  Requires-Dist: cryptography==3.4.*
@@ -245,7 +245,7 @@ wbportfolio/migrations/0076_alter_dividendtransaction_price_and_more.py,sha256=4
245
245
  wbportfolio/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
246
246
  wbportfolio/models/__init__.py,sha256=IIS_PNRxyX2Dcvyk1bcQOUzFt0B9SPC0WlM88CXqj04,881
247
247
  wbportfolio/models/adjustments.py,sha256=osXWkJZOiansPWYPyHtl7Z121zDWi7u1YMtrBQtbHVo,10272
248
- wbportfolio/models/asset.py,sha256=yf4vBPfN1eZlWXG9SowQwr5-goG8rO1yYDitHDLZCBs,37758
248
+ wbportfolio/models/asset.py,sha256=QftWva0GmlStpspcp33TOMZf2t_PTeEhwWdp6xUbQNU,37738
249
249
  wbportfolio/models/custodians.py,sha256=owTiS2Vm5CRKzh9M_P9GOVg-s-ndQ9UvRmw3yZP7cw0,3815
250
250
  wbportfolio/models/exceptions.py,sha256=3ix0tWUO-O6jpz8f07XIwycw2x3JFRoWzjwil8FVA2Q,52
251
251
  wbportfolio/models/indexes.py,sha256=iLYF2gzNzX4GLj_Nh3fybUcAQ1TslnT0wgQ6mN164QI,728
@@ -276,8 +276,8 @@ wbportfolio/models/transactions/dividends.py,sha256=92-jG8bZN9nU9oDubpu-UDH43Ri7
276
276
  wbportfolio/models/transactions/expiry.py,sha256=vnNHdcC1hf2HP4rAbmoGgOfagBYKNFytqOwzOI0MlVI,144
277
277
  wbportfolio/models/transactions/fees.py,sha256=ffvqo8I4A0l5rLi00jJ6sGot0jmnkoxaNsbDzdPLwCg,5712
278
278
  wbportfolio/models/transactions/rebalancing.py,sha256=nrBi6x6PssCtkLtOpV-2OoAHDUKnnYyrM6xH1PmnjSo,7240
279
- wbportfolio/models/transactions/trade_proposals.py,sha256=9ggUkwzDgMKa8cC8dNZ4IHlleiGpRN0WU-oXGDSiuaw,26730
280
- wbportfolio/models/transactions/trades.py,sha256=P9cSTEpZBsqKBvw25XY_RNctTiOBEzCvcuy0r8IkedE,28013
279
+ wbportfolio/models/transactions/trade_proposals.py,sha256=qpjjQMK7G5sX7iiCnXBt07EH-ztxZz-l842yfYrBLQw,27764
280
+ wbportfolio/models/transactions/trades.py,sha256=NA_hUpFKQ4H-w3B0gMZ5IFkZo6WJTH2S7GBUQC1jUpU,28922
281
281
  wbportfolio/models/transactions/transactions.py,sha256=fWoDf0TSV0L0gLUDOQpCRLzjMt1H4MUvUHGEaMsilCc,7027
282
282
  wbportfolio/pms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
283
283
  wbportfolio/pms/typing.py,sha256=b2pBWYt1E8ok-Kqm0lEFIakSnWJ6Ib57z-VX3C3gkQc,6081
@@ -290,10 +290,10 @@ wbportfolio/rebalancing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
290
290
  wbportfolio/rebalancing/base.py,sha256=NwTGZtBm1f35gj5Jp6iTyyFvDT1GSIztN990cKBvYzQ,637
291
291
  wbportfolio/rebalancing/decorators.py,sha256=JhQ2vkcIuGBTGvNmpkQerdw2-vLq-RAb0KAPjOrETkw,515
292
292
  wbportfolio/rebalancing/models/__init__.py,sha256=AQjG7Tu5vlmhqncVoYOjpBKU2UIvgo9FuP2_jD2w-UI,232
293
- wbportfolio/rebalancing/models/composite.py,sha256=uyF1n3NAFprnAZ3Gl5pNJl0GajelqJspC2NufZ7Tf0s,1636
294
- wbportfolio/rebalancing/models/equally_weighted.py,sha256=U29MOHJMQMIg7Y7W_8t5K3nXjaznzt4ArIxQSiv0Xok,863
293
+ wbportfolio/rebalancing/models/composite.py,sha256=XEgK3oMurrE_d_l5uN0stBKRrtvnKQzRWyXNXuBYfmc,1818
294
+ wbportfolio/rebalancing/models/equally_weighted.py,sha256=FCpSKOs49ckNYVgoYIiHB0BqPT9OeCMuFoet4Ixbp-Y,1210
295
295
  wbportfolio/rebalancing/models/market_capitalization_weighted.py,sha256=6ZsR8iJg6l89CsxHqoxJSXlTaj8Pmb8_bFPrXhnxaRs,5295
296
- wbportfolio/rebalancing/models/model_portfolio.py,sha256=XQdvs03-0M9YUnL4DidwZC4E6k-ANCNcZ--T_aaOXTQ,1233
296
+ wbportfolio/rebalancing/models/model_portfolio.py,sha256=DNg9vEDYDUwXTOnIpk26FQSPHC0qxkuvW2sJWX0VodQ,1489
297
297
  wbportfolio/reports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
298
298
  wbportfolio/reports/monthly_position_report.py,sha256=e7BzjDd6eseUOwLwQJXKvWErQ58YnCsznHU2VtR6izM,2981
299
299
  wbportfolio/risk_management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -383,13 +383,13 @@ wbportfolio/tests/models/utils.py,sha256=ORNJq6NMo1Za22jGZXfTfKeNEnTRlfEt_8SJ6xL
383
383
  wbportfolio/tests/models/transactions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
384
384
  wbportfolio/tests/models/transactions/test_claim.py,sha256=NG3BKB-FVcIDgHSJHCjImxgMM3ISVUMl24xUPmEcPec,5570
385
385
  wbportfolio/tests/models/transactions/test_fees.py,sha256=1gp_h_CCC4Z_cWHUgrZCjGAxYuT2u8FZdw0krDpESiY,2801
386
- wbportfolio/tests/models/transactions/test_rebalancing.py,sha256=H1UbxPm-oa00-JJXbBxNutdTKvXXJmuATTab2XsXp44,3900
386
+ wbportfolio/tests/models/transactions/test_rebalancing.py,sha256=fZ5tx6kByEGXD6nhapYdvk9HOjYlmjhU2w6KlQJ6QE4,4061
387
387
  wbportfolio/tests/models/transactions/test_trade_proposals.py,sha256=sqC8uT46Znb03ZwWxIJOaFNBENKunfA5U73xjWbdj80,17053
388
388
  wbportfolio/tests/models/transactions/test_trades.py,sha256=vqvOqUY_uXvBp8YOKR0Wq9ycA2oeeEBhO3dzV7sbXEU,9863
389
389
  wbportfolio/tests/pms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
390
390
  wbportfolio/tests/pms/test_analytics.py,sha256=fAuY1zcXibttFpBh2GhKVyzdYfi1kz_b7SPa9xZQXY0,1086
391
391
  wbportfolio/tests/rebalancing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
392
- wbportfolio/tests/rebalancing/test_models.py,sha256=qyZkNG1WF1lZdYwvjUElkkNey8b5ZMGSlWpzxujCuNg,7549
392
+ wbportfolio/tests/rebalancing/test_models.py,sha256=_gT_7UtpOWceDwT7FbTUW6P6ZpCVLBpgXWM0goIljWc,8090
393
393
  wbportfolio/tests/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
394
394
  wbportfolio/tests/serializers/test_claims.py,sha256=vQrg73xQXRFEgvx3KI9ivFre_wpBFzdO0p0J13PkvdY,582
395
395
  wbportfolio/tests/viewsets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -444,7 +444,7 @@ wbportfolio/viewsets/configs/display/esg.py,sha256=W8uetCPN2TjHtU2kvQjKOmkq7uoaY
444
444
  wbportfolio/viewsets/configs/display/fees.py,sha256=_8HDJADh5bA5VMVPkhKObPmNNdPHWR8Oz23PEgd04F0,5834
445
445
  wbportfolio/viewsets/configs/display/portfolio_cash_flow.py,sha256=CS_UGhKZSXnX-0SZ0RXzbG1WMJm6NX3GUljwIa4RWBk,5025
446
446
  wbportfolio/viewsets/configs/display/portfolio_relationship.py,sha256=8DvsYWFCX29qj5wUf1wCtjlPwzKRd_E7JDuo_CopaJ0,1294
447
- wbportfolio/viewsets/configs/display/portfolios.py,sha256=HFUFYajD7l-7WhwsxEeBp8QvJ5ZZY03EA8Am-DQ6f6w,10726
447
+ wbportfolio/viewsets/configs/display/portfolios.py,sha256=FDL0wucfqnyehrPg_vJqZ1JJ_1S37C_TAbON165-07w,10660
448
448
  wbportfolio/viewsets/configs/display/positions.py,sha256=yolWLxzGPIpSQSiVhVQChURqbomPt5kSjkYrmXT1Mik,3123
449
449
  wbportfolio/viewsets/configs/display/product_groups.py,sha256=PwI-A0_ofShT2pub9-C1HqreiqpHxKMHd51JYwEzvbM,2500
450
450
  wbportfolio/viewsets/configs/display/product_performance.py,sha256=6Mme48JBn_okwClR44dBK2OK26ejvdasDvBa5DI33_0,10070
@@ -521,7 +521,7 @@ wbportfolio/viewsets/transactions/rebalancing.py,sha256=6rIrdK0rtKL1afJ-tYfAGdQV
521
521
  wbportfolio/viewsets/transactions/trade_proposals.py,sha256=iQpC_Thbj56SmM05vPRsF1JZguGBDaTUH3I-_iCHCV0,5958
522
522
  wbportfolio/viewsets/transactions/trades.py,sha256=xeEzx7GP34aBNPlDmiUmT86labsbb8_f1U2RCN1Jatg,21494
523
523
  wbportfolio/viewsets/transactions/transactions.py,sha256=ixDp-nsNA8t_A06rBCT19hOMJHy0iRmdz1XKdV1OwAs,4450
524
- wbportfolio-1.49.5.dist-info/METADATA,sha256=5rVe6Q67fXJTmyNtQe8MP7cfiBkqOQ7z-sRlfgU5f-U,734
525
- wbportfolio-1.49.5.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
526
- wbportfolio-1.49.5.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
527
- wbportfolio-1.49.5.dist-info/RECORD,,
524
+ wbportfolio-1.49.6.dist-info/METADATA,sha256=KMGYS4c7qMFyTTr5hrm23nCftQHaW2JSIRsfp0SetKA,734
525
+ wbportfolio-1.49.6.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
526
+ wbportfolio-1.49.6.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
527
+ wbportfolio-1.49.6.dist-info/RECORD,,