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

@@ -100,15 +100,16 @@ class AssetPositionIterator:
100
100
  self._instruments[instrument_id] = instrument
101
101
  return instrument
102
102
 
103
- def _get_fx_rate(self, val_date: date, currency: Currency) -> CurrencyFXRates:
103
+ def _get_fx_rate(self, val_date: date, currency: Currency) -> CurrencyFXRates | None:
104
104
  try:
105
105
  return self._fx_rates[val_date][currency]
106
106
  except KeyError:
107
- fx_rate = CurrencyFXRates.objects.get_or_create(
108
- currency=currency, date=val_date, defaults={"value": Decimal(0)}
109
- )[0] # we create a fx rate anyway to not fail the position. The fx rate expect to be there later on
110
- self._fx_rates[val_date][currency] = fx_rate
111
- return fx_rate
107
+ with suppress(CurrencyFXRates.DoesNotExist):
108
+ fx_rate = CurrencyFXRates.objects.get(
109
+ currency=currency, date=val_date
110
+ ) # we create a fx rate anyway to not fail the position. The fx rate expect to be there later on
111
+ self._fx_rates[val_date][currency] = fx_rate
112
+ return fx_rate
112
113
 
113
114
  def _get_price(self, val_date: date, instrument: Instrument) -> float | None:
114
115
  try:
@@ -170,7 +171,7 @@ class AssetPositionIterator:
170
171
  position.initial_shares += existing_position.initial_shares
171
172
  # ensure the position portfolio is the iterator portfolio (could be different when computing look-through for instance)
172
173
  position.portfolio = self.portfolio
173
- if position.initial_price is not None:
174
+ if position.initial_price is not None and position.initial_currency_fx_rate is not None:
174
175
  self.positions[key] = position
175
176
  self._weights[position.date][position.underlying_quote.id] = float(position.weighting)
176
177
 
@@ -687,7 +687,6 @@ class Portfolio(DeleteToDisableMixin, WBModel):
687
687
  val_date: date,
688
688
  recompute_weighting: bool = False,
689
689
  force_recompute_weighting: bool = False,
690
- compute_metrics: bool = False,
691
690
  evaluate_rebalancer: bool = True,
692
691
  changed_weights: dict[int, float] | None = None,
693
692
  ):
@@ -731,10 +730,6 @@ class Portfolio(DeleteToDisableMixin, WBModel):
731
730
  self.initial_position_date = val_date
732
731
  self.save()
733
732
 
734
- if compute_metrics:
735
- compute_metrics_as_task.delay(
736
- val_date, basket_id=self.id, basket_content_type_id=ContentType.objects.get_for_model(Portfolio).id
737
- )
738
733
  self.handle_controlling_portfolio_change_at_date(val_date)
739
734
 
740
735
  def handle_controlling_portfolio_change_at_date(self, val_date: date):
@@ -1000,7 +995,12 @@ class Portfolio(DeleteToDisableMixin, WBModel):
1000
995
  return "{{name}}"
1001
996
 
1002
997
  def bulk_create_positions(
1003
- self, positions: AssetPositionIterator, delete_leftovers: bool = False, force_save: bool = False, **kwargs
998
+ self,
999
+ positions: AssetPositionIterator,
1000
+ delete_leftovers: bool = False,
1001
+ force_save: bool = False,
1002
+ compute_metrics: bool = True,
1003
+ **kwargs,
1004
1004
  ):
1005
1005
  if positions:
1006
1006
  # we need to delete the existing estimated portfolio because otherwise we risk to have existing and not
@@ -1041,6 +1041,13 @@ class Portfolio(DeleteToDisableMixin, WBModel):
1041
1041
  leftover_positions_ids = list(filter(lambda i: i not in objs_ids, leftover_positions_ids))
1042
1042
  logger.info(f"deleting {len(leftover_positions_ids)} leftover positions..")
1043
1043
  AssetPosition.objects.filter(id__in=leftover_positions_ids).delete()
1044
+ if compute_metrics and self.is_tracked:
1045
+ for val_date in dates:
1046
+ compute_metrics_as_task.delay(
1047
+ val_date,
1048
+ basket_id=self.id,
1049
+ basket_content_type_id=ContentType.objects.get_for_model(Portfolio).id,
1050
+ )
1044
1051
  for update_date, changed_weights in positions.get_weights().items():
1045
1052
  self.change_at_date(update_date, changed_weights=changed_weights, **kwargs)
1046
1053
 
@@ -254,13 +254,10 @@ class TestPortfolioModel(PortfolioTestMixin):
254
254
  assert Decimal(res.weighting[1]) == pytest.approx((abs(short_p2.weighting)) / total_weight, rel=Decimal(1e-4))
255
255
 
256
256
  @patch.object(Portfolio, "estimate_net_asset_values", autospec=True)
257
- @patch("wbportfolio.models.portfolio.compute_metrics_as_task.delay")
258
- def test_change_at_date(
259
- self, mock_compute_metrics, mock_estimate_net_asset_values, asset_position_factory, portfolio, weekday
260
- ):
257
+ def test_change_at_date(self, mock_estimate_net_asset_values, asset_position_factory, portfolio, weekday):
261
258
  asset_position_factory.create_batch(10, portfolio=portfolio, date=weekday)
262
259
 
263
- portfolio.change_at_date(weekday, compute_metrics=True, recompute_weighting=True)
260
+ portfolio.change_at_date(weekday, recompute_weighting=True)
264
261
 
265
262
  # test that change at date normalize the weighting
266
263
  total_value = AssetPosition.objects.aggregate(s=Sum("total_value_fx_portfolio"))["s"]
@@ -268,9 +265,6 @@ class TestPortfolioModel(PortfolioTestMixin):
268
265
  assert float(pos.weighting) == pytest.approx(float(pos.total_value_fx_portfolio / total_value), rel=1e-2)
269
266
 
270
267
  mock_estimate_net_asset_values.assert_called_once_with(portfolio, (weekday + BDay(1)).date(), weights=None)
271
- mock_compute_metrics.assert_called_once_with(
272
- weekday, basket_id=portfolio.id, basket_content_type_id=ContentType.objects.get_for_model(Portfolio).id
273
- )
274
268
 
275
269
  @patch.object(Portfolio, "compute_lookthrough", autospec=True)
276
270
  def test_change_at_date_with_dependent_portfolio(
@@ -1074,7 +1068,10 @@ class TestPortfolioModel(PortfolioTestMixin):
1074
1068
  assert portfolio.assets.get(date=rebalancing_date, underlying_instrument=i1).weighting == Decimal("0.5")
1075
1069
  assert portfolio.assets.get(date=rebalancing_date, underlying_instrument=i2).weighting == Decimal("0.5")
1076
1070
 
1077
- def test_bulk_create_positions(self, portfolio, weekday, asset_position_factory, instrument_factory):
1071
+ @patch("wbportfolio.models.portfolio.compute_metrics_as_task.delay")
1072
+ def test_bulk_create_positions(
1073
+ self, mock_compute_metrics, portfolio, weekday, asset_position_factory, instrument_factory
1074
+ ):
1078
1075
  portfolio.is_manageable = False
1079
1076
  portfolio.save()
1080
1077
  i1 = instrument_factory.create()
@@ -1087,6 +1084,10 @@ class TestPortfolioModel(PortfolioTestMixin):
1087
1084
  assert AssetPosition.objects.get(portfolio=portfolio, date=weekday).weighting == a1.weighting
1088
1085
  assert AssetPosition.objects.get(portfolio=portfolio, date=weekday).underlying_instrument == i1
1089
1086
 
1087
+ mock_compute_metrics.assert_called_once_with(
1088
+ weekday, basket_id=portfolio.id, basket_content_type_id=ContentType.objects.get_for_model(Portfolio).id
1089
+ )
1090
+
1090
1091
  # check that if we change key value, an already exising position will be updated accordingly
1091
1092
  a1.weighting = Decimal(0.5)
1092
1093
  portfolio.bulk_create_positions(AssetPositionIterator(portfolio).add([a1]))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbportfolio
3
- Version: 1.50.4
3
+ Version: 1.50.5
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  License-File: LICENSE
6
6
  Requires-Dist: cryptography==3.4.*
@@ -245,11 +245,11 @@ 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=wwAWW2vl21nJS7RVswjvB1PAZRjJ1VzCyLF4fb7R0Gw,45264
248
+ wbportfolio/models/asset.py,sha256=wxV8rHogMAKzWE3qEtu9EiFmK_DKHd5cVAUSsGpUKbA,45353
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
252
- wbportfolio/models/portfolio.py,sha256=l7YsGpei9a4yhTBPZsWk0W9YNO3WoC1EoraQFzSa79Q,55799
252
+ wbportfolio/models/portfolio.py,sha256=253Z9K4929UVNaoXmOdcLqWM8xutYwJHdiy0GAHheUM,55967
253
253
  wbportfolio/models/portfolio_cash_flow.py,sha256=uElG7IJUBY8qvtrXftOoskX6EA-dKgEG1JJdvHeWV7g,7336
254
254
  wbportfolio/models/portfolio_cash_targets.py,sha256=WmgG-etPisZsh2yaFQpz7EkpvAudKBEzqPsO715w52U,1498
255
255
  wbportfolio/models/portfolio_relationship.py,sha256=mMb18UMRWg9kx_9uIPkMktwORuXXLjKdgRPQQvB6fVE,5486
@@ -374,7 +374,7 @@ wbportfolio/tests/models/test_merge.py,sha256=sdsjiZsmR6vsUKwTa5kkvL6QTeAZqtd_EP
374
374
  wbportfolio/tests/models/test_portfolio_cash_flow.py,sha256=X8dsXexsb1b0lBiuGzu40ps_Az_1UmmKT0eo1vbXH94,5792
375
375
  wbportfolio/tests/models/test_portfolio_cash_targets.py,sha256=q8QWAwt-kKRkLC0E05GyRhF_TTQXIi8bdHjXVU0fCV0,965
376
376
  wbportfolio/tests/models/test_portfolio_swing_pricings.py,sha256=kr2AOcQkyg2pX3ULjU-o9ye-NVpjMrrfoe-DVbYCbjs,1656
377
- wbportfolio/tests/models/test_portfolios.py,sha256=2VqUF3HALY1VvCksARk4XJlQL-qr2PbF2DMmw9KQzdY,53132
377
+ wbportfolio/tests/models/test_portfolios.py,sha256=ECUAxE07KwmlHN1udMHVCmNDQrRcCbMfRQe8L3cHOX0,53111
378
378
  wbportfolio/tests/models/test_product_groups.py,sha256=AcdxhurV-n_bBuUsfD1GqVtwLFcs7VI2CRrwzsIUWbU,3337
379
379
  wbportfolio/tests/models/test_products.py,sha256=FkQLms3kXzyg6mNEEJcHUVKu8YbATY9l6lZyaRUnpjw,9314
380
380
  wbportfolio/tests/models/test_roles.py,sha256=4Cn7WyrA2ztJNeWLk5cy9kYo5XLWMbFSvo1O-9JYxeA,3323
@@ -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=-yJ4j8NJTu2VWyhCq5BXGNND_925Ietoxx9k07SLVh0,21634
523
523
  wbportfolio/viewsets/transactions/transactions.py,sha256=ixDp-nsNA8t_A06rBCT19hOMJHy0iRmdz1XKdV1OwAs,4450
524
- wbportfolio-1.50.4.dist-info/METADATA,sha256=cfKIITTuvQ6HWvOoAKeVL76jQyLNAVVaxLlXmc17DRg,702
525
- wbportfolio-1.50.4.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
526
- wbportfolio-1.50.4.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
527
- wbportfolio-1.50.4.dist-info/RECORD,,
524
+ wbportfolio-1.50.5.dist-info/METADATA,sha256=YSKxspCHBavsYHEEz8Qv_6W4F6B6AcdB_KrG1Xy6e8Q,702
525
+ wbportfolio-1.50.5.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
526
+ wbportfolio-1.50.5.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
527
+ wbportfolio-1.50.5.dist-info/RECORD,,