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.
- wbportfolio/migrations/0081_alter_trade_drift_factor.py +19 -0
- wbportfolio/models/portfolio.py +38 -28
- wbportfolio/models/transactions/trade_proposals.py +2 -0
- wbportfolio/models/transactions/trades.py +4 -2
- wbportfolio/pms/analytics/portfolio.py +8 -3
- wbportfolio/tests/models/transactions/test_trade_proposals.py +20 -0
- wbportfolio/tests/pms/test_analytics.py +21 -3
- wbportfolio/viewsets/assets.py +2 -2
- {wbportfolio-1.54.10.dist-info → wbportfolio-1.54.11.dist-info}/METADATA +1 -1
- {wbportfolio-1.54.10.dist-info → wbportfolio-1.54.11.dist-info}/RECORD +12 -11
- {wbportfolio-1.54.10.dist-info → wbportfolio-1.54.11.dist-info}/WHEEL +0 -0
- {wbportfolio-1.54.10.dist-info → wbportfolio-1.54.11.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
+
]
|
wbportfolio/models/portfolio.py
CHANGED
|
@@ -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
|
|
81
|
+
Return a tuple of the raw prices and the returns dataframe
|
|
76
82
|
"""
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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 =
|
|
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.
|
|
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=
|
|
234
|
-
|
|
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
|
-
|
|
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-
|
|
20
|
-
assert next_weights[1] == pytest.approx(w1 * (r1 + 1) / (w0 * (r0 + 1) + w1 * (r1 + 1) + w2 * (r2 + 1)), abs=10e-
|
|
21
|
-
assert next_weights[2] == pytest.approx(w2 * (r2 + 1) / (w0 * (r0 + 1) + w1 * (r1 + 1) + w2 * (r2 + 1)), abs=10e-
|
|
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():
|
wbportfolio/viewsets/assets.py
CHANGED
|
@@ -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=
|
|
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=
|
|
207
|
+
aggregates["weighting"] = {"Σ": format_number(weighting, decimal=8)}
|
|
208
208
|
return aggregates
|
|
209
209
|
|
|
210
210
|
def get_queryset(self):
|
|
@@ -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=
|
|
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=
|
|
288
|
-
wbportfolio/models/transactions/trades.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
526
|
-
wbportfolio-1.54.
|
|
527
|
-
wbportfolio-1.54.
|
|
528
|
-
wbportfolio-1.54.
|
|
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,,
|
|
File without changes
|
|
File without changes
|