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

@@ -26,7 +26,7 @@ from wbportfolio.pms.trading import TradingService
26
26
  from wbportfolio.pms.typing import Portfolio as PortfolioDTO
27
27
  from wbportfolio.pms.typing import TradeBatch as TradeBatchDTO
28
28
 
29
- from .. import AssetPosition
29
+ from ..asset import AssetPosition, AssetPositionIterator
30
30
  from .trades import Trade
31
31
 
32
32
  logger = logging.getLogger("pms")
@@ -80,6 +80,13 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
80
80
  def save(self, *args, **kwargs):
81
81
  if not self.trade_date and self.portfolio.assets.exists():
82
82
  self.trade_date = (self.portfolio.assets.latest("date").date + BDay(1)).date()
83
+
84
+ # if a trade proposal is created before the existing earliest trade proposal, we automatically shift the linked instruments inception date to allow automatic NAV computation since the new inception date
85
+ if not self.portfolio.trade_proposals.filter(trade_date__lt=self.trade_date).exists():
86
+ new_inception_date = (self.trade_date + BDay(1)).date()
87
+ self.portfolio.instruments.filter(inception_date__gt=new_inception_date).update(
88
+ inception_date=new_inception_date
89
+ )
83
90
  super().save(*args, **kwargs)
84
91
 
85
92
  @property
@@ -302,9 +309,12 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
302
309
  next_trade_date = (
303
310
  next_trade_proposal.trade_date - timedelta(days=1) if next_trade_proposal else date.today()
304
311
  )
305
- overriding_trade_proposal = last_trade_proposal.portfolio.batch_portfolio(
312
+ positions, overriding_trade_proposal = self.portfolio.drift_weights(
306
313
  last_trade_proposal.trade_date, next_trade_date
307
314
  )
315
+ self.portfolio.bulk_create_positions(
316
+ positions, delete_leftovers=True, compute_metrics=False, evaluate_rebalancer=False
317
+ )
308
318
  if overriding_trade_proposal:
309
319
  last_trade_proposal_created = True
310
320
  last_trade_proposal = overriding_trade_proposal
@@ -527,7 +537,9 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
527
537
  assets.append(estimated_cash_position)
528
538
 
529
539
  Trade.objects.bulk_update(trades, ["status"])
530
- self.portfolio.bulk_create_positions(assets, evaluate_rebalancer=False)
540
+ self.portfolio.bulk_create_positions(
541
+ AssetPositionIterator(self.portfolio).add(assets), evaluate_rebalancer=False, force_save=True
542
+ )
531
543
  if replay and self.portfolio.is_manageable:
532
544
  replay_as_task.delay(self.id)
533
545
 
@@ -647,7 +659,9 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
647
659
  asset.set_weighting(asset.weighting - trade.weighting)
648
660
  assets.append(asset)
649
661
  Trade.objects.bulk_update(trades, ["status"])
650
- self.portfolio.bulk_create_positions(assets, evaluate_rebalancer=False)
662
+ self.portfolio.bulk_create_positions(
663
+ AssetPositionIterator(self.portfolio).add(assets), evaluate_rebalancer=False, force_save=True
664
+ )
651
665
 
652
666
  def can_revert(self):
653
667
  errors = dict()
@@ -443,7 +443,7 @@ class Trade(ShareMixin, Transaction, OrderedModel, WBModel):
443
443
  elif (
444
444
  assets := AssetPosition.unannotated_objects.filter(
445
445
  underlying_quote=self.underlying_instrument,
446
- date__lt=self.transaction_date,
446
+ date__lte=self.value_date,
447
447
  portfolio=self.portfolio,
448
448
  )
449
449
  ).exists():
@@ -20,8 +20,9 @@ from wbportfolio.models import (
20
20
  PortfolioPortfolioThroughModel,
21
21
  Trade,
22
22
  )
23
+ from wbportfolio.models.asset import AssetPositionIterator
23
24
 
24
- from ...models.portfolio import PositionDictConverter, update_portfolio_after_investable_universe
25
+ from ...models.portfolio import get_returns, update_portfolio_after_investable_universe
25
26
  from .utils import PortfolioTestMixin
26
27
 
27
28
  fake = Faker()
@@ -266,17 +267,15 @@ class TestPortfolioModel(PortfolioTestMixin):
266
267
  for pos in AssetPosition.objects.all():
267
268
  assert float(pos.weighting) == pytest.approx(float(pos.total_value_fx_portfolio / total_value), rel=1e-2)
268
269
 
269
- mock_estimate_net_asset_values.assert_called_once_with(portfolio, (weekday + BDay(1)).date())
270
+ mock_estimate_net_asset_values.assert_called_once_with(portfolio, (weekday + BDay(1)).date(), weights=None)
270
271
  mock_compute_metrics.assert_called_once_with(
271
272
  weekday, basket_id=portfolio.id, basket_content_type_id=ContentType.objects.get_for_model(Portfolio).id
272
273
  )
273
274
 
274
- @patch.object(Portfolio, "get_total_asset_under_management")
275
275
  @patch.object(Portfolio, "compute_lookthrough", autospec=True)
276
276
  def test_change_at_date_with_dependent_portfolio(
277
277
  self,
278
278
  mock_compute_lookthrough,
279
- mock_get_total_asset_under_management,
280
279
  portfolio_factory,
281
280
  product_factory,
282
281
  instrument_price_factory,
@@ -284,16 +283,12 @@ class TestPortfolioModel(PortfolioTestMixin):
284
283
  weekday,
285
284
  ):
286
285
  base_portfolio = portfolio_factory.create()
287
- base_portfolio_total_asset_under_management = fake.pydecimal()
288
- mock_get_total_asset_under_management.return_value = base_portfolio_total_asset_under_management
289
286
 
290
287
  dependent_portfolio = portfolio_factory.create(is_lookthrough=True)
291
288
  dependent_portfolio.depends_on.add(base_portfolio)
292
289
  base_portfolio.change_at_date(weekday)
293
290
 
294
- mock_compute_lookthrough.assert_called_once_with(
295
- dependent_portfolio, weekday, portfolio_total_asset_value=base_portfolio_total_asset_under_management
296
- )
291
+ mock_compute_lookthrough.assert_called_once_with(dependent_portfolio, weekday)
297
292
 
298
293
  def test_is_active_at_date(
299
294
  self,
@@ -437,14 +432,6 @@ class TestPortfolioModel(PortfolioTestMixin):
437
432
  )
438
433
  instrument_price_factory.create(date=next_day, instrument=instrument)
439
434
 
440
- # asset_position_factory.create(
441
- # portfolio=portfolio,
442
- # date=next_day,
443
- # underlying_instrument=instrument,
444
- # currency=instrument.currency,
445
- # exchange=a1.exchange,
446
- # portfolio_created=a1.portfolio_created,
447
- # )
448
435
  active_product.delisted_date = weekday
449
436
  active_product.save()
450
437
  # Test1: test if unactive portfolio keep having the to date assets. (asset found at next day are suppose to be deleted when the portfolio is non active at the from date)
@@ -465,6 +452,8 @@ class TestPortfolioModel(PortfolioTestMixin):
465
452
  initial_shares = a1.initial_shares
466
453
 
467
454
  # Test that estimated shares keep being updated
455
+ portfolio.only_weighting = False
456
+ portfolio.save()
468
457
  a1.initial_shares *= 2
469
458
  a1.save()
470
459
  portfolio.propagate_or_update_assets(weekday, next_day)
@@ -491,8 +480,9 @@ class TestPortfolioModel(PortfolioTestMixin):
491
480
  a_future.is_estimated = True
492
481
  a_future.save()
493
482
  portfolio.propagate_or_update_assets(weekday, next_day)
494
- a_future = AssetPosition.objects.get(portfolio=portfolio, date=next_day)
495
- assert a_future.weighting == 0
483
+ with pytest.raises(AssetPosition.DoesNotExist):
484
+ AssetPosition.objects.get(portfolio=portfolio, date=next_day)
485
+ # assert a_future.weighting == 0
496
486
 
497
487
  def test_update_preferred_classification_per_instrument(
498
488
  self, portfolio, asset_position_factory, equity_factory, classification_factory, classification_group_factory
@@ -588,6 +578,11 @@ class TestPortfolioModel(PortfolioTestMixin):
588
578
 
589
579
  portfolio.is_tracked = False
590
580
  portfolio.save()
581
+ assert portfolio.is_manageable is True
582
+ assert Portfolio.tracked_objects.exists()
583
+
584
+ portfolio.is_manageable = False
585
+ portfolio.save()
591
586
  assert not Portfolio.tracked_objects.exists()
592
587
 
593
588
  def test_is_invested_at_date(self, portfolio_factory):
@@ -600,8 +595,10 @@ class TestPortfolioModel(PortfolioTestMixin):
600
595
  assert set(Portfolio.objects.filter_invested_at_date(date(2024, 1, 2))) == {portfolio}
601
596
  assert set(Portfolio.objects.filter_invested_at_date(date(2024, 1, 1))) == set()
602
597
 
598
+ @patch.object(Portfolio, "get_total_asset_under_management", autospec=True)
603
599
  def test_compute_lookthrough(
604
600
  self,
601
+ mock_fct,
605
602
  active_product,
606
603
  weekday,
607
604
  portfolio_factory,
@@ -766,9 +763,10 @@ class TestPortfolioModel(PortfolioTestMixin):
766
763
  )
767
764
  assert Decimal(1.0) == pytest.approx(product_portfolio.assets.aggregate(s=Sum("weighting"))["s"])
768
765
 
769
- product_portfolio.is_weighting = True
766
+ product_portfolio.only_weighting = False
770
767
  product_portfolio.save()
771
- product_portfolio.compute_lookthrough(weekday, portfolio_total_asset_value=Decimal(1_000_000))
768
+ mock_fct.return_value = Decimal(1_000_000)
769
+ product_portfolio.compute_lookthrough(weekday)
772
770
  position = product_portfolio.assets.get(
773
771
  portfolio_created=index1_portfolio,
774
772
  underlying_instrument=a1_1.underlying_instrument,
@@ -891,24 +889,20 @@ class TestPortfolioModel(PortfolioTestMixin):
891
889
  assert set(portfolio.pms_instruments) == {product_group, product, index}
892
890
 
893
891
  @pytest.mark.parametrize(
894
- "portfolio__is_tracked, portfolio__is_manageable, portfolio__is_lookthrough",
892
+ "portfolio__is_manageable, portfolio__is_lookthrough",
895
893
  [
896
- (True, True, True),
897
- (True, False, True),
898
- (True, False, False),
899
- (False, True, True),
900
- (False, True, False),
901
- (False, False, True),
902
- (False, False, False),
894
+ (True, True),
895
+ (False, True),
896
+ (False, False),
903
897
  ],
904
898
  )
905
899
  def test_cannot_be_rebalanced(self, portfolio):
906
900
  assert portfolio.can_be_rebalanced is False
907
901
 
908
902
  @pytest.mark.parametrize(
909
- "portfolio__is_tracked, portfolio__is_manageable, portfolio__is_lookthrough",
903
+ "portfolio__is_manageable, portfolio__is_lookthrough",
910
904
  [
911
- (True, True, False),
905
+ (True, False),
912
906
  ],
913
907
  )
914
908
  def test_can_be_rebalanced(self, portfolio):
@@ -917,8 +911,8 @@ class TestPortfolioModel(PortfolioTestMixin):
917
911
  def test_get_analytic_portfolio(
918
912
  self, weekday, portfolio, asset_position_factory, instrument_factory, instrument_price_factory
919
913
  ):
920
- i1 = instrument_factory.create()
921
- i2 = instrument_factory.create()
914
+ i1 = instrument_factory.create(currency=portfolio.currency)
915
+ i2 = instrument_factory.create(currency=portfolio.currency)
922
916
  p10 = instrument_price_factory.create(instrument=i1, date=weekday)
923
917
  p11 = instrument_price_factory.create(instrument=i1, date=(weekday + BDay(1)).date())
924
918
  p20 = instrument_price_factory.create(instrument=i2, date=weekday)
@@ -931,7 +925,7 @@ class TestPortfolioModel(PortfolioTestMixin):
931
925
  )
932
926
  a2.refresh_from_db()
933
927
 
934
- analytic_portfolio, _ = portfolio.get_analytic_portfolio(weekday, convert_to_portfolio_currency=False)
928
+ analytic_portfolio, _ = portfolio.get_analytic_portfolio(weekday)
935
929
  assert analytic_portfolio.weights.tolist() == [float(a1.weighting), float(a2.weighting)]
936
930
  expected_X = pd.DataFrame(
937
931
  [[float(p11.net_value / p10.net_value - Decimal(1)), float(p21.net_value / p20.net_value - Decimal(1))]],
@@ -968,7 +962,7 @@ class TestPortfolioModel(PortfolioTestMixin):
968
962
  def test_update_portfolio_after_investable_universe(
969
963
  self, mock_fct, weekday, portfolio_factory, asset_position_factory
970
964
  ):
971
- untracked_portfolio = portfolio_factory.create(is_tracked=False) # noqa
965
+ untracked_portfolio = portfolio_factory.create(is_tracked=False, is_manageable=False) # noqa
972
966
  asset_position_factory.create(portfolio=untracked_portfolio)
973
967
  tracked_lookthrough_portfolio = portfolio_factory.create(is_tracked=True, is_lookthrough=True) # noqa
974
968
  asset_position_factory.create(portfolio=tracked_lookthrough_portfolio)
@@ -1009,12 +1003,14 @@ class TestPortfolioModel(PortfolioTestMixin):
1009
1003
  p = instrument_price_factory.create(instrument=instrument, date=weekday)
1010
1004
  fx_portfolio = currency_fx_rates_factory.create(currency=portfolio.currency, date=weekday)
1011
1005
  fx_instrument = currency_fx_rates_factory.create(currency=instrument.currency, date=weekday)
1006
+ instrument_id: int = instrument.id
1007
+ weights = {instrument_id: random.random()}
1008
+ positions = AssetPositionIterator(
1009
+ portfolio, prices={weekday: {instrument_id: p.net_value}}, infer_underlying_quote_price=False
1010
+ )
1011
+ positions.add((weekday, weights))
1012
1012
 
1013
- weights = {instrument.id: random.random()}
1014
- prices = pd.DataFrame([{instrument.id: p.net_value, "date": weekday}]).set_index("date")
1015
- prices.index = pd.to_datetime(prices.index)
1016
- converter = PositionDictConverter(portfolio, prices)
1017
- res = list(converter.convert({weekday: weights}))
1013
+ res = list(positions)
1018
1014
  a = res[0]
1019
1015
  assert len(res) == 1
1020
1016
  assert a.date == weekday
@@ -1029,7 +1025,7 @@ class TestPortfolioModel(PortfolioTestMixin):
1029
1025
  a.save()
1030
1026
  assert a
1031
1027
 
1032
- def test_batch_portfolio_with_rebalancer(
1028
+ def test_drift_weights_with_rebalancer(
1033
1029
  self,
1034
1030
  weekday,
1035
1031
  rebalancer_factory,
@@ -1056,15 +1052,12 @@ class TestPortfolioModel(PortfolioTestMixin):
1056
1052
  instrument_price_factory.create(instrument=i2, date=rebalancing_date)
1057
1053
 
1058
1054
  rebalancer_factory.create(portfolio=portfolio, frequency="RRULE:FREQ=DAILY;", activation_date=rebalancing_date)
1059
- rebalancing_trade_proposal = portfolio.batch_portfolio(weekday, rebalancing_date)
1060
-
1061
- # check that the position before the rebalancing date were created
1062
- assert portfolio.assets.get(date=middle_date, underlying_instrument=i1)
1063
- assert portfolio.assets.get(date=middle_date, underlying_instrument=i2)
1064
- with pytest.raises(
1065
- AssetPosition.DoesNotExist
1066
- ): # there is no asset position because the rebalancing stopped it:
1067
- portfolio.assets.get(date=rebalancing_date)
1055
+ positions, rebalancing_trade_proposal = portfolio.drift_weights(weekday, (rebalancing_date + BDay(1)).date())
1056
+ assert rebalancing_trade_proposal.trade_date == rebalancing_date
1057
+ assert rebalancing_trade_proposal.status == "SUBMIT"
1058
+ assert set(positions.get_weights().keys()) == {
1059
+ middle_date,
1060
+ }, "Drifting weight with a non automatic rebalancer stops the iteration"
1068
1061
 
1069
1062
  # we expect a equally rebalancing (default) so both trades needs to be created
1070
1063
  t1 = rebalancing_trade_proposal.trades.get(transaction_date=rebalancing_date, underlying_instrument=i1)
@@ -1090,17 +1083,17 @@ class TestPortfolioModel(PortfolioTestMixin):
1090
1083
  a1 = asset_position_factory.build(date=weekday, portfolio=portfolio, underlying_instrument=i1)
1091
1084
 
1092
1085
  # check initial creation
1093
- portfolio.bulk_create_positions([a1])
1086
+ portfolio.bulk_create_positions(AssetPositionIterator(portfolio).add([a1]))
1094
1087
  assert AssetPosition.objects.get(portfolio=portfolio, date=weekday).weighting == a1.weighting
1095
1088
  assert AssetPosition.objects.get(portfolio=portfolio, date=weekday).underlying_instrument == i1
1096
1089
 
1097
1090
  # check that if we change key value, an already exising position will be updated accordingly
1098
1091
  a1.weighting = Decimal(0.5)
1099
- portfolio.bulk_create_positions([a1])
1092
+ portfolio.bulk_create_positions(AssetPositionIterator(portfolio).add([a1]))
1100
1093
  assert AssetPosition.objects.get(portfolio=portfolio, date=weekday).weighting == Decimal(0.5)
1101
1094
 
1102
1095
  a2 = asset_position_factory.build(date=weekday, portfolio=portfolio, underlying_instrument=i2)
1103
- portfolio.bulk_create_positions([a2])
1096
+ portfolio.bulk_create_positions(AssetPositionIterator(portfolio).add([a2]))
1104
1097
  assert (
1105
1098
  AssetPosition.objects.get(portfolio=portfolio, date=weekday, underlying_instrument=i1).weighting
1106
1099
  == a1.weighting
@@ -1111,7 +1104,7 @@ class TestPortfolioModel(PortfolioTestMixin):
1111
1104
  )
1112
1105
 
1113
1106
  a3 = asset_position_factory.build(date=weekday, portfolio=portfolio, underlying_instrument=i3)
1114
- portfolio.bulk_create_positions([a3], delete_leftovers=True)
1107
+ portfolio.bulk_create_positions(AssetPositionIterator(portfolio).add([a3]), delete_leftovers=True)
1115
1108
  assert AssetPosition.objects.get(portfolio=portfolio, date=weekday).weighting == a3.weighting
1116
1109
  assert AssetPosition.objects.get(portfolio=portfolio, date=weekday).underlying_instrument == i3
1117
1110
 
@@ -1146,7 +1139,7 @@ class TestPortfolioModel(PortfolioTestMixin):
1146
1139
  i11.refresh_from_db()
1147
1140
  i12.refresh_from_db()
1148
1141
  i13.refresh_from_db()
1149
- returns, _ = portfolio.get_returns(from_date=v1, to_date=v3, convert_to_portfolio_currency=False)
1142
+ returns, _ = get_returns([i1.id, i2.id], from_date=v1, to_date=v3)
1150
1143
 
1151
1144
  expected_returns = pd.DataFrame(
1152
1145
  [[i12.net_value / i11.net_value - 1, 0.0], [i13.net_value / i12.net_value - 1, 0.0]],
@@ -1157,14 +1150,8 @@ class TestPortfolioModel(PortfolioTestMixin):
1157
1150
  expected_returns.index = pd.to_datetime(expected_returns.index)
1158
1151
  pd.testing.assert_frame_equal(returns, expected_returns, check_names=False, check_freq=False, atol=1e-6)
1159
1152
 
1160
- @patch.object(Portfolio, "get_total_asset_under_management")
1161
1153
  @patch.object(Portfolio, "compute_lookthrough", autospec=True)
1162
- def test_handle_controlling_portfolio_change_at_date(
1163
- self, mock_compute_lookthrough, mock_get_total_asset_under_management, weekday, portfolio_factory
1164
- ):
1165
- portfolio_total_asset_value = Decimal(1_000_000)
1166
- mock_get_total_asset_under_management.return_value = portfolio_total_asset_value
1167
-
1154
+ def test_handle_controlling_portfolio_change_at_date(self, mock_compute_lookthrough, weekday, portfolio_factory):
1168
1155
  primary_portfolio = portfolio_factory.create(only_weighting=True)
1169
1156
  lookthrough_portfolio = portfolio_factory.create(is_lookthrough=True, only_weighting=False)
1170
1157
  PortfolioPortfolioThroughModel.objects.create(
@@ -1174,6 +1161,4 @@ class TestPortfolioModel(PortfolioTestMixin):
1174
1161
  )
1175
1162
 
1176
1163
  primary_portfolio.handle_controlling_portfolio_change_at_date(weekday)
1177
- mock_compute_lookthrough.assert_called_once_with(
1178
- lookthrough_portfolio, weekday, portfolio_total_asset_value=portfolio_total_asset_value
1179
- )
1164
+ mock_compute_lookthrough.assert_called_once_with(lookthrough_portfolio, weekday)
@@ -142,11 +142,6 @@ class TestProductModel(PortfolioTestMixin):
142
142
 
143
143
  def test_subquery_is_white_label_product(self, product, white_label_product):
144
144
  product_queryset = Product.objects.all().annotate(is_white_label=Product.subquery_is_white_label_product())
145
- # for product in product_queryset:
146
- # print(product, product.is_white_label)
147
-
148
- # print(product_queryset.filter(is_white_label=True).count())
149
-
150
145
  assert product_queryset.filter(is_white_label=True).count() == 1
151
146
 
152
147
  def test_annotate_aum(
@@ -310,12 +310,12 @@ class TestTradeProposal:
310
310
  assert t3.weighting == Decimal("0.5")
311
311
 
312
312
  # Test replaying trade proposals
313
- @patch.object(Portfolio, "batch_portfolio")
313
+ @patch.object(Portfolio, "drift_weights")
314
314
  def test_replay(self, mock_fct, trade_proposal_factory):
315
315
  """
316
- Ensure replaying trade proposals correctly calls batch_portfolio for each period.
316
+ Ensure replaying trade proposals correctly calls drift_weights for each period.
317
317
  """
318
- mock_fct.return_value = None
318
+ mock_fct.return_value = None, None
319
319
 
320
320
  # Create approved trade proposals for testing
321
321
  tp0 = trade_proposal_factory.create(status=TradeProposal.Status.APPROVED)
@@ -333,14 +333,14 @@ class TestTradeProposal:
333
333
  # Replay trade proposals
334
334
  tp0.replay()
335
335
 
336
- # Expected calls to batch_portfolio
336
+ # Expected calls to drift_weights
337
337
  expected_calls = [
338
338
  call(tp0.trade_date, tp1.trade_date - timedelta(days=1)),
339
339
  call(tp1.trade_date, tp2.trade_date - timedelta(days=1)),
340
340
  call(tp2.trade_date, date.today()),
341
341
  ]
342
342
 
343
- # Assert batch_portfolio was called as expected
343
+ # Assert drift_weights was called as expected
344
344
  mock_fct.assert_has_calls(expected_calls)
345
345
 
346
346
  # Test stopping replay on a non-approved proposal
@@ -396,3 +396,15 @@ class TestTradeProposal:
396
396
  target_cash_position = trade_proposal.get_estimated_target_cash(trade_proposal.portfolio.currency)
397
397
  assert target_cash_position.weighting == Decimal("0.2") + Decimal("1.0") - (Decimal("0.7") + Decimal("0.2"))
398
398
  assert target_cash_position.initial_shares == Decimal(1_000_000) * Decimal("0.3")
399
+
400
+ def test_trade_proposal_update_inception_date(self, trade_proposal_factory, portfolio, instrument_factory):
401
+ # Check that if we create a prior trade proposal, the instrument inception date is updated accordingly
402
+ instrument = instrument_factory.create(inception_date=None)
403
+ instrument.portfolios.add(portfolio)
404
+ tp = trade_proposal_factory.create(portfolio=portfolio)
405
+ instrument.refresh_from_db()
406
+ assert instrument.inception_date == (tp.trade_date + BDay(1)).date()
407
+
408
+ tp2 = trade_proposal_factory.create(portfolio=portfolio, trade_date=tp.trade_date - BDay(1))
409
+ instrument.refresh_from_db()
410
+ assert instrument.inception_date == (tp2.trade_date + BDay(1)).date()
@@ -27,7 +27,7 @@ from wbportfolio.models import (
27
27
  RebalancingModel,
28
28
  TradeProposal,
29
29
  )
30
- from wbportfolio.models.portfolio import batch_recompute_lookthrough_as_task
30
+ from wbportfolio.models.portfolio import compute_lookthrough_as_task
31
31
  from wbportfolio.serializers import (
32
32
  PortfolioModelSerializer,
33
33
  PortfolioPortfolioThroughModelSerializer,
@@ -173,7 +173,7 @@ class PortfolioModelViewSet(UserPortfolioRequestPermissionMixin, InternalUserPer
173
173
  with suppress(KeyError):
174
174
  start = datetime.strptime(request.POST["start"], "%Y-%m-%d")
175
175
  end = datetime.strptime(request.POST["end"], "%Y-%m-%d")
176
- batch_recompute_lookthrough_as_task.delay(portfolio.id, start, end)
176
+ compute_lookthrough_as_task.delay(portfolio.id, start, end)
177
177
  return HttpResponse("Ok", status=200)
178
178
 
179
179
  return HttpResponse("Bad arguments", status=400)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbportfolio
3
- Version: 1.49.9
3
+ Version: 1.49.10
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  License-File: LICENSE
6
6
  Requires-Dist: cryptography==3.4.*
@@ -138,7 +138,7 @@ wbportfolio/import_export/parsers/natixis/d1_trade.py,sha256=jhUa35DGfN5pvGuGhnc
138
138
  wbportfolio/import_export/parsers/natixis/d1_valuation.py,sha256=O6L-tCjfSApBTUTe2-azBJqyG3YfnhZvGjNhIFFx3A8,1290
139
139
  wbportfolio/import_export/parsers/natixis/dividend.py,sha256=h_J7ZH724BkO_WtDo0Ycn9GfqtNEX9Wnjdf-iq52dOQ,2368
140
140
  wbportfolio/import_export/parsers/natixis/equity.py,sha256=Aam3aI7rE3dTO4SrzctMnzCxQSYacRjkZsHrZyaBA-0,2474
141
- wbportfolio/import_export/parsers/natixis/fees.py,sha256=LdtT-z8PiRGeWQ85DtYbgPSeVENFXB7_CKiRcngoGq0,1917
141
+ wbportfolio/import_export/parsers/natixis/fees.py,sha256=P3vicaOXAx_7zISlsyB3hezM6-skg4Hi8F59djYEsoY,1918
142
142
  wbportfolio/import_export/parsers/natixis/trade.py,sha256=oIQGwL7k6T4H8d_XvhirvHsctCynsg_Ws1zbcJspfSE,2530
143
143
  wbportfolio/import_export/parsers/natixis/utils.py,sha256=aCl14mKhm0PamlHI2gd9GQjpHKd-prH-iNRhsuY8yxQ,2910
144
144
  wbportfolio/import_export/parsers/natixis/valuation.py,sha256=mLjIw1GBlPPlzHJkxg14kJddnKlOZjX8hL-bT227H_k,1538
@@ -148,7 +148,7 @@ wbportfolio/import_export/parsers/sg_lux/__init__.py,sha256=47DEQpj8HBSa-_TImW-5
148
148
  wbportfolio/import_export/parsers/sg_lux/custodian_positions.py,sha256=hl-LqJbwojQOe0zGEH7W31i4hNTVme5L7nwXwyk6XBY,2483
149
149
  wbportfolio/import_export/parsers/sg_lux/customer_trade.py,sha256=gTEUIaxlZvXXCgYyg9FD3D3aQhffhruYmeZlaAghrK8,2617
150
150
  wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py,sha256=4KSxUGyflf3kY5BVJqMAF1aFkeSWEJegfDtLiA1BM6c,5065
151
- wbportfolio/import_export/parsers/sg_lux/customer_trade_slk.py,sha256=24e-vonaBAkvX-F_Gw4hLg4FBjgbpCTUbiFC_dZ-czw,2958
151
+ wbportfolio/import_export/parsers/sg_lux/customer_trade_slk.py,sha256=D_1c0v6lTXkopjZzr9pQb575HxrMFe91tKk8xT1X3Ns,3604
152
152
  wbportfolio/import_export/parsers/sg_lux/customer_trade_without_pw.py,sha256=nMYV9-OwY_TagU36Ab0dOweZYzVriFWHXPYjauBg2Yk,2034
153
153
  wbportfolio/import_export/parsers/sg_lux/equity.py,sha256=w13LAMNWReZzqmdFyXdazVnpGr1UUNLG-pJgn5c16gI,6116
154
154
  wbportfolio/import_export/parsers/sg_lux/fees.py,sha256=xyPO9sYQyJW5w-1XhUMizY2RbH1Pr4n7LAUE-E67Yj4,1911
@@ -245,19 +245,19 @@ 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=H4UvrEstrCCbRG_poF68Ih4JFgHE08guMkyhNCZ7QFQ,38636
248
+ wbportfolio/models/asset.py,sha256=5GHcT8vbqAAQqWmGmA8g01H4TZ5_ML9hT5jNpgXrRUs,45304
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=dUj1iPZke17qYk2zSSL-6Mcjr7cKAoq_wIrZtEmTmLg,58666
253
- wbportfolio/models/portfolio_cash_flow.py,sha256=2blPiXSw7dbhUVd-7LcxDBb4v0SheNOdvRK3MFYiChA,7273
252
+ wbportfolio/models/portfolio.py,sha256=pH-qZ3xgwC27Lg7-tAE9XZgPIyDEhMcT1CQV-dP70js,55057
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
256
256
  wbportfolio/models/portfolio_swing_pricings.py,sha256=_LqYC1VRjnnFFmVqFPRdnbgYsVxocMVpClTk2dnooig,1778
257
257
  wbportfolio/models/product_groups.py,sha256=qoT8qv6tRdD6zmNDxsHO6dtnWw1-TRnyS5NqOq_gwhI,7901
258
258
  wbportfolio/models/products.py,sha256=5z9OMLVS0ps6aOJbG3JE7gKTf3PlbC4rXIPpv55dD1A,22863
259
259
  wbportfolio/models/registers.py,sha256=qA6T33t4gxFYnabQFBMd90WGIr6wxxirDLKDFqjOfok,4667
260
- wbportfolio/models/roles.py,sha256=34BwZleaPMHnUqDK1nHett45xaNNsUqSHY44844itW8,7387
260
+ wbportfolio/models/roles.py,sha256=wF5Atp0xsJxDrYjmFw9XHgEOe_4C-lm4pppeYdymOrA,7343
261
261
  wbportfolio/models/utils.py,sha256=iBdMjRCvr6aOL0nLgfSCWUKe0h39h3IGmUbYo6l9t6w,394
262
262
  wbportfolio/models/graphs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
263
263
  wbportfolio/models/graphs/portfolio.py,sha256=NwkehWvTcyTYrKO5ku3eNNaYLuBwuLdSbTEuugGuSIU,6541
@@ -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=obzgewWKOD4kJbCoF5fhtfDk502QkbrjPKh8T9KDGew,7355
279
- wbportfolio/models/transactions/trade_proposals.py,sha256=a4bxSn6xGuURwsE-WqCJN5gBdwu9TpCf2E8kKn4_RiY,29659
280
- wbportfolio/models/transactions/trades.py,sha256=NA_hUpFKQ4H-w3B0gMZ5IFkZo6WJTH2S7GBUQC1jUpU,28922
279
+ wbportfolio/models/transactions/trade_proposals.py,sha256=eWT_pTFLQ_4Eq8jVEfWhfewPVQ1Mx2viD0gAIosCNqI,30540
280
+ wbportfolio/models/transactions/trades.py,sha256=1kCgNXWeKvkRtoe8W7vjfzvpYIDwuU0jvgyCJY-IABc,28917
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
@@ -374,9 +374,9 @@ 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=LFAYrfhEbcDB2fyC0q1TNUy9vWE9oQpqLNKJ5sx_aCw,53872
377
+ wbportfolio/tests/models/test_portfolios.py,sha256=UoR9rmojIHNjOo770hKVTXA4bf06Yj5l06JhFt8R1MA,53138
378
378
  wbportfolio/tests/models/test_product_groups.py,sha256=AcdxhurV-n_bBuUsfD1GqVtwLFcs7VI2CRrwzsIUWbU,3337
379
- wbportfolio/tests/models/test_products.py,sha256=nBEgyUoY-4F_pfHYnAr7KXdNYvdIkSu-PWJrqp5tPHg,9482
379
+ wbportfolio/tests/models/test_products.py,sha256=FkQLms3kXzyg6mNEEJcHUVKu8YbATY9l6lZyaRUnpjw,9314
380
380
  wbportfolio/tests/models/test_roles.py,sha256=4Cn7WyrA2ztJNeWLk5cy9kYo5XLWMbFSvo1O-9JYxeA,3323
381
381
  wbportfolio/tests/models/test_splits.py,sha256=ytKcHsI_90kj1L4s8It-KEcc24rkDcElxwQ8q0QxEvk,9689
382
382
  wbportfolio/tests/models/utils.py,sha256=ORNJq6NMo1Za22jGZXfTfKeNEnTRlfEt_8SJ6xLaQWg,325
@@ -384,7 +384,7 @@ wbportfolio/tests/models/transactions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCe
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
386
  wbportfolio/tests/models/transactions/test_rebalancing.py,sha256=fZ5tx6kByEGXD6nhapYdvk9HOjYlmjhU2w6KlQJ6QE4,4061
387
- wbportfolio/tests/models/transactions/test_trade_proposals.py,sha256=P05F1RC53xjkp8BFq8D57k-86eAOkRQw6doLVX5SQlM,17945
387
+ wbportfolio/tests/models/transactions/test_trade_proposals.py,sha256=AhPI3XEjxzZxEUSzatCs6cOBnRHRig55NnV__PYubcQ,18675
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
@@ -409,7 +409,7 @@ wbportfolio/viewsets/portfolio_cash_flow.py,sha256=jkBfdZRQ3KsxGMJpltRjmdrZ2qEFJ
409
409
  wbportfolio/viewsets/portfolio_cash_targets.py,sha256=CvHlrDE8qnnnfRpTYnFu-Uu15MDbF5d5gTmEKth2S24,322
410
410
  wbportfolio/viewsets/portfolio_relationship.py,sha256=RGyvxd8NfFEs8YdqEvVD3VbrISvAO5UtCTlocSIuWQw,2109
411
411
  wbportfolio/viewsets/portfolio_swing_pricing.py,sha256=-57l3WLQZRslIV67OT0ucHE5JXTtTtLvd3t7MppdVn8,357
412
- wbportfolio/viewsets/portfolios.py,sha256=1POzE9jrt2iLVMnIY_BWDr0A_zpOlO3Z8tM80TaXkhk,14454
412
+ wbportfolio/viewsets/portfolios.py,sha256=FFK8Vt7Mk5ttS9FzGcRlKhTg0suX1wa8H35h4xZEe8Y,14438
413
413
  wbportfolio/viewsets/positions.py,sha256=2rzFHB_SI09rXC_EYi58G_eqvzONbk8z61JDkkjt3Ew,13207
414
414
  wbportfolio/viewsets/product_groups.py,sha256=YvmuXPPy98K1J_rz6YPsx9gNK-tCS2P-wc1uRYgfyo0,2399
415
415
  wbportfolio/viewsets/product_performance.py,sha256=dRfRgifjGS1RgZSu9uJRM0SmB7eLnNUkPuqARMO4gyo,28371
@@ -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.49.9.dist-info/METADATA,sha256=5SEVaByFm-DP4vkkCTwnfqOBfF3edgboFG-Eoyx70oA,734
525
- wbportfolio-1.49.9.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
526
- wbportfolio-1.49.9.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
527
- wbportfolio-1.49.9.dist-info/RECORD,,
524
+ wbportfolio-1.49.10.dist-info/METADATA,sha256=VBzotvqIdSqWY0CbQ_sobO0hiQPjSs8Y8uw8C5v4chE,735
525
+ wbportfolio-1.49.10.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
526
+ wbportfolio-1.49.10.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
527
+ wbportfolio-1.49.10.dist-info/RECORD,,