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

@@ -0,0 +1,2 @@
1
+ class InvalidAnalyticPortfolio(Exception):
2
+ pass
@@ -52,6 +52,7 @@ from wbportfolio.pms.analytics.portfolio import Portfolio as AnalyticPortfolio
52
52
  from wbportfolio.pms.typing import Portfolio as PortfolioDTO
53
53
 
54
54
  from . import ProductGroup
55
+ from .exceptions import InvalidAnalyticPortfolio
55
56
 
56
57
  logger = logging.getLogger("pms")
57
58
 
@@ -373,6 +374,8 @@ class Portfolio(DeleteToDisableMixin, WBModel):
373
374
  instrument_ids = weights.keys()
374
375
  return_date = (val_date + BDay(1)).date()
375
376
  returns, prices = self.get_returns(instrument_ids, (val_date - BDay(2)).date(), return_date, **kwargs)
377
+ if pd.Timestamp(return_date) not in returns.index:
378
+ raise InvalidAnalyticPortfolio()
376
379
  returns = returns.fillna(0) # not sure this is what we want
377
380
  return (
378
381
  AnalyticPortfolio(
@@ -733,10 +736,9 @@ class Portfolio(DeleteToDisableMixin, WBModel):
733
736
  },
734
737
  )
735
738
  if (
736
- val_date == instrument.prices.latest("date").date
739
+ val_date == instrument.last_price_date
737
740
  ): # if price date is the latest instrument price date, we recompute the last valuation data
738
741
  instrument.update_last_valuation_date()
739
- instrument.update_last_valuation_date()
740
742
 
741
743
  def batch_portfolio(self, start_date: date, end_date: date):
742
744
  """
@@ -758,9 +760,13 @@ class Portfolio(DeleteToDisableMixin, WBModel):
758
760
  if rebalancer and rebalancer.is_valid(to_date):
759
761
  rebalancing_date = to_date
760
762
  break
761
- last_returns = returns.loc[[to_date_ts], :]
762
- analytic_portfolio = AnalyticPortfolio(weights=weights, X=last_returns)
763
- positions[to_date] = analytic_portfolio.get_next_weights()
763
+ try:
764
+ last_returns = returns.loc[[to_date_ts], :]
765
+ analytic_portfolio = AnalyticPortfolio(weights=weights, X=last_returns)
766
+ weights = analytic_portfolio.get_next_weights()
767
+ positions[to_date] = weights
768
+ except KeyError: # if no return for that date, we break and continue
769
+ break
764
770
 
765
771
  positions_generator = PositionDictConverter(self, prices)
766
772
  positions = list(positions_generator.convert(positions))
@@ -797,14 +803,15 @@ class Portfolio(DeleteToDisableMixin, WBModel):
797
803
  self.is_tracked and not self.is_lookthrough and not is_target_portfolio_imported and from_is_active
798
804
  ): # we cannot propagate a new portfolio for untracked, or look-through or already imported or inactive portfolios
799
805
  logger.info(f"computing next weight for {self} from {from_date:%Y-%m-%d} to {to_date:%Y-%m-%d}")
800
- analytic_portfolio, to_prices = self.get_analytic_portfolio(from_date)
801
- if not to_prices.empty:
802
- weights = analytic_portfolio.get_next_weights()
803
- positions_generator = PositionDictConverter(
804
- self, to_prices, infer_underlying_quote_price=True
805
- ).convert({to_date: weights})
806
- positions = list(map(lambda a: _parse_position(a), positions_generator))
807
- self.bulk_create_positions(positions, delete_leftovers=True, compute_metrics=True)
806
+ with suppress(InvalidAnalyticPortfolio):
807
+ analytic_portfolio, to_prices = self.get_analytic_portfolio(from_date)
808
+ if not to_prices.empty:
809
+ weights = analytic_portfolio.get_next_weights()
810
+ positions_generator = PositionDictConverter(
811
+ self, to_prices, infer_underlying_quote_price=True
812
+ ).convert({to_date: weights})
813
+ positions = list(map(lambda a: _parse_position(a), positions_generator))
814
+ self.bulk_create_positions(positions, delete_leftovers=True, compute_metrics=True)
808
815
 
809
816
  def get_lookthrough_positions(
810
817
  self,
@@ -1030,7 +1037,9 @@ class Portfolio(DeleteToDisableMixin, WBModel):
1030
1037
  .astype(float)
1031
1038
  .sort_index()
1032
1039
  )
1033
- ts = pd.bdate_range(from_date, to_date, freq="B")
1040
+ if prices_df.empty:
1041
+ raise InvalidAnalyticPortfolio()
1042
+ ts = pd.bdate_range(prices_df.index.min(), prices_df.index.max(), freq="B")
1034
1043
  prices_df = prices_df.reindex(ts)
1035
1044
  if ffill_returns:
1036
1045
  prices_df = prices_df.ffill()
@@ -1225,7 +1234,9 @@ def default_estimate_net_value(val_date: date, instrument: Instrument) -> float
1225
1234
  last_price := instrument.get_latest_price(previous_val_date)
1226
1235
  )
1227
1236
  ):
1228
- with suppress(IndexError): # we silent any indexerror introduced by no returns for the past days
1237
+ with suppress(
1238
+ IndexError, InvalidAnalyticPortfolio
1239
+ ): # we silent any indexerror introduced by no returns for the past days
1229
1240
  analytic_portfolio, _ = portfolio.get_analytic_portfolio(previous_val_date)
1230
1241
  return analytic_portfolio.get_estimate_net_value(float(last_price.net_value))
1231
1242
 
@@ -428,8 +428,10 @@ class TestPortfolioModel(PortfolioTestMixin):
428
428
  next_day = (weekday + BDay(1)).date()
429
429
 
430
430
  portfolio = active_product.portfolio
431
- instrument_price_factory.create(date=(weekday - BDay(1)).date(), instrument=instrument)
431
+ portfolio.currency = instrument.currency
432
+ portfolio.save()
432
433
 
434
+ instrument_price_factory.create(date=(weekday - BDay(1)).date(), instrument=instrument)
433
435
  a1 = asset_position_factory.create(
434
436
  portfolio=portfolio, date=weekday, underlying_instrument=instrument, currency=instrument.currency
435
437
  )
@@ -1016,41 +1018,45 @@ class TestPortfolioModel(PortfolioTestMixin):
1016
1018
  assert a
1017
1019
 
1018
1020
  def test_batch_portfolio_with_rebalancer(
1019
- self, weekday, rebalancer_factory, portfolio, asset_position_factory, instrument_price_factory
1021
+ self,
1022
+ weekday,
1023
+ rebalancer_factory,
1024
+ portfolio,
1025
+ asset_position_factory,
1026
+ instrument_price_factory,
1027
+ instrument_factory,
1020
1028
  ):
1021
1029
  middle_date = (weekday + BDay(1)).date()
1022
1030
  rebalancing_date = (middle_date + BDay(1)).date()
1023
1031
 
1024
- a1 = asset_position_factory.create(date=weekday, portfolio=portfolio, weighting=0.7)
1025
- a2 = asset_position_factory.create(date=weekday, portfolio=portfolio, weighting=0.3)
1026
-
1027
- instrument_price_factory.create(instrument=a1.underlying_instrument, date=(weekday - BDay(1)).date())
1028
- instrument_price_factory.create(instrument=a1.underlying_instrument, date=weekday)
1029
- instrument_price_factory.create(instrument=a1.underlying_instrument, date=middle_date)
1030
- instrument_price_factory.create(instrument=a1.underlying_instrument, date=rebalancing_date)
1031
- instrument_price_factory.create(instrument=a2.underlying_instrument, date=(weekday - BDay(1)).date())
1032
- instrument_price_factory.create(instrument=a2.underlying_instrument, date=weekday)
1033
- instrument_price_factory.create(instrument=a2.underlying_instrument, date=middle_date)
1034
- instrument_price_factory.create(instrument=a2.underlying_instrument, date=rebalancing_date)
1032
+ i1 = instrument_factory.create(currency=portfolio.currency)
1033
+ i2 = instrument_factory.create(currency=portfolio.currency)
1034
+ asset_position_factory.create(date=weekday, portfolio=portfolio, underlying_instrument=i1, weighting=0.7)
1035
+ asset_position_factory.create(date=weekday, portfolio=portfolio, underlying_instrument=i2, weighting=0.3)
1036
+
1037
+ instrument_price_factory.create(instrument=i1, date=(weekday - BDay(1)).date())
1038
+ instrument_price_factory.create(instrument=i1, date=weekday)
1039
+ instrument_price_factory.create(instrument=i1, date=middle_date)
1040
+ instrument_price_factory.create(instrument=i1, date=rebalancing_date)
1041
+ instrument_price_factory.create(instrument=i2, date=(weekday - BDay(1)).date())
1042
+ instrument_price_factory.create(instrument=i2, date=weekday)
1043
+ instrument_price_factory.create(instrument=i2, date=middle_date)
1044
+ instrument_price_factory.create(instrument=i2, date=rebalancing_date)
1035
1045
 
1036
1046
  rebalancer_factory.create(portfolio=portfolio, frequency="RRULE:FREQ=DAILY;", activation_date=rebalancing_date)
1037
1047
  rebalancing_trade_proposal = portfolio.batch_portfolio(weekday, rebalancing_date)
1038
1048
 
1039
1049
  # check that the position before the rebalancing date were created
1040
- assert portfolio.assets.get(date=middle_date, underlying_instrument=a1.underlying_instrument)
1041
- assert portfolio.assets.get(date=middle_date, underlying_instrument=a2.underlying_instrument)
1050
+ assert portfolio.assets.get(date=middle_date, underlying_instrument=i1)
1051
+ assert portfolio.assets.get(date=middle_date, underlying_instrument=i2)
1042
1052
  with pytest.raises(
1043
1053
  AssetPosition.DoesNotExist
1044
1054
  ): # there is no asset position because the rebalancing stopped it:
1045
1055
  portfolio.assets.get(date=rebalancing_date)
1046
1056
 
1047
1057
  # we expect a equally rebalancing (default) so both trades needs to be created
1048
- t1 = rebalancing_trade_proposal.trades.get(
1049
- transaction_date=rebalancing_date, underlying_instrument=a1.underlying_instrument
1050
- )
1051
- t2 = rebalancing_trade_proposal.trades.get(
1052
- transaction_date=rebalancing_date, underlying_instrument=a2.underlying_instrument
1053
- )
1058
+ t1 = rebalancing_trade_proposal.trades.get(transaction_date=rebalancing_date, underlying_instrument=i1)
1059
+ t2 = rebalancing_trade_proposal.trades.get(transaction_date=rebalancing_date, underlying_instrument=i2)
1054
1060
  assert t1._target_weight == Decimal("0.5")
1055
1061
  assert t2._target_weight == Decimal("0.5")
1056
1062
 
@@ -1060,12 +1066,8 @@ class TestPortfolioModel(PortfolioTestMixin):
1060
1066
  rebalancing_trade_proposal.save()
1061
1067
 
1062
1068
  # check that the rebalancing was applied and position reflect that
1063
- assert portfolio.assets.get(
1064
- date=rebalancing_date, underlying_instrument=a1.underlying_instrument
1065
- ).weighting == Decimal("0.5")
1066
- assert portfolio.assets.get(
1067
- date=rebalancing_date, underlying_instrument=a2.underlying_instrument
1068
- ).weighting == Decimal("0.5")
1069
+ assert portfolio.assets.get(date=rebalancing_date, underlying_instrument=i1).weighting == Decimal("0.5")
1070
+ assert portfolio.assets.get(date=rebalancing_date, underlying_instrument=i2).weighting == Decimal("0.5")
1069
1071
 
1070
1072
  def test_bulk_create_positions(self, portfolio, weekday, asset_position_factory, instrument_factory):
1071
1073
  portfolio.is_manageable = False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbportfolio
3
- Version: 1.46.12
3
+ Version: 1.46.13
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  License-File: LICENSE
6
6
  Requires-Dist: cryptography==3.4.*
@@ -245,8 +245,9 @@ wbportfolio/models/__init__.py,sha256=PDLJry5w1zE4N4arQh20_uFi2v7gy9QyavJ_rfGE21
245
245
  wbportfolio/models/adjustments.py,sha256=osXWkJZOiansPWYPyHtl7Z121zDWi7u1YMtrBQtbHVo,10272
246
246
  wbportfolio/models/asset.py,sha256=TEOsSlbog6Yw0PZiMxjnCccp6ApXAAJNd3BIx_wU7D8,37749
247
247
  wbportfolio/models/custodians.py,sha256=owTiS2Vm5CRKzh9M_P9GOVg-s-ndQ9UvRmw3yZP7cw0,3815
248
+ wbportfolio/models/exceptions.py,sha256=3ix0tWUO-O6jpz8f07XIwycw2x3JFRoWzjwil8FVA2Q,52
248
249
  wbportfolio/models/indexes.py,sha256=iLYF2gzNzX4GLj_Nh3fybUcAQ1TslnT0wgQ6mN164QI,728
249
- wbportfolio/models/portfolio.py,sha256=eGWhPlkIMYwK10__3jU1Gy1JwpwkJWzCyv7ov17mRRI,57509
250
+ wbportfolio/models/portfolio.py,sha256=oW1wUCT8wgXRamhaqjnPjiU0eYC3dbPWbR63OMAQmFQ,57994
250
251
  wbportfolio/models/portfolio_cash_flow.py,sha256=2blPiXSw7dbhUVd-7LcxDBb4v0SheNOdvRK3MFYiChA,7273
251
252
  wbportfolio/models/portfolio_cash_targets.py,sha256=WmgG-etPisZsh2yaFQpz7EkpvAudKBEzqPsO715w52U,1498
252
253
  wbportfolio/models/portfolio_relationship.py,sha256=mMb18UMRWg9kx_9uIPkMktwORuXXLjKdgRPQQvB6fVE,5486
@@ -371,7 +372,7 @@ wbportfolio/tests/models/test_merge.py,sha256=sdsjiZsmR6vsUKwTa5kkvL6QTeAZqtd_EP
371
372
  wbportfolio/tests/models/test_portfolio_cash_flow.py,sha256=X8dsXexsb1b0lBiuGzu40ps_Az_1UmmKT0eo1vbXH94,5792
372
373
  wbportfolio/tests/models/test_portfolio_cash_targets.py,sha256=q8QWAwt-kKRkLC0E05GyRhF_TTQXIi8bdHjXVU0fCV0,965
373
374
  wbportfolio/tests/models/test_portfolio_swing_pricings.py,sha256=kr2AOcQkyg2pX3ULjU-o9ye-NVpjMrrfoe-DVbYCbjs,1656
374
- wbportfolio/tests/models/test_portfolios.py,sha256=Yd3aTeg8-bKCIJHL4jy14yWVNMEwyb3lzsFyu-Br_mM,52078
375
+ wbportfolio/tests/models/test_portfolios.py,sha256=ZXFqvCI2ozJmVTPmhtAEVaBaTz2BW73re1afsmKXJNc,52003
375
376
  wbportfolio/tests/models/test_product_groups.py,sha256=AcdxhurV-n_bBuUsfD1GqVtwLFcs7VI2CRrwzsIUWbU,3337
376
377
  wbportfolio/tests/models/test_products.py,sha256=5YYmQreFnaKLbWmrSib103wgLalqn8u01Fnh3A0XMz8,8217
377
378
  wbportfolio/tests/models/test_roles.py,sha256=4Cn7WyrA2ztJNeWLk5cy9kYo5XLWMbFSvo1O-9JYxeA,3323
@@ -517,7 +518,7 @@ wbportfolio/viewsets/transactions/rebalancing.py,sha256=6rIrdK0rtKL1afJ-tYfAGdQV
517
518
  wbportfolio/viewsets/transactions/trade_proposals.py,sha256=gXdJJ00D6UavZfBczvZQb5cYPQlU6-xOg_-TnMBzEO0,4742
518
519
  wbportfolio/viewsets/transactions/trades.py,sha256=mo5b1wFm0twvGVp-CYnzpGLYMqPcHN8GjH4G_WwFFwc,16237
519
520
  wbportfolio/viewsets/transactions/transactions.py,sha256=ixDp-nsNA8t_A06rBCT19hOMJHy0iRmdz1XKdV1OwAs,4450
520
- wbportfolio-1.46.12.dist-info/METADATA,sha256=7Jdv2fopJVYlVfZ7aigzy_f_wdEQD4BjpcsmKdL3CZE,735
521
- wbportfolio-1.46.12.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
522
- wbportfolio-1.46.12.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
523
- wbportfolio-1.46.12.dist-info/RECORD,,
521
+ wbportfolio-1.46.13.dist-info/METADATA,sha256=Z2xctYKKSO27XMnm51D_LmzDTxkXe2MfH0jsjzeu150,735
522
+ wbportfolio-1.46.13.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
523
+ wbportfolio-1.46.13.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
524
+ wbportfolio-1.46.13.dist-info/RECORD,,