wbportfolio 1.50.0__py2.py3-none-any.whl → 1.50.2__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 +2 -2
- wbportfolio/models/portfolio.py +3 -1
- wbportfolio/models/transactions/trade_proposals.py +4 -3
- wbportfolio/models/transactions/trades.py +23 -23
- wbportfolio/serializers/transactions/trades.py +63 -4
- wbportfolio/viewsets/configs/display/trades.py +10 -7
- {wbportfolio-1.50.0.dist-info → wbportfolio-1.50.2.dist-info}/METADATA +1 -1
- {wbportfolio-1.50.0.dist-info → wbportfolio-1.50.2.dist-info}/RECORD +10 -10
- {wbportfolio-1.50.0.dist-info → wbportfolio-1.50.2.dist-info}/WHEEL +0 -0
- {wbportfolio-1.50.0.dist-info → wbportfolio-1.50.2.dist-info}/licenses/LICENSE +0 -0
wbportfolio/models/asset.py
CHANGED
|
@@ -185,8 +185,8 @@ class AssetPositionIterator:
|
|
|
185
185
|
return dict(self._weights)
|
|
186
186
|
|
|
187
187
|
def __iter__(self):
|
|
188
|
-
# return an iterable excluding the position
|
|
189
|
-
yield from filter(lambda a: a.weighting, self.positions.values())
|
|
188
|
+
# return an iterable excluding the position with a null weight if the portfolio is manageable (otherwise, we assume the 0-weight position is valid)
|
|
189
|
+
yield from filter(lambda a: not a.portfolio.is_manageable or a.weighting, self.positions.values())
|
|
190
190
|
|
|
191
191
|
def __getitem__(self, item: tuple[date, Instrument]) -> float:
|
|
192
192
|
return self._weights[item[0]][item[1].id]
|
wbportfolio/models/portfolio.py
CHANGED
|
@@ -854,7 +854,9 @@ class Portfolio(DeleteToDisableMixin, WBModel):
|
|
|
854
854
|
not self.is_lookthrough and not is_target_portfolio_imported and self.is_active_at_date(from_date)
|
|
855
855
|
): # we cannot propagate a new portfolio for untracked, or look-through or already imported or inactive portfolios
|
|
856
856
|
positions, _ = self.drift_weights(from_date, to_date)
|
|
857
|
-
self.bulk_create_positions(
|
|
857
|
+
self.bulk_create_positions(
|
|
858
|
+
positions, delete_leftovers=True, compute_metrics=True, evaluate_rebalancer=False
|
|
859
|
+
)
|
|
858
860
|
|
|
859
861
|
def get_lookthrough_positions(
|
|
860
862
|
self,
|
|
@@ -260,9 +260,10 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
260
260
|
last_effective_date, self.portfolio.currency, exact_lookup=True
|
|
261
261
|
)
|
|
262
262
|
# we cannot do a bulk-create because Trade is a multi table inheritance
|
|
263
|
+
weighting = round(trade_dto.delta_weight, 6)
|
|
263
264
|
try:
|
|
264
265
|
trade = self.trades.get(underlying_instrument=instrument)
|
|
265
|
-
trade.weighting =
|
|
266
|
+
trade.weighting = weighting
|
|
266
267
|
trade.currency_fx_rate = currency_fx_rate
|
|
267
268
|
trade.status = Trade.Status.DRAFT
|
|
268
269
|
except Trade.DoesNotExist:
|
|
@@ -273,7 +274,7 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
273
274
|
transaction_date=self.trade_date,
|
|
274
275
|
trade_proposal=self,
|
|
275
276
|
portfolio=self.portfolio,
|
|
276
|
-
weighting=
|
|
277
|
+
weighting=weighting,
|
|
277
278
|
status=Trade.Status.DRAFT,
|
|
278
279
|
currency_fx_rate=currency_fx_rate,
|
|
279
280
|
)
|
|
@@ -524,7 +525,7 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
524
525
|
|
|
525
526
|
for trade in self.trades.all():
|
|
526
527
|
with suppress(ValueError):
|
|
527
|
-
asset = trade.
|
|
528
|
+
asset = trade.get_asset()
|
|
528
529
|
# we add the corresponding asset only if it is not the cache position (already included in estimated_cash_position)
|
|
529
530
|
if asset.underlying_quote != estimated_cash_position.underlying_quote:
|
|
530
531
|
assets.append(asset)
|
|
@@ -299,31 +299,9 @@ class Trade(ShareMixin, Transaction, OrderedModel, WBModel):
|
|
|
299
299
|
)
|
|
300
300
|
},
|
|
301
301
|
)
|
|
302
|
-
def to_asset(self) -> AssetPosition:
|
|
303
|
-
last_underlying_quote_price = self.last_underlying_quote_price
|
|
304
|
-
if not last_underlying_quote_price:
|
|
305
|
-
raise ValueError("No price found")
|
|
306
|
-
asset = AssetPosition(
|
|
307
|
-
underlying_quote=self.underlying_instrument,
|
|
308
|
-
portfolio_created=None,
|
|
309
|
-
portfolio=self.portfolio,
|
|
310
|
-
date=self.transaction_date,
|
|
311
|
-
initial_currency_fx_rate=self.currency_fx_rate,
|
|
312
|
-
weighting=self._target_weight,
|
|
313
|
-
initial_price=self.last_underlying_quote_price.net_value,
|
|
314
|
-
initial_shares=None,
|
|
315
|
-
underlying_quote_price=self.last_underlying_quote_price,
|
|
316
|
-
asset_valuation_date=self.transaction_date,
|
|
317
|
-
currency=self.currency,
|
|
318
|
-
is_estimated=False,
|
|
319
|
-
)
|
|
320
|
-
asset.set_weighting(self._target_weight)
|
|
321
|
-
asset.pre_save()
|
|
322
|
-
return asset
|
|
323
|
-
|
|
324
302
|
def execute(self, **kwargs):
|
|
325
303
|
with suppress(ValueError):
|
|
326
|
-
asset = self.
|
|
304
|
+
asset = self.get_asset()
|
|
327
305
|
AssetPosition.unannotated_objects.update_or_create(
|
|
328
306
|
underlying_quote=asset.underlying_quote,
|
|
329
307
|
portfolio_created=asset.portfolio_created,
|
|
@@ -570,6 +548,28 @@ class Trade(ShareMixin, Transaction, OrderedModel, WBModel):
|
|
|
570
548
|
|
|
571
549
|
"""
|
|
572
550
|
|
|
551
|
+
def get_asset(self) -> AssetPosition:
|
|
552
|
+
last_underlying_quote_price = self.last_underlying_quote_price
|
|
553
|
+
if not last_underlying_quote_price:
|
|
554
|
+
raise ValueError("No price found")
|
|
555
|
+
asset = AssetPosition(
|
|
556
|
+
underlying_quote=self.underlying_instrument,
|
|
557
|
+
portfolio_created=None,
|
|
558
|
+
portfolio=self.portfolio,
|
|
559
|
+
date=self.transaction_date,
|
|
560
|
+
initial_currency_fx_rate=self.currency_fx_rate,
|
|
561
|
+
weighting=self._target_weight,
|
|
562
|
+
initial_price=self.last_underlying_quote_price.net_value,
|
|
563
|
+
initial_shares=None,
|
|
564
|
+
underlying_quote_price=self.last_underlying_quote_price,
|
|
565
|
+
asset_valuation_date=self.transaction_date,
|
|
566
|
+
currency=self.currency,
|
|
567
|
+
is_estimated=False,
|
|
568
|
+
)
|
|
569
|
+
asset.set_weighting(self._target_weight)
|
|
570
|
+
asset.pre_save()
|
|
571
|
+
return asset
|
|
572
|
+
|
|
573
573
|
def delete(self, **kwargs):
|
|
574
574
|
pre_collection.send(sender=self.__class__, instance=self)
|
|
575
575
|
super().delete(**kwargs)
|
|
@@ -4,7 +4,9 @@ from decimal import Decimal
|
|
|
4
4
|
from rest_framework import serializers
|
|
5
5
|
from rest_framework.reverse import reverse
|
|
6
6
|
from wbcore import serializers as wb_serializers
|
|
7
|
+
from wbfdm.models import Instrument
|
|
7
8
|
from wbfdm.serializers import InvestableInstrumentRepresentationSerializer
|
|
9
|
+
from wbfdm.serializers.instruments.instruments import CompanyRepresentationSerializer, SecurityRepresentationSerializer
|
|
8
10
|
|
|
9
11
|
from wbportfolio.models import PortfolioRole, Trade, TradeProposal
|
|
10
12
|
from wbportfolio.models.transactions.claim import Claim
|
|
@@ -212,13 +214,62 @@ class TradeModelSerializer(TransactionModelSerializer):
|
|
|
212
214
|
)
|
|
213
215
|
|
|
214
216
|
|
|
217
|
+
class GetSecurityDefault:
|
|
218
|
+
requires_context = True
|
|
219
|
+
|
|
220
|
+
def __call__(self, serializer_instance):
|
|
221
|
+
try:
|
|
222
|
+
instance = serializer_instance.view.get_object()
|
|
223
|
+
return instance.underlying_instrument.parent or instance.underlying_instrument
|
|
224
|
+
except Exception:
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class GetCompanyDefault:
|
|
229
|
+
requires_context = True
|
|
230
|
+
|
|
231
|
+
def __call__(self, serializer_instance):
|
|
232
|
+
try:
|
|
233
|
+
instance = serializer_instance.view.get_object()
|
|
234
|
+
security = instance.underlying_instrument.parent or instance.underlying_instrument
|
|
235
|
+
return security.parent or security
|
|
236
|
+
except Exception:
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
|
|
215
240
|
class TradeTradeProposalModelSerializer(TradeModelSerializer):
|
|
216
241
|
underlying_instrument_isin = wb_serializers.CharField(read_only=True)
|
|
217
242
|
underlying_instrument_ticker = wb_serializers.CharField(read_only=True)
|
|
218
243
|
underlying_instrument_refinitiv_identifier_code = wb_serializers.CharField(read_only=True)
|
|
219
244
|
underlying_instrument_instrument_type = wb_serializers.CharField(read_only=True)
|
|
245
|
+
|
|
246
|
+
company = wb_serializers.PrimaryKeyRelatedField(
|
|
247
|
+
queryset=Instrument.objects.filter(level=0),
|
|
248
|
+
required=False,
|
|
249
|
+
read_only=lambda view: not view.new_mode,
|
|
250
|
+
default=GetCompanyDefault(),
|
|
251
|
+
)
|
|
252
|
+
_company = CompanyRepresentationSerializer(source="company", required=False)
|
|
253
|
+
|
|
254
|
+
security = wb_serializers.PrimaryKeyRelatedField(
|
|
255
|
+
queryset=Instrument.objects.filter(is_security=True),
|
|
256
|
+
required=False,
|
|
257
|
+
read_only=lambda view: not view.new_mode,
|
|
258
|
+
default=GetSecurityDefault(),
|
|
259
|
+
)
|
|
260
|
+
_security = SecurityRepresentationSerializer(
|
|
261
|
+
source="security",
|
|
262
|
+
optional_get_parameters={"company": "parent"},
|
|
263
|
+
depends_on=[{"field": "company", "options": {}}],
|
|
264
|
+
required=False,
|
|
265
|
+
)
|
|
266
|
+
underlying_instrument = wb_serializers.PrimaryKeyRelatedField(
|
|
267
|
+
queryset=Instrument.objects.all(), label="Quote", read_only=lambda view: not view.new_mode
|
|
268
|
+
)
|
|
220
269
|
_underlying_instrument = InvestableInstrumentRepresentationSerializer(
|
|
221
|
-
source="underlying_instrument",
|
|
270
|
+
source="underlying_instrument",
|
|
271
|
+
optional_get_parameters={"security": "parent"},
|
|
272
|
+
depends_on=[{"field": "security", "options": {}}],
|
|
222
273
|
)
|
|
223
274
|
|
|
224
275
|
status = wb_serializers.ChoiceField(default=Trade.Status.DRAFT, choices=Trade.Status.choices)
|
|
@@ -228,6 +279,8 @@ class TradeTradeProposalModelSerializer(TradeModelSerializer):
|
|
|
228
279
|
target_shares = wb_serializers.DecimalField(read_only=True, max_digits=16, decimal_places=6, default=0)
|
|
229
280
|
|
|
230
281
|
def validate(self, data):
|
|
282
|
+
data.pop("company", None)
|
|
283
|
+
data.pop("security", None)
|
|
231
284
|
if self.instance and "underlying_instrument" in data:
|
|
232
285
|
raise serializers.ValidationError(
|
|
233
286
|
{
|
|
@@ -235,13 +288,15 @@ class TradeTradeProposalModelSerializer(TradeModelSerializer):
|
|
|
235
288
|
}
|
|
236
289
|
)
|
|
237
290
|
effective_weight = self.instance._effective_weight if self.instance else Decimal(0.0)
|
|
291
|
+
weighting = data.get("weighting", self.instance.weighting if self.instance else Decimal(0.0))
|
|
238
292
|
if (target_weight := data.pop("target_weight", None)) is not None:
|
|
239
|
-
|
|
240
|
-
if
|
|
293
|
+
weighting = target_weight - effective_weight
|
|
294
|
+
if weighting >= 0:
|
|
241
295
|
data["transaction_subtype"] = "BUY"
|
|
242
296
|
else:
|
|
243
297
|
data["transaction_subtype"] = "SELL"
|
|
244
|
-
|
|
298
|
+
data["weighting"] = weighting
|
|
299
|
+
return super().validate(data)
|
|
245
300
|
|
|
246
301
|
class Meta:
|
|
247
302
|
model = Trade
|
|
@@ -262,6 +317,10 @@ class TradeTradeProposalModelSerializer(TradeModelSerializer):
|
|
|
262
317
|
"underlying_instrument_ticker",
|
|
263
318
|
"underlying_instrument_refinitiv_identifier_code",
|
|
264
319
|
"underlying_instrument_instrument_type",
|
|
320
|
+
"company",
|
|
321
|
+
"_company",
|
|
322
|
+
"security",
|
|
323
|
+
"_security",
|
|
265
324
|
"underlying_instrument",
|
|
266
325
|
"_underlying_instrument",
|
|
267
326
|
"transaction_subtype",
|
|
@@ -390,10 +390,13 @@ class TradeTradeProposalDisplayConfig(DisplayViewConfig):
|
|
|
390
390
|
)
|
|
391
391
|
|
|
392
392
|
def get_instance_display(self) -> Display:
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
393
|
+
trade_proposal = get_object_or_404(TradeProposal, pk=self.view.kwargs.get("trade_proposal_id", None))
|
|
394
|
+
|
|
395
|
+
fields = [
|
|
396
|
+
["company", "security", "underlying_instrument"],
|
|
397
|
+
["effective_weight", "target_weight", "weighting"],
|
|
398
|
+
]
|
|
399
|
+
if not trade_proposal.portfolio.only_weighting:
|
|
400
|
+
fields.append(["effective_shares", "target_shares", "shares"])
|
|
401
|
+
fields.append([repeat_field(3, "comment")])
|
|
402
|
+
return create_simple_display(fields)
|
|
@@ -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=wwAWW2vl21nJS7RVswjvB1PAZRjJ1VzCyLF4fb7R0Gw,45264
|
|
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=l7YsGpei9a4yhTBPZsWk0W9YNO3WoC1EoraQFzSa79Q,55799
|
|
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
|
|
@@ -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=
|
|
280
|
-
wbportfolio/models/transactions/trades.py,sha256=
|
|
279
|
+
wbportfolio/models/transactions/trade_proposals.py,sha256=ppSiF8y8mzK2BREETVODTJ8iBhHwuUso_5Meculmb_o,30576
|
|
280
|
+
wbportfolio/models/transactions/trades.py,sha256=4qA0cEff_hcV3kMnmJKXr0wSThpKhhm0HbzHbspBuxw,28919
|
|
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
|
|
@@ -343,7 +343,7 @@ wbportfolio/serializers/transactions/dividends.py,sha256=EULwKDumHBv4r2HsdEGZMZG
|
|
|
343
343
|
wbportfolio/serializers/transactions/expiry.py,sha256=K3XOSbCyef-xRzOjCr4Qg_YFJ_JuuiJ9u6tDS86l0hg,477
|
|
344
344
|
wbportfolio/serializers/transactions/fees.py,sha256=uPmSWuCeoV2bwVS6RmEz3a0VRBWJHIQr0WhklYc1UAI,1068
|
|
345
345
|
wbportfolio/serializers/transactions/trade_proposals.py,sha256=fiGpL6za5ERLtdbud5wrciCVXHX6-3SjeF8Zaa6Zhzg,3410
|
|
346
|
-
wbportfolio/serializers/transactions/trades.py,sha256=
|
|
346
|
+
wbportfolio/serializers/transactions/trades.py,sha256=OCm33pxy-d3YeEue3lIdEXt1utpx8ZLks4Am4XcP708,12932
|
|
347
347
|
wbportfolio/serializers/transactions/transactions.py,sha256=O137zeCndK-nxIWSRLEj7bXbBZDGa4d6qK6pJIIYK3g,4170
|
|
348
348
|
wbportfolio/static/wbportfolio/css/macro_review.css,sha256=FAVVO8nModxwPXcTKpcfzVxBGPZGJVK1Xn-0dkSfGyc,233
|
|
349
349
|
wbportfolio/static/wbportfolio/markdown/documentation/account_holding_reconciliation.md,sha256=MabxOvOne8s5gl6osDoow6-3ghaXLAYg9THWpvy6G5I,921
|
|
@@ -454,7 +454,7 @@ wbportfolio/viewsets/configs/display/reconciliations.py,sha256=YvMAuwmpX0HExvGsu
|
|
|
454
454
|
wbportfolio/viewsets/configs/display/registers.py,sha256=1np75exIk5rfct6UkVN_RnfJ9ozvIkcWJgFV4_4rJns,3182
|
|
455
455
|
wbportfolio/viewsets/configs/display/roles.py,sha256=SFUyCdxSlHZ3NsMrJmpVBSlg-XKGaEFteV89nyLMMAQ,1815
|
|
456
456
|
wbportfolio/viewsets/configs/display/trade_proposals.py,sha256=sRLSUjKlarBhnTwg7tX_Juldor3beswJlyvZfFPvNEk,4315
|
|
457
|
-
wbportfolio/viewsets/configs/display/trades.py,sha256=
|
|
457
|
+
wbportfolio/viewsets/configs/display/trades.py,sha256=tn7kpkzwd5AwzPznsIlRlQjjCoK1qVlTt4gtw6VHPpg,16841
|
|
458
458
|
wbportfolio/viewsets/configs/display/transactions.py,sha256=DOM3eV1DxBwX6Iiw3C2sJamWh6A_3ZSYC9447Jc3Wmo,2586
|
|
459
459
|
wbportfolio/viewsets/configs/endpoints/__init__.py,sha256=E13AYY3CIW4CZtmqwBVMPDYA5zNyKJeRZtiXKtad68Y,2871
|
|
460
460
|
wbportfolio/viewsets/configs/endpoints/adjustments.py,sha256=9CcnfNuFcxsZ8YvfUitSeyCvpLxY-jU-gIw3GG0mIp4,697
|
|
@@ -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.2.dist-info/METADATA,sha256=-bG1N-yBGfUwXgtyNOipqJ5KLH7rKb0hB1edpIAzGYQ,734
|
|
525
|
+
wbportfolio-1.50.2.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
|
|
526
|
+
wbportfolio-1.50.2.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
|
|
527
|
+
wbportfolio-1.50.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|