wbportfolio 1.54.10__py2.py3-none-any.whl → 1.54.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.

@@ -0,0 +1,19 @@
1
+ # Generated by Django 5.0.14 on 2025-07-16 12:39
2
+
3
+ from decimal import Decimal
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('wbportfolio', '0080_alter_trade_drift_factor_alter_trade_weighting'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AlterField(
15
+ model_name='trade',
16
+ name='drift_factor',
17
+ field=models.DecimalField(decimal_places=16, default=Decimal('1'), help_text='Drift factor to be applied to the previous portfolio weight to get the actual effective weight including daily return', max_digits=19, verbose_name='Drift Factor'),
18
+ ),
19
+ ]
@@ -18,14 +18,16 @@ from django.db.models import (
18
18
  Q,
19
19
  QuerySet,
20
20
  Sum,
21
+ Value,
21
22
  )
23
+ from django.db.models.functions import Coalesce
22
24
  from django.db.models.signals import post_save
23
25
  from django.dispatch import receiver
24
26
  from django.utils import timezone
25
27
  from django.utils.functional import cached_property
26
28
  from pandas._libs.tslibs.offsets import BDay
27
29
  from skfolio.preprocessing import prices_to_returns
28
- from wbcore.contrib.currency.models import Currency
30
+ from wbcore.contrib.currency.models import Currency, CurrencyFXRates
29
31
  from wbcore.contrib.notifications.utils import create_notification_type
30
32
  from wbcore.models import WBModel
31
33
  from wbcore.utils.importlib import import_from_dotted_path
@@ -63,6 +65,7 @@ def get_returns(
63
65
  to_date: date,
64
66
  to_currency: Currency | None = None,
65
67
  ffill_returns: bool = True,
68
+ use_dl: bool = False,
66
69
  ) -> tuple[dict[date, dict[int, float]], pd.DataFrame]:
67
70
  """
68
71
  Utility methods to get instrument returns for a given date range
@@ -70,46 +73,53 @@ def get_returns(
70
73
  Args:
71
74
  from_date: date range lower bound
72
75
  to_date: date range upper bound
76
+ to_currency: currency to use for returns
77
+ ffill_returns: whether to ffill returns and prices
78
+ use_dl: whether to get data straight from the dataloader or use the internal table
73
79
 
74
80
  Returns:
75
- Return a tuple of the returns and the last prices series for conveniance
81
+ Return a tuple of the raw prices and the returns dataframe
76
82
  """
77
- # if to_currency:
78
- # fx_rate = CurrencyFXRates.get_fx_rates_subquery_for_two_currencies("date", "instrument__currency", to_currency)
79
- # else:
80
- # fx_rate = Value(Decimal(1.0))
81
- # prices = InstrumentPrice.objects.filter(
82
- # instrument__in=instrument_ids, date__gte=from_date, date__lte=to_date
83
- # ).annotate(fx_rate=fx_rate, price_fx_portfolio=F("net_value") * F("fx_rate"))
84
- # prices_df = (
85
- # pd.DataFrame(
86
- # prices.filter_only_valid_prices().values_list("instrument", "price_fx_portfolio", "date"),
87
- # columns=["instrument", "price_fx_portfolio", "date"],
88
- # )
89
- # .pivot_table(index="date", values="price_fx_portfolio", columns="instrument")
90
- # .astype(float)
91
- # .sort_index()
92
- # )
93
- kwargs = dict(from_date=from_date, to_date=to_date, values=[MarketData.CLOSE], apply_fx_rate=False)
94
- if to_currency:
95
- kwargs["target_currency"] = to_currency.key
96
- df = pd.DataFrame(Instrument.objects.filter(id__in=instrument_ids).dl.market_data(**kwargs))
83
+ if use_dl:
84
+ kwargs = dict(from_date=from_date, to_date=to_date, values=[MarketData.CLOSE], apply_fx_rate=False)
85
+ if to_currency:
86
+ kwargs["target_currency"] = to_currency.key
87
+ df = pd.DataFrame(Instrument.objects.filter(id__in=instrument_ids).dl.market_data(**kwargs))
88
+ if df.empty:
89
+ raise InvalidAnalyticPortfolio()
90
+ df = df[["instrument_id", "fx_rate", "close", "valuation_date"]]
91
+ else:
92
+ if to_currency:
93
+ fx_rate = Coalesce(
94
+ CurrencyFXRates.get_fx_rates_subquery_for_two_currencies("date", "instrument__currency", to_currency),
95
+ Decimal("1"),
96
+ )
97
+ else:
98
+ fx_rate = Value(Decimal("1"))
99
+ prices = InstrumentPrice.objects.filter(
100
+ instrument__in=instrument_ids, date__gte=from_date, date__lte=to_date
101
+ ).annotate(fx_rate=fx_rate)
102
+ df = pd.DataFrame(
103
+ prices.filter_only_valid_prices().values_list("instrument", "fx_rate", "net_value", "date"),
104
+ columns=["instrument_id", "fx_rate", "close", "valuation_date"],
105
+ )
106
+
97
107
  if df.empty:
98
108
  raise InvalidAnalyticPortfolio()
99
- df = df[["instrument_id", "fx_rate", "close", "valuation_date"]].pivot(
100
- index="valuation_date", columns="instrument_id", values=["fx_rate", "close"]
109
+ df = (
110
+ df.pivot_table(index="valuation_date", columns="instrument_id", values=["fx_rate", "close"])
111
+ .astype(float)
112
+ .sort_index()
101
113
  )
102
114
  ts = pd.bdate_range(df.index.min(), df.index.max(), freq="B")
103
115
  df = df.reindex(ts)
104
116
  if ffill_returns:
105
117
  df = df.ffill()
106
118
  df.index = pd.to_datetime(df.index)
107
-
108
119
  prices_df = df["close"]
109
120
  fx_rate_df = df["fx_rate"]
110
-
111
121
  returns = prices_to_returns(fx_rate_df * prices_df, drop_inceptions_nan=False, fill_nan=ffill_returns)
112
- return {ts.date(): row for ts, row in prices_df.replace(np.nan, None).to_dict("index").items()}, returns.replace(
122
+ return {ts.date(): row for ts, row in prices_df.to_dict("index").items()}, returns.replace(
113
123
  [np.inf, -np.inf, np.nan], 0
114
124
  )
115
125
 
@@ -833,6 +843,7 @@ class Portfolio(DeleteToDisableMixin, WBModel):
833
843
  end_date,
834
844
  to_currency=self.currency,
835
845
  ffill_returns=True,
846
+ use_dl=True,
836
847
  )
837
848
  # Get raw prices to speed up asset position creation
838
849
  # Instantiate the position iterator with the initial weights
@@ -849,7 +860,6 @@ class Portfolio(DeleteToDisableMixin, WBModel):
849
860
  drifted_weights = analytic_portfolio.get_next_weights()
850
861
  except KeyError: # if no return for that date, we break and continue
851
862
  drifted_weights = weights
852
-
853
863
  if rebalancer and rebalancer.is_valid(to_date):
854
864
  effective_portfolio = PortfolioDTO(
855
865
  positions=[
@@ -366,6 +366,8 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
366
366
  trade.weighting = -trade_dto.effective_weight
367
367
 
368
368
  trade.save()
369
+ # final sanity check to make sure invalid trade with effective and target weight of 0 are automatically removed:
370
+ self.trades.all().annotate_base_info().filter(target_weight=0, effective_weight=0).delete()
369
371
 
370
372
  def approve_workflow(
371
373
  self,
@@ -230,8 +230,10 @@ class Trade(TransactionMixin, ImportMixin, OrderedModel, models.Model):
230
230
  help_text="The Trade Proposal this trade is coming from",
231
231
  )
232
232
  drift_factor = models.DecimalField(
233
- max_digits=16,
234
- decimal_places=TRADE_WEIGHTING_PRECISION,
233
+ max_digits=TRADE_WEIGHTING_PRECISION * 2
234
+ + 3, # we don't expect any drift factor to be in the order of magnitude greater than 1000
235
+ decimal_places=TRADE_WEIGHTING_PRECISION
236
+ * 2, # we need a higher precision for this factor to avoid float inprecision
235
237
  default=Decimal(1.0),
236
238
  verbose_name="Drift Factor",
237
239
  help_text="Drift factor to be applied to the previous portfolio weight to get the actual effective weight including daily return",
@@ -16,10 +16,10 @@ class Portfolio(BasePortfolio):
16
16
  )
17
17
  return df
18
18
 
19
- def get_next_weights(self) -> dict[int, float]:
19
+ def get_next_weights(self, round_precision: int = 8) -> dict[int, float]:
20
20
  """
21
21
  Given the next returns, compute the drifted weights of this portfolio
22
-
22
+ round_precision: Round the weight to the given round number and ensure the total weight reflects this. Default to 8 decimals
23
23
  Returns:
24
24
  A dictionary of weights (instrument ids as keys and weights as values)
25
25
  """
@@ -29,7 +29,12 @@ class Portfolio(BasePortfolio):
29
29
  next_weights = weights * (returns + 1.0) / portfolio_returns
30
30
  next_weights = next_weights.dropna()
31
31
  next_weights = next_weights / next_weights.sum()
32
- return next_weights.to_dict()
32
+ if round_precision and not next_weights.empty:
33
+ next_weights = next_weights.round(round_precision)
34
+ quantization_error = 1.0 - next_weights.sum()
35
+ largest_weight = next_weights.idxmax()
36
+ next_weights.loc[largest_weight] = next_weights.loc[largest_weight] + quantization_error
37
+ return {i: round(w, round_precision) for i, w in next_weights.items()} # handle float precision manually
33
38
 
34
39
  def get_estimate_net_value(self, previous_net_asset_value: float) -> float:
35
40
  expected_returns = self.weights @ self.X.iloc[-1, :].T
@@ -336,6 +336,26 @@ class TestTradeProposal:
336
336
  assert t2.weighting == Decimal("0")
337
337
  assert t3.weighting == Decimal("0.5")
338
338
 
339
+ def test_reset_trades_remove_invalid_trades(self, trade_proposal, trade_factory, instrument_price_factory):
340
+ # create a invalid trade and its price
341
+ invalid_trade = trade_factory.create(trade_proposal=trade_proposal, weighting=0)
342
+ instrument_price_factory.create(
343
+ date=invalid_trade.transaction_date, instrument=invalid_trade.underlying_instrument
344
+ )
345
+
346
+ # create a valid trade and its price
347
+ valid_trade = trade_factory.create(trade_proposal=trade_proposal, weighting=1.0)
348
+ instrument_price_factory.create(
349
+ date=valid_trade.transaction_date, instrument=valid_trade.underlying_instrument
350
+ )
351
+
352
+ trade_proposal.reset_trades()
353
+ assert trade_proposal.trades.get(underlying_instrument=valid_trade.underlying_instrument).weighting == Decimal(
354
+ "1"
355
+ )
356
+ with pytest.raises(Trade.DoesNotExist):
357
+ trade_proposal.trades.get(underlying_instrument=invalid_trade.underlying_instrument)
358
+
339
359
  # Test replaying trade proposals
340
360
  @patch.object(Portfolio, "drift_weights")
341
361
  def test_replay(self, mock_fct, trade_proposal_factory):
@@ -16,9 +16,27 @@ def test_get_next_weights():
16
16
  portfolio = Portfolio(X=pd.DataFrame([returns]), weights=pd.Series(weights))
17
17
  next_weights = portfolio.get_next_weights()
18
18
 
19
- assert next_weights[0] == pytest.approx(w0 * (r0 + 1) / (w0 * (r0 + 1) + w1 * (r1 + 1) + w2 * (r2 + 1)), abs=10e-6)
20
- assert next_weights[1] == pytest.approx(w1 * (r1 + 1) / (w0 * (r0 + 1) + w1 * (r1 + 1) + w2 * (r2 + 1)), abs=10e-6)
21
- assert next_weights[2] == pytest.approx(w2 * (r2 + 1) / (w0 * (r0 + 1) + w1 * (r1 + 1) + w2 * (r2 + 1)), abs=10e-6)
19
+ assert next_weights[0] == pytest.approx(w0 * (r0 + 1) / (w0 * (r0 + 1) + w1 * (r1 + 1) + w2 * (r2 + 1)), abs=10e-8)
20
+ assert next_weights[1] == pytest.approx(w1 * (r1 + 1) / (w0 * (r0 + 1) + w1 * (r1 + 1) + w2 * (r2 + 1)), abs=10e-8)
21
+ assert next_weights[2] == pytest.approx(w2 * (r2 + 1) / (w0 * (r0 + 1) + w1 * (r1 + 1) + w2 * (r2 + 1)), abs=10e-8)
22
+
23
+
24
+ def test_get_next_weights_solve_quantization_error():
25
+ w0 = 0.33333334
26
+ w1 = 0.33333333
27
+ w2 = 0.33333333
28
+ weights = [w0, w1, w2]
29
+ returns = [1.0, 1.0, 1.0] # no returns
30
+ portfolio = Portfolio(X=pd.DataFrame([returns]), weights=pd.Series(weights))
31
+ next_weights = portfolio.get_next_weights(round_precision=8) # no rounding as number are all 8 decimals
32
+ assert sum(next_weights.values()) == 1.0
33
+ next_weights = portfolio.get_next_weights(
34
+ round_precision=7
35
+ ) # we expect the weight to be rounded to 6 decimals, which would lead to a total sum of 0.999999
36
+
37
+ assert next_weights[0] == 0.3333334
38
+ assert next_weights[1] == 0.3333333
39
+ assert next_weights[2] == 0.3333333
22
40
 
23
41
 
24
42
  def test_get_estimate_net_value():
@@ -133,7 +133,7 @@ class AssetPositionModelViewSet(
133
133
  weighting = queryset.aggregate(s=Sum(F("weighting")))["s"]
134
134
  aggregates.update(
135
135
  {
136
- "weighting": {"Σ": format_number(weighting, decimal=4)},
136
+ "weighting": {"Σ": format_number(weighting, decimal=8)},
137
137
  "total_value_fx_usd": {"Σ": format_number(total_value_fx_usd)},
138
138
  }
139
139
  )
@@ -204,7 +204,7 @@ class AssetPositionPortfolioModelViewSet(InstrumentMetricMixin, AssetPositionMod
204
204
  total_value_fx_portfolio = queryset.aggregate(s=Sum(F("total_value_fx_portfolio")))["s"]
205
205
  aggregates = super().get_aggregates(queryset, paginated_queryset)
206
206
  aggregates["total_value_fx_portfolio"] = {"Σ": format_number(total_value_fx_portfolio)}
207
- aggregates["weighting"] = {"Σ": format_number(weighting, decimal=4)}
207
+ aggregates["weighting"] = {"Σ": format_number(weighting, decimal=8)}
208
208
  return aggregates
209
209
 
210
210
  def get_queryset(self):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbportfolio
3
- Version: 1.54.10
3
+ Version: 1.54.11
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  License-File: LICENSE
6
6
  Requires-Dist: cryptography==3.4.*
@@ -250,6 +250,7 @@ wbportfolio/migrations/0077_remove_transaction_currency_and_more.py,sha256=Yf4a3
250
250
  wbportfolio/migrations/0078_trade_drift_factor.py,sha256=26Z3yoiBhMueB-k2R9HaIzg5Qr7BYpdtzlU-65T_cH0,999
251
251
  wbportfolio/migrations/0079_alter_trade_drift_factor.py,sha256=2tvPecUxEy60-ELy9wtiuTR2dhJ8HucJjvOEuia4Pp4,627
252
252
  wbportfolio/migrations/0080_alter_trade_drift_factor_alter_trade_weighting.py,sha256=vCPJSrM7x24jUJseGkHEVBD0c5nEpDTrb9-zkJ-0QoI,569
253
+ wbportfolio/migrations/0081_alter_trade_drift_factor.py,sha256=rF3HA1MQJ0hltr0dExJAx47w8XxUCWRbDUyxQLoQB2I,656
253
254
  wbportfolio/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
254
255
  wbportfolio/models/__init__.py,sha256=HSpa5xwh_MHQaBpNrq9E0CbdEE5Iq-pDLIsPzZ-TRTg,904
255
256
  wbportfolio/models/adjustments.py,sha256=osXWkJZOiansPWYPyHtl7Z121zDWi7u1YMtrBQtbHVo,10272
@@ -257,7 +258,7 @@ wbportfolio/models/asset.py,sha256=b0vPt4LwNrxcMiK7UmBKViYnbNNlZzPTagvU5vFuyrc,4
257
258
  wbportfolio/models/custodians.py,sha256=owTiS2Vm5CRKzh9M_P9GOVg-s-ndQ9UvRmw3yZP7cw0,3815
258
259
  wbportfolio/models/exceptions.py,sha256=3ix0tWUO-O6jpz8f07XIwycw2x3JFRoWzjwil8FVA2Q,52
259
260
  wbportfolio/models/indexes.py,sha256=gvW4K9U9Bj8BmVCqFYdWiXvDWhjHINRON8XhNsZUiQY,639
260
- wbportfolio/models/portfolio.py,sha256=2m35YgEsF1gkt_JN5O4UlOtNLAqSfKi8JUp0G-1HPmQ,57997
261
+ wbportfolio/models/portfolio.py,sha256=5gOPWWv-_ke-p-B14S9ZNKRvvGQVHNBzR2r5xL-AKm0,58341
261
262
  wbportfolio/models/portfolio_cash_flow.py,sha256=uElG7IJUBY8qvtrXftOoskX6EA-dKgEG1JJdvHeWV7g,7336
262
263
  wbportfolio/models/portfolio_cash_targets.py,sha256=WmgG-etPisZsh2yaFQpz7EkpvAudKBEzqPsO715w52U,1498
263
264
  wbportfolio/models/portfolio_relationship.py,sha256=ZGECiPZiLdlk4uSamOrEfuzO0hduK6OMKJLUSnh5_kc,5190
@@ -284,13 +285,13 @@ wbportfolio/models/transactions/claim.py,sha256=SF2FlwG6SRVmA_hT0NbXah5-fYejccWK
284
285
  wbportfolio/models/transactions/dividends.py,sha256=mmOdGWR35yndUMoCuG24Y6BdtxDhSk2gMQ-8LVguqzg,1890
285
286
  wbportfolio/models/transactions/fees.py,sha256=wJtlzbBCAq1UHvv0wqWTE2BEjCF5RMtoaSDS3kODFRo,7112
286
287
  wbportfolio/models/transactions/rebalancing.py,sha256=rwePcmTZOYgfSWnBQcBrZ3DQHRJ3w17hdO_hgrRbbhI,7696
287
- wbportfolio/models/transactions/trade_proposals.py,sha256=UYhV2pjQE4V-k0tVTBeZgPPK0C-tBeY345iroDzulfg,38106
288
- wbportfolio/models/transactions/trades.py,sha256=oAUWCPPdBcMf5bcYI6BwvnrFKK588ZLRHPyPr65h-0U,33966
288
+ wbportfolio/models/transactions/trade_proposals.py,sha256=Iilb67WdUh4XuBhxMqnnxTj_43NxTloGNNtJQh1izD8,38327
289
+ wbportfolio/models/transactions/trades.py,sha256=1gmAdavuWu1Iko90s9prMxsK_NuDKIUBIKMDuHiKzow,34176
289
290
  wbportfolio/models/transactions/transactions.py,sha256=XTcUeMUfkf5XTSZaR2UAyGqCVkOhQYk03_vzHLIgf8Q,3807
290
291
  wbportfolio/pms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
291
292
  wbportfolio/pms/typing.py,sha256=BV4dzazNHdfpfLV99bLVyYGcETmbQSnFV6ipc4fNKfg,8470
292
293
  wbportfolio/pms/analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
293
- wbportfolio/pms/analytics/portfolio.py,sha256=n_tv4gX-mOFfKCxhSGblqQzW62Bx-yoM2QcJxQd3dLE,1384
294
+ wbportfolio/pms/analytics/portfolio.py,sha256=QaYArF-8Dk9MY0ZLUZ1IHaz3T-uYG81P5SqS_jSar8A,1950
294
295
  wbportfolio/pms/statistics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
295
296
  wbportfolio/pms/trading/__init__.py,sha256=R_yLKc54sCak8A1cW0O1Aszrcv5KV8mC_3h17Hr20e4,36
296
297
  wbportfolio/pms/trading/handler.py,sha256=ZOwgnOU4ScVIhTMRQ0SLR2cCCZP9whmVv-S5hF-TOME,8593
@@ -390,10 +391,10 @@ wbportfolio/tests/models/transactions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCe
390
391
  wbportfolio/tests/models/transactions/test_claim.py,sha256=NG3BKB-FVcIDgHSJHCjImxgMM3ISVUMl24xUPmEcPec,5570
391
392
  wbportfolio/tests/models/transactions/test_fees.py,sha256=tAp18x2wCNQr11LUnLtHNbBDbbX0v1DZnmW7i-cEi5Q,2423
392
393
  wbportfolio/tests/models/transactions/test_rebalancing.py,sha256=fZ5tx6kByEGXD6nhapYdvk9HOjYlmjhU2w6KlQJ6QE4,4061
393
- wbportfolio/tests/models/transactions/test_trade_proposals.py,sha256=_EH7N2-oTqgz13NGSahD1d0rIiu93Ce2rcA9A8gUlSg,29800
394
+ wbportfolio/tests/models/transactions/test_trade_proposals.py,sha256=INZb4J7qodXiT8FwbRNjEoXOm9HjO6W-vkA9uW8e3CI,30804
394
395
  wbportfolio/tests/models/transactions/test_trades.py,sha256=vqvOqUY_uXvBp8YOKR0Wq9ycA2oeeEBhO3dzV7sbXEU,9863
395
396
  wbportfolio/tests/pms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
396
- wbportfolio/tests/pms/test_analytics.py,sha256=WHicJBjAjpIRL1-AW2nZ4VD9oJRpMoeH6V1Qx2D95-w,1178
397
+ wbportfolio/tests/pms/test_analytics.py,sha256=KzgZqZ9yYB1gsokw6IU-uKYWr1eFHWyFte8RSpMVRg8,1897
397
398
  wbportfolio/tests/rebalancing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
398
399
  wbportfolio/tests/rebalancing/test_models.py,sha256=QMfcYDvFew1bH6kPm-jVJLC_RqmPE-oGTqUldx1KVgg,8025
399
400
  wbportfolio/tests/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -406,7 +407,7 @@ wbportfolio/tests/viewsets/transactions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J
406
407
  wbportfolio/tests/viewsets/transactions/test_claims.py,sha256=QEZfMAW07dyoZ63t2umSwGOqvaTULfYfbN_F4ZoSAcw,6368
407
408
  wbportfolio/viewsets/__init__.py,sha256=3kUaQ66ybvROwejd3bEcSt4XKzfOlPDaeoStMvlz7qY,2294
408
409
  wbportfolio/viewsets/adjustments.py,sha256=ugbX4aFRCaD4Yj1hxL-VIPaNI7GF_wt0FrkN6mq1YjU,1524
409
- wbportfolio/viewsets/assets.py,sha256=nMMVh7P531KZ-zpFogIMFKbfvRD3KdG2oIAAHQ42lpc,24634
410
+ wbportfolio/viewsets/assets.py,sha256=MCE81rmDwbMGxO_LsD8AvU9tHWWgi-OkX5ecLFH-KGY,24634
410
411
  wbportfolio/viewsets/assets_and_net_new_money_progression.py,sha256=Jl4vEQP4N2OFL5IGBXoKcj-0qaPviU0I8npvQLw4Io0,4464
411
412
  wbportfolio/viewsets/custodians.py,sha256=CTFqkqVP1R3AV7lhdvcdICxB5DfwDYCyikNSI5kbYEo,2322
412
413
  wbportfolio/viewsets/esg.py,sha256=27MxxdXQH3Cq_1UEYmcrF7htUOg6i81fUpbVQXAAKJI,6985
@@ -522,7 +523,7 @@ wbportfolio/viewsets/transactions/mixins.py,sha256=WipvJoi5hylkpD0y9VATe30WAcwIH
522
523
  wbportfolio/viewsets/transactions/rebalancing.py,sha256=6rIrdK0rtKL1afJ-tYfAGdQVTN2MH1kG_yCeVkmyK8k,1263
523
524
  wbportfolio/viewsets/transactions/trade_proposals.py,sha256=kQCojTNKBEyn2NcenL3a9auzBH4sIgLEx8rLAYCGLGg,6161
524
525
  wbportfolio/viewsets/transactions/trades.py,sha256=Y8v2cM0vpspHysaAvu8qqhzt86dNtb2Q3puo4HCJsTI,22629
525
- wbportfolio-1.54.10.dist-info/METADATA,sha256=uKqFJps-zs7857cq_8X_JmogU7gB5LG3RaZORctTI-k,703
526
- wbportfolio-1.54.10.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
527
- wbportfolio-1.54.10.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
528
- wbportfolio-1.54.10.dist-info/RECORD,,
526
+ wbportfolio-1.54.11.dist-info/METADATA,sha256=PhblDUHcEGHQmY_KCEcvlc3oIl98hhageKfJGRshiPg,703
527
+ wbportfolio-1.54.11.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
528
+ wbportfolio-1.54.11.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
529
+ wbportfolio-1.54.11.dist-info/RECORD,,