wbportfolio 1.49.10__py2.py3-none-any.whl → 1.49.11__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 +4 -7
- wbportfolio/models/portfolio.py +34 -19
- wbportfolio/tests/models/test_portfolios.py +2 -2
- {wbportfolio-1.49.10.dist-info → wbportfolio-1.49.11.dist-info}/METADATA +1 -1
- {wbportfolio-1.49.10.dist-info → wbportfolio-1.49.11.dist-info}/RECORD +7 -7
- {wbportfolio-1.49.10.dist-info → wbportfolio-1.49.11.dist-info}/WHEEL +0 -0
- {wbportfolio-1.49.10.dist-info → wbportfolio-1.49.11.dist-info}/licenses/LICENSE +0 -0
wbportfolio/models/asset.py
CHANGED
|
@@ -88,7 +88,7 @@ class AssetPositionIterator:
|
|
|
88
88
|
# Initialize data stores with type hints
|
|
89
89
|
self._instruments = {}
|
|
90
90
|
self._fx_rates = defaultdict(dict)
|
|
91
|
-
self._weights = dict
|
|
91
|
+
self._weights = defaultdict(dict)
|
|
92
92
|
self._prices = prices or defaultdict(dict)
|
|
93
93
|
self.positions = dict()
|
|
94
94
|
|
|
@@ -121,7 +121,6 @@ class AssetPositionIterator:
|
|
|
121
121
|
currency_fx_rate_portfolio_to_usd = self._get_fx_rate(val_date, self.portfolio.currency)
|
|
122
122
|
currency_fx_rate_instrument_to_usd = self._get_fx_rate(val_date, underlying_quote.currency)
|
|
123
123
|
price = self._get_price(val_date, underlying_quote)
|
|
124
|
-
|
|
125
124
|
position = AssetPosition(
|
|
126
125
|
underlying_quote=underlying_quote,
|
|
127
126
|
weighting=weighting,
|
|
@@ -173,10 +172,8 @@ class AssetPositionIterator:
|
|
|
173
172
|
position.portfolio = self.portfolio
|
|
174
173
|
if position.initial_price is not None:
|
|
175
174
|
self.positions[key] = position
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
except KeyError:
|
|
179
|
-
self._weights[position.date] = {position.underlying_quote.id: float(position.weighting)}
|
|
175
|
+
self._weights[position.date][position.underlying_quote.id] = float(position.weighting)
|
|
176
|
+
|
|
180
177
|
return self
|
|
181
178
|
|
|
182
179
|
def get_dates(self) -> list[date]:
|
|
@@ -185,7 +182,7 @@ class AssetPositionIterator:
|
|
|
185
182
|
|
|
186
183
|
def get_weights(self) -> dict[date, dict[int, float]]:
|
|
187
184
|
"""Get weight structure with instrument IDs as keys"""
|
|
188
|
-
return self._weights
|
|
185
|
+
return dict(self._weights)
|
|
189
186
|
|
|
190
187
|
def __iter__(self):
|
|
191
188
|
# return an iterable excluding the position without a valid weighting
|
wbportfolio/models/portfolio.py
CHANGED
|
@@ -54,7 +54,24 @@ if TYPE_CHECKING:
|
|
|
54
54
|
from wbportfolio.models.transactions.trade_proposals import TradeProposal
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
def
|
|
57
|
+
def get_prices(instrument_ids: list[int], from_date: date, to_date: date) -> dict[date, dict[int, float]]:
|
|
58
|
+
"""
|
|
59
|
+
Utility to fetch raw prices
|
|
60
|
+
"""
|
|
61
|
+
prices = InstrumentPrice.objects.filter(instrument__in=instrument_ids, date__gte=from_date, date__lte=to_date)
|
|
62
|
+
df = (
|
|
63
|
+
pd.DataFrame(
|
|
64
|
+
prices.filter_only_valid_prices().values_list("instrument", "net_value", "date"),
|
|
65
|
+
columns=["instrument", "net_value", "date"],
|
|
66
|
+
)
|
|
67
|
+
.pivot_table(index="date", values="net_value", columns="instrument")
|
|
68
|
+
.astype(float)
|
|
69
|
+
.sort_index()
|
|
70
|
+
)
|
|
71
|
+
ts = pd.bdate_range(df.index.min(), df.index.max(), freq="B")
|
|
72
|
+
df = df.reindex(ts)
|
|
73
|
+
df = df.ffill()
|
|
74
|
+
df.index = pd.to_datetime(df.index)
|
|
58
75
|
return {ts.date(): row for ts, row in df.to_dict("index").items()}
|
|
59
76
|
|
|
60
77
|
|
|
@@ -64,7 +81,7 @@ def get_returns(
|
|
|
64
81
|
to_date: date,
|
|
65
82
|
to_currency: Currency | None = None,
|
|
66
83
|
ffill_returns: bool = True,
|
|
67
|
-
) ->
|
|
84
|
+
) -> pd.DataFrame:
|
|
68
85
|
"""
|
|
69
86
|
Utility methods to get instrument returns for a given date range
|
|
70
87
|
|
|
@@ -99,7 +116,7 @@ def get_returns(
|
|
|
99
116
|
prices_df = prices_df.ffill()
|
|
100
117
|
prices_df.index = pd.to_datetime(prices_df.index)
|
|
101
118
|
returns = prices_to_returns(prices_df, drop_inceptions_nan=False, fill_nan=ffill_returns)
|
|
102
|
-
return returns.replace([np.inf, -np.inf, np.nan], 0)
|
|
119
|
+
return returns.replace([np.inf, -np.inf, np.nan], 0)
|
|
103
120
|
|
|
104
121
|
|
|
105
122
|
class DefaultPortfolioQueryset(QuerySet):
|
|
@@ -364,7 +381,7 @@ class Portfolio(DeleteToDisableMixin, WBModel):
|
|
|
364
381
|
|
|
365
382
|
def get_analytic_portfolio(
|
|
366
383
|
self, val_date: date, weights: dict[int, float] | None = None, **kwargs
|
|
367
|
-
) ->
|
|
384
|
+
) -> AnalyticPortfolio:
|
|
368
385
|
"""
|
|
369
386
|
Return the analytic portfolio associated with this portfolio at the given date
|
|
370
387
|
|
|
@@ -378,18 +395,15 @@ class Portfolio(DeleteToDisableMixin, WBModel):
|
|
|
378
395
|
if not weights:
|
|
379
396
|
weights = self.get_weights(val_date)
|
|
380
397
|
return_date = (val_date + BDay(1)).date()
|
|
381
|
-
returns
|
|
398
|
+
returns = get_returns(
|
|
382
399
|
list(weights.keys()), (val_date - BDay(2)).date(), return_date, to_currency=self.currency, **kwargs
|
|
383
400
|
)
|
|
384
401
|
if pd.Timestamp(return_date) not in returns.index:
|
|
385
402
|
raise InvalidAnalyticPortfolio()
|
|
386
403
|
returns = returns.fillna(0) # not sure this is what we want
|
|
387
|
-
return (
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
weights=weights,
|
|
391
|
-
),
|
|
392
|
-
prices,
|
|
404
|
+
return AnalyticPortfolio(
|
|
405
|
+
X=returns,
|
|
406
|
+
weights=weights,
|
|
393
407
|
)
|
|
394
408
|
|
|
395
409
|
def is_invested_at_date(self, val_date: date) -> bool:
|
|
@@ -781,15 +795,18 @@ class Portfolio(DeleteToDisableMixin, WBModel):
|
|
|
781
795
|
weights = drifted_positions.get_weights()[start_date]
|
|
782
796
|
|
|
783
797
|
# Get returns and prices data for the whole date range
|
|
784
|
-
|
|
785
|
-
|
|
798
|
+
instrument_ids = list(weights.keys())
|
|
799
|
+
returns = get_returns(
|
|
800
|
+
instrument_ids,
|
|
786
801
|
(start_date - BDay(3)).date(),
|
|
787
802
|
end_date,
|
|
788
803
|
to_currency=self.currency,
|
|
789
804
|
ffill_returns=True,
|
|
790
805
|
)
|
|
806
|
+
# Get raw prices to speed up asset position creation
|
|
807
|
+
prices = get_prices(instrument_ids, (start_date - BDay(3)).date(), end_date)
|
|
791
808
|
# Instantiate the position iterator with the initial weights
|
|
792
|
-
positions = AssetPositionIterator(self, prices=
|
|
809
|
+
positions = AssetPositionIterator(self, prices=prices)
|
|
793
810
|
last_trade_proposal = None
|
|
794
811
|
for to_date_ts in pd.date_range(start_date + timedelta(days=1), end_date, freq="B"):
|
|
795
812
|
to_date = to_date_ts.date()
|
|
@@ -883,10 +900,8 @@ class Portfolio(DeleteToDisableMixin, WBModel):
|
|
|
883
900
|
|
|
884
901
|
setattr(position, "path", path)
|
|
885
902
|
position.initial_shares = None
|
|
886
|
-
if portfolio_total_asset_value:
|
|
887
|
-
position.initial_shares = (position.weighting * portfolio_total_asset_value) /
|
|
888
|
-
position.price * position.currency_fx_rate
|
|
889
|
-
)
|
|
903
|
+
if portfolio_total_asset_value and (price_fx_portfolio := position.price * position.currency_fx_rate):
|
|
904
|
+
position.initial_shares = (position.weighting * portfolio_total_asset_value) / price_fx_portfolio
|
|
890
905
|
if child_portfolio := position.underlying_quote.primary_portfolio:
|
|
891
906
|
if with_intermediary_position:
|
|
892
907
|
yield position
|
|
@@ -1191,7 +1206,7 @@ def default_estimate_net_value(
|
|
|
1191
1206
|
with suppress(
|
|
1192
1207
|
IndexError, InvalidAnalyticPortfolio
|
|
1193
1208
|
): # we silent any indexerror introduced by no returns for the past days
|
|
1194
|
-
analytic_portfolio
|
|
1209
|
+
analytic_portfolio = portfolio.get_analytic_portfolio(previous_val_date, weights=weights)
|
|
1195
1210
|
return analytic_portfolio.get_estimate_net_value(float(last_price.net_value))
|
|
1196
1211
|
|
|
1197
1212
|
|
|
@@ -925,7 +925,7 @@ class TestPortfolioModel(PortfolioTestMixin):
|
|
|
925
925
|
)
|
|
926
926
|
a2.refresh_from_db()
|
|
927
927
|
|
|
928
|
-
analytic_portfolio
|
|
928
|
+
analytic_portfolio = portfolio.get_analytic_portfolio(weekday)
|
|
929
929
|
assert analytic_portfolio.weights.tolist() == [float(a1.weighting), float(a2.weighting)]
|
|
930
930
|
expected_X = pd.DataFrame(
|
|
931
931
|
[[float(p11.net_value / p10.net_value - Decimal(1)), float(p21.net_value / p20.net_value - Decimal(1))]],
|
|
@@ -1139,7 +1139,7 @@ class TestPortfolioModel(PortfolioTestMixin):
|
|
|
1139
1139
|
i11.refresh_from_db()
|
|
1140
1140
|
i12.refresh_from_db()
|
|
1141
1141
|
i13.refresh_from_db()
|
|
1142
|
-
returns
|
|
1142
|
+
returns = get_returns([i1.id, i2.id], from_date=v1, to_date=v3)
|
|
1143
1143
|
|
|
1144
1144
|
expected_returns = pd.DataFrame(
|
|
1145
1145
|
[[i12.net_value / i11.net_value - 1, 0.0], [i13.net_value / i12.net_value - 1, 0.0]],
|
|
@@ -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=-bw8AoIIBy68je1vnGXYbkb7Vdp_mRBNJixd0qnCWxA,45153
|
|
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=Rq9ih9aPhLC1RukrIF_wrmtRQm0-3w4i9NEtWJGZ02g,55742
|
|
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=2VqUF3HALY1VvCksARk4XJlQL-qr2PbF2DMmw9KQzdY,53132
|
|
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.49.
|
|
525
|
-
wbportfolio-1.49.
|
|
526
|
-
wbportfolio-1.49.
|
|
527
|
-
wbportfolio-1.49.
|
|
524
|
+
wbportfolio-1.49.11.dist-info/METADATA,sha256=vbQQ5UCTEyqLH-YcOOF6BVCDXb3ujdegBOUX6k_iQ-8,735
|
|
525
|
+
wbportfolio-1.49.11.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
|
|
526
|
+
wbportfolio-1.49.11.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
|
|
527
|
+
wbportfolio-1.49.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|