wbportfolio 1.50.4__py2.py3-none-any.whl → 1.50.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.
- wbportfolio/models/asset.py +8 -7
- wbportfolio/models/portfolio.py +13 -6
- wbportfolio/tests/models/test_portfolios.py +10 -9
- {wbportfolio-1.50.4.dist-info → wbportfolio-1.50.6.dist-info}/METADATA +1 -1
- {wbportfolio-1.50.4.dist-info → wbportfolio-1.50.6.dist-info}/RECORD +7 -7
- {wbportfolio-1.50.4.dist-info → wbportfolio-1.50.6.dist-info}/WHEEL +0 -0
- {wbportfolio-1.50.4.dist-info → wbportfolio-1.50.6.dist-info}/licenses/LICENSE +0 -0
wbportfolio/models/asset.py
CHANGED
|
@@ -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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
wbportfolio/models/portfolio.py
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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]))
|
|
@@ -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=
|
|
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=
|
|
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=
|
|
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.
|
|
525
|
-
wbportfolio-1.50.
|
|
526
|
-
wbportfolio-1.50.
|
|
527
|
-
wbportfolio-1.50.
|
|
524
|
+
wbportfolio-1.50.6.dist-info/METADATA,sha256=IzBOxYukspYaLc64HoG1si5Lq1Dgkfa1dHgbZ2DBeow,702
|
|
525
|
+
wbportfolio-1.50.6.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
|
|
526
|
+
wbportfolio-1.50.6.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
|
|
527
|
+
wbportfolio-1.50.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|