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

@@ -24,6 +24,10 @@ def file_name_parse(file_name):
24
24
 
25
25
 
26
26
  def parse(import_source):
27
+ bank_exclusion_list = [
28
+ excluded_bank.strip().lower()
29
+ for excluded_bank in import_source.source.import_parameters.get("bank_exclusion_list", [])
30
+ ]
27
31
  # Load files into a CSV DictReader
28
32
  df = pd.read_csv(import_source.file, encoding="latin1", delimiter=",")
29
33
  df = df.replace([np.inf, -np.inf, np.nan], None)
@@ -42,18 +46,22 @@ def parse(import_source):
42
46
  # Check whether it is a buy or a sell and convert the value correspondely
43
47
  shares = shares if nominal_data["Side"] == "S" else shares * -1
44
48
  portfolio = product.primary_portfolio
45
- data.append(
46
- {
47
- "underlying_instrument": {"id": product.id, "instrument_type": "product"},
48
- "transaction_date": nominal_data["Trade Date"].strftime("%Y-%m-%d"),
49
- "shares": shares,
50
- "portfolio": portfolio.id,
51
- # 'currency': product.currency.key,
52
- "transaction_subtype": Trade.Type.REDEMPTION if shares < 0 else Trade.Type.SUBSCRIPTION,
53
- "bank": nominal_data["CounterParty Name"],
54
- "price": convert_string_to_number(nominal_data["Price"]),
55
- }
56
- )
49
+ bank = nominal_data["CounterParty Name"]
50
+ if (
51
+ len(bank_exclusion_list) == 0 or bank.strip().lower() not in bank_exclusion_list
52
+ ): # we do basic string comparison to exclude appropriate banks. We might want to include regex if bank data is inconsistent
53
+ data.append(
54
+ {
55
+ "underlying_instrument": {"id": product.id, "instrument_type": "product"},
56
+ "transaction_date": nominal_data["Trade Date"].strftime("%Y-%m-%d"),
57
+ "shares": shares,
58
+ "portfolio": portfolio.id,
59
+ # 'currency': product.currency.key,
60
+ "transaction_subtype": Trade.Type.REDEMPTION if shares < 0 else Trade.Type.SUBSCRIPTION,
61
+ "bank": bank,
62
+ "price": convert_string_to_number(nominal_data["Price"]),
63
+ }
64
+ )
57
65
  import_data = {"data": data}
58
66
  if "isin" in parts:
59
67
  product = Product.objects.get(isin=parts["isin"])
@@ -802,7 +802,11 @@ class Portfolio(DeleteToDisableMixin, WBModel):
802
802
  adjusted_currency_fx_rate,
803
803
  adjusted_is_estimated,
804
804
  portfolio_created=None,
805
+ path=None,
805
806
  ):
807
+ if not path:
808
+ path = []
809
+ path.append(parent_portfolio)
806
810
  for position in parent_portfolio.assets.filter(date=sync_date):
807
811
  position.id = None
808
812
  position.weighting = adjusted_weighting * position.weighting
@@ -811,7 +815,7 @@ class Portfolio(DeleteToDisableMixin, WBModel):
811
815
  position.weighting == 1.0
812
816
  )
813
817
  position.portfolio_created = portfolio_created
814
- position.parent_portfolio = parent_portfolio
818
+ setattr(position, "path", path)
815
819
  position.initial_shares = None
816
820
  if portfolio_total_asset_value:
817
821
  position.initial_shares = (position.weighting * portfolio_total_asset_value) / (
@@ -826,8 +830,9 @@ class Portfolio(DeleteToDisableMixin, WBModel):
826
830
  position.currency_fx_rate,
827
831
  position.is_estimated,
828
832
  portfolio_created=child_portfolio,
833
+ path=path.copy(),
829
834
  )
830
- elif position.weighting: # we do not yield postion with weight 0 because of issue with certain multi-thematic portfolios which contain duplicates
835
+ elif position.weighting: # we do not yield position with weight 0 because of issue with certain multi-thematic portfolios which contain duplicates
831
836
  yield position
832
837
 
833
838
  yield from _crawl_portfolio(self, Decimal(1.0), Decimal(1.0), False)
@@ -8,6 +8,7 @@ from django.dispatch import receiver
8
8
  from django.utils.functional import cached_property
9
9
  from django.utils.module_loading import autodiscover_modules
10
10
  from django.utils.translation import gettext_lazy as _
11
+ from pandas._libs.tslibs.offsets import BDay
11
12
  from wbcore.utils.importlib import import_from_dotted_path
12
13
  from wbcore.utils.models import ComplexToStringMixin
13
14
  from wbcore.utils.rrules import convert_rrulestr_to_dict, humanize_rrule
@@ -41,10 +42,10 @@ class RebalancingModel(models.Model):
41
42
  def get_target_portfolio(
42
43
  self, portfolio: Portfolio, trade_date: date, last_effective_date: date, **kwargs
43
44
  ) -> PortfolioDTO:
44
- model = self.model_class(portfolio, trade_date, last_effective_date)
45
+ model = self.model_class(portfolio, trade_date, last_effective_date, **kwargs)
45
46
  if not model.is_valid():
46
47
  raise ValidationError("Rebalacing cannot applied for these parameters")
47
- return model.get_target_portfolio(**kwargs)
48
+ return model.get_target_portfolio()
48
49
 
49
50
  @classmethod
50
51
  def get_representation_endpoint(cls) -> str:
@@ -86,9 +87,22 @@ class Rebalancer(ComplexToStringMixin, models.Model):
86
87
  self.activation_date = date.today()
87
88
  super().save(*args, **kwargs)
88
89
 
90
+ def _get_next_valid_date(self, valid_date: date) -> date:
91
+ pivot_date = valid_date
92
+ while TradeProposal.objects.filter(
93
+ portfolio=self.portfolio, status=TradeProposal.Status.FAILED, trade_date=pivot_date
94
+ ).exists():
95
+ pivot_date += BDay(1)
96
+ return pivot_date
97
+
89
98
  def is_valid(self, trade_date: date) -> bool:
90
- valid_dates = [_d.date() for _d in self.get_rrule(trade_date)]
91
- return trade_date in valid_dates
99
+ for valid_datetime in self.get_rrule(trade_date):
100
+ valid_date = self._get_next_valid_date(valid_datetime.date())
101
+ if valid_date == trade_date:
102
+ return True
103
+ if valid_date > trade_date:
104
+ break
105
+ return False
92
106
 
93
107
  def evaluate_rebalancing(self, trade_date: date):
94
108
  trade_proposal, _ = TradeProposal.objects.get_or_create(
@@ -101,17 +115,18 @@ class Rebalancer(ComplexToStringMixin, models.Model):
101
115
  )
102
116
  if trade_proposal.rebalancing_model == self.rebalancing_model:
103
117
  trade_proposal.status = TradeProposal.Status.DRAFT
104
- target_portfolio = self.rebalancing_model.get_target_portfolio(
105
- self.portfolio, trade_date, trade_proposal.last_effective_date, **self.parameters
106
- )
107
118
  try:
119
+ target_portfolio = self.rebalancing_model.get_target_portfolio(
120
+ self.portfolio, trade_date, trade_proposal.last_effective_date, **self.parameters
121
+ )
108
122
  trade_proposal.reset_trades(target_portfolio)
109
123
  trade_proposal.submit()
124
+ if self.approve_trade_proposal_automatically and self.portfolio.can_be_rebalanced:
125
+ trade_proposal.approve()
110
126
  except ValidationError:
111
- pass # Do something
127
+ # If we encountered a validation error, we set the trade proposal as failed
128
+ trade_proposal.status = TradeProposal.Status.FAILED
112
129
 
113
- if self.approve_trade_proposal_automatically and self.portfolio.can_be_rebalanced:
114
- trade_proposal.approve()
115
130
  trade_proposal.save()
116
131
 
117
132
  return trade_proposal
@@ -38,6 +38,7 @@ class TradeProposal(RiskCheckMixin, WBModel):
38
38
  SUBMIT = "SUBMIT", "Submit"
39
39
  APPROVED = "APPROVED", "Approved"
40
40
  DENIED = "DENIED", "Denied"
41
+ FAILED = "FAILED", "Failed"
41
42
 
42
43
  comment = models.TextField(default="", verbose_name="Trade Comment", blank=True)
43
44
  status = FSMField(default=Status.DRAFT, choices=Status.choices, verbose_name="Status")
@@ -4,7 +4,7 @@ from wbportfolio.pms.typing import Portfolio as PortfolioDTO
4
4
 
5
5
 
6
6
  class AbstractRebalancingModel:
7
- def __init__(self, portfolio, trade_date: date, last_effective_date: date):
7
+ def __init__(self, portfolio, trade_date: date, last_effective_date: date, **kwargs):
8
8
  self.portfolio = portfolio
9
9
  self.trade_date = trade_date
10
10
  self.last_effective_date = last_effective_date
@@ -12,5 +12,5 @@ class AbstractRebalancingModel:
12
12
  def is_valid(self) -> bool:
13
13
  return True
14
14
 
15
- def get_target_portfolio(self, **kwargs) -> PortfolioDTO:
15
+ def get_target_portfolio(self) -> PortfolioDTO:
16
16
  raise NotImplementedError()
@@ -1,3 +1,4 @@
1
1
  from .composite import CompositeRebalancing
2
2
  from .model_portfolio import ModelPortfolioRebalancing
3
3
  from .equally_weighted import EquallyWeightedRebalancing
4
+ from .market_capitalization_weighted import MarketCapitalizationRebalancing
@@ -22,7 +22,7 @@ class CompositeRebalancing(AbstractRebalancingModel):
22
22
  def is_valid(self) -> bool:
23
23
  return len(self.base_assets.keys()) > 0
24
24
 
25
- def get_target_portfolio(self, **kwargs) -> Portfolio:
25
+ def get_target_portfolio(self) -> Portfolio:
26
26
  positions = []
27
27
  for underlying_instrument, weighting in self.base_assets.items():
28
28
  positions.append(
@@ -10,7 +10,7 @@ class EquallyWeightedRebalancing(AbstractRebalancingModel):
10
10
  def is_valid(self) -> bool:
11
11
  return self.portfolio.assets.filter(date=self.last_effective_date).exists()
12
12
 
13
- def get_target_portfolio(self, **kwargs) -> Portfolio:
13
+ def get_target_portfolio(self) -> Portfolio:
14
14
  positions = []
15
15
  assets = self.portfolio.assets.filter(date=self.last_effective_date)
16
16
  nb_assets = assets.count()
@@ -0,0 +1,85 @@
1
+ from decimal import Decimal
2
+
3
+ import pandas as pd
4
+ from django.db.models import Q, QuerySet
5
+ from wbfdm.enums import MarketData
6
+ from wbfdm.models import Classification, Instrument, InstrumentClassificationThroughModel
7
+
8
+ from wbportfolio.pms.typing import Portfolio, Position
9
+ from wbportfolio.rebalancing.base import AbstractRebalancingModel
10
+ from wbportfolio.rebalancing.decorators import register
11
+
12
+
13
+ @register("Market Capitalization Rebalancing")
14
+ class MarketCapitalizationRebalancing(AbstractRebalancingModel):
15
+ TARGET_CURRENCY: str = "USD"
16
+
17
+ def __init__(self, *args, **kwargs):
18
+ super().__init__(*args, **kwargs)
19
+ instruments = self._get_instruments(**kwargs)
20
+ self.market_cap_df = pd.Series()
21
+ df = pd.DataFrame(
22
+ instruments.dl.market_data(
23
+ values=[MarketData.MARKET_CAPITALIZATION],
24
+ from_date=self.last_effective_date,
25
+ to_date=self.trade_date,
26
+ target_currency=self.TARGET_CURRENCY,
27
+ )
28
+ )
29
+ try:
30
+ df = df[["valuation_date", "market_capitalization", "instrument_id"]].pivot_table(
31
+ index="valuation_date", columns="instrument_id", values="market_capitalization"
32
+ )
33
+ df = df.ffill()
34
+ self.market_cap_df = df.iloc[-1, :].transpose()
35
+ except (IndexError, KeyError):
36
+ self.market_cap_df = pd.Series()
37
+
38
+ def _get_instruments(
39
+ self, classification_ids: list[int] | None = None, instrument_ids: list[int] | None = None, **kwargs
40
+ ) -> QuerySet[Instrument]:
41
+ """
42
+ Use the provided kwargs to return a list of instruments as universe.
43
+ - If classifications are given, we returns all the instrument linked to these classifications
44
+ - Or directly from a static list of instrument ids
45
+ - fallback to the last effective portfolio underlying instruments list
46
+ """
47
+ if classification_ids:
48
+ classifications = set()
49
+ for classification in Classification.objects.filter(id__in=classification_ids):
50
+ for children in classification.get_descendants(include_self=True):
51
+ classifications.add(children)
52
+ instrument_ids = list(
53
+ InstrumentClassificationThroughModel.objects.filter(classification__in=classifications).values_list(
54
+ "id", flat=True
55
+ )
56
+ )
57
+ elif not instrument_ids:
58
+ instrument_ids = list(
59
+ self.portfolio.assets.filter(date=self.last_effective_date).values_list(
60
+ "underlying_instrument", flat=True
61
+ )
62
+ )
63
+ return Instrument.objects.filter(id__in=instrument_ids).filter(
64
+ Q(delisted_date__isnull=True) | Q(delisted_date__gt=self.trade_date)
65
+ )
66
+
67
+ def is_valid(self) -> bool:
68
+ return (
69
+ not self.market_cap_df.empty and not self.market_cap_df.isnull().any()
70
+ ) # if we are missing any market cap for not-delisted instrument, we consider the rebalancing not valid
71
+
72
+ def get_target_portfolio(self) -> Portfolio:
73
+ positions = []
74
+ market_cap_df = self.market_cap_df
75
+ total_market_cap = market_cap_df.sum()
76
+
77
+ for underlying_instrument, market_cap in market_cap_df.to_dict().items():
78
+ positions.append(
79
+ Position(
80
+ underlying_instrument=underlying_instrument,
81
+ weighting=Decimal(market_cap / total_market_cap),
82
+ date=self.trade_date,
83
+ )
84
+ )
85
+ return Portfolio(positions=tuple(positions))
@@ -24,7 +24,7 @@ class ModelPortfolioRebalancing(AbstractRebalancingModel):
24
24
  else False
25
25
  )
26
26
 
27
- def get_target_portfolio(self, **kwargs) -> Portfolio:
27
+ def get_target_portfolio(self) -> Portfolio:
28
28
  positions = []
29
29
  assets = self.model_portfolio.get_positions(self.last_effective_date)
30
30
 
@@ -1,7 +1,9 @@
1
1
  from decimal import Decimal
2
2
 
3
+ import numpy as np
3
4
  import pytest
4
5
  from pandas._libs.tslibs.offsets import BDay
6
+ from wbfdm.models import InstrumentPrice
5
7
 
6
8
  from wbportfolio.factories import PortfolioFactory, TradeFactory, TradeProposalFactory
7
9
  from wbportfolio.models import PortfolioPortfolioThroughModel, Trade, TradeProposal
@@ -125,3 +127,35 @@ class TestCompositeRebalancing:
125
127
  target_positions = target_portfolio.positions_map
126
128
  assert target_positions[t1.underlying_instrument.id].weighting == Decimal("0.800000")
127
129
  assert target_positions[t2.underlying_instrument.id].weighting == Decimal("0.200000")
130
+
131
+
132
+ @pytest.mark.django_db
133
+ class TestMarketCapitalizationRebalancing:
134
+ @pytest.fixture()
135
+ def model(self, portfolio, weekday, instrument_factory, instrument_price_factory):
136
+ from wbportfolio.rebalancing.models import MarketCapitalizationRebalancing
137
+
138
+ last_effective_date = (weekday - BDay(1)).date()
139
+
140
+ i1 = instrument_factory()
141
+ i2 = instrument_factory()
142
+ instrument_price_factory.create(instrument=i1, date=last_effective_date)
143
+ instrument_price_factory.create(instrument=i1, date=weekday)
144
+ instrument_price_factory.create(instrument=i2, date=last_effective_date) # weekday is ffill
145
+ return MarketCapitalizationRebalancing(portfolio, weekday, last_effective_date, instrument_ids=[i1.id, i2.id])
146
+
147
+ def test_is_valid(self, portfolio, weekday, model, instrument_factory, instrument_price_factory):
148
+ assert model.is_valid()
149
+ model.market_cap_df.iloc[0] = np.nan
150
+ assert not model.is_valid()
151
+
152
+ def test_get_target_portfolio(self, portfolio, weekday, model, asset_position_factory):
153
+ i1 = model.market_cap_df.index[0]
154
+ i2 = model.market_cap_df.index[1]
155
+ mkt12 = InstrumentPrice.objects.get(instrument_id=i1, date=model.trade_date).market_capitalization
156
+ mkt21 = InstrumentPrice.objects.get(instrument_id=i2, date=model.last_effective_date).market_capitalization
157
+
158
+ target_portfolio = model.get_target_portfolio()
159
+ target_positions = target_portfolio.positions_map
160
+ assert target_positions[i1].weighting == mkt12 / (mkt12 + mkt21)
161
+ assert target_positions[i2].weighting == mkt21 / (mkt12 + mkt21)
@@ -196,7 +196,7 @@ class TopDownPortfolioCompositionPandasAPIView(UserPortfolioRequestPermissionMix
196
196
  pandas_fields = pf.PandasFields(
197
197
  fields=[
198
198
  pf.PKField(key="id", label="ID"),
199
- pf.CharField(key="_group_key", label="Group Key"),
199
+ pf.IntegerField(key="_group_key", label="Group Key"),
200
200
  pf.IntegerField(key="parent_row_id", label="Parent Row"),
201
201
  pf.CharField(key="instrument", label="Instrument"),
202
202
  pf.FloatField(key="effective_weights", label="Effective Weights", precision=2, percent=True),
@@ -229,7 +229,7 @@ class TopDownPortfolioCompositionPandasAPIView(UserPortfolioRequestPermissionMix
229
229
 
230
230
  def _get_parent_instrument_id(portfolio):
231
231
  with suppress(AttributeError):
232
- return portfolio.instruments.first().id
232
+ return str(portfolio.instruments.first().id)
233
233
 
234
234
  if (
235
235
  self.has_portfolio_access
@@ -237,85 +237,87 @@ class TopDownPortfolioCompositionPandasAPIView(UserPortfolioRequestPermissionMix
237
237
  and self.last_rebalancing_date
238
238
  and self.last_effective_date
239
239
  ):
240
- tree_positions_effective_date_df = (
241
- pd.DataFrame(
242
- map(
243
- lambda x: {
244
- "instrument": x.underlying_instrument.id,
245
- "parent_instrument": _get_parent_instrument_id(
246
- getattr(x, "parent_portfolio", "portfolio")
247
- ),
248
- "effective_weights": x.weighting,
249
- },
250
- self.composition_portfolio.get_positions(
251
- self.last_effective_date, with_intermediary_position=True
240
+ df = pd.DataFrame(
241
+ map(
242
+ lambda x: {
243
+ "instrument": str(x.underlying_instrument.id),
244
+ "path": "-".join(
245
+ map(
246
+ lambda o: _get_parent_instrument_id(o),
247
+ getattr(x, "path", [self.composition_portfolio]),
248
+ )
252
249
  ),
250
+ "effective_weights": x.weighting,
251
+ },
252
+ self.composition_portfolio.get_positions(
253
+ self.last_effective_date, with_intermediary_position=True
253
254
  ),
254
- columns=["instrument", "parent_instrument", "effective_weights"],
255
- )
256
- .groupby(["parent_instrument", "instrument"])
257
- .sum()
258
- )
259
- tree_positions_rebalancing_date_df = (
260
- pd.DataFrame(
255
+ ),
256
+ columns=["instrument", "path", "effective_weights"],
257
+ ).set_index(["path", "instrument"])
258
+
259
+ if last_rebalancing_date := self.last_rebalancing_date:
260
+ tree_positions_rebalancing_date_df = pd.DataFrame(
261
261
  map(
262
262
  lambda x: {
263
- "instrument": x.underlying_instrument.id,
264
- "parent_instrument": _get_parent_instrument_id(
265
- getattr(x, "parent_portfolio", "portfolio")
263
+ "instrument": str(x.underlying_instrument.id),
264
+ "path": "-".join(
265
+ map(
266
+ lambda o: _get_parent_instrument_id(o),
267
+ getattr(x, "path", [self.composition_portfolio]),
268
+ )
266
269
  ),
267
270
  "rebalancing_weights": x.weighting,
268
271
  },
269
272
  self.composition_portfolio.get_positions(
270
- self.last_rebalancing_date, with_intermediary_position=True
273
+ last_rebalancing_date, with_intermediary_position=True
271
274
  ),
272
275
  ),
273
- columns=["instrument", "parent_instrument", "rebalancing_weights"],
274
- )
275
- .groupby(["parent_instrument", "instrument"])
276
- .sum()
277
- )
276
+ columns=["instrument", "path", "rebalancing_weights"],
277
+ ).set_index(["path", "instrument"])
278
278
 
279
+ df = pd.concat([df, tree_positions_rebalancing_date_df], axis=1)
280
+ df = df.reset_index()
279
281
  df = pd.concat(
280
- [tree_positions_effective_date_df, tree_positions_rebalancing_date_df], axis=1
281
- ).reset_index()
282
-
283
- if not df["parent_instrument"].isnull().any():
284
- df = pd.concat(
285
- [
286
- pd.DataFrame(
287
- [
288
- {
289
- "instrument": self.portfolio.instruments.first().id,
290
- "parent_instrument": None,
291
- "effective_weights": 1.0,
292
- "rebalancing_weights": 1.0,
293
- }
294
- ]
295
- ),
296
- df,
297
- ],
298
- ignore_index=True,
299
- )
282
+ [
283
+ pd.DataFrame(
284
+ [
285
+ {
286
+ "instrument": str(self.portfolio.instruments.first().id),
287
+ "path": "",
288
+ "effective_weights": 1.0,
289
+ "rebalancing_weights": 1.0,
290
+ }
291
+ ]
292
+ ),
293
+ df,
294
+ ],
295
+ ignore_index=True,
296
+ )
300
297
 
301
298
  df = df.reset_index(names="id")
302
299
 
303
- def _get_parent_id(x):
304
- dff = df.loc[df["instrument"] == x, "id"]
300
+ def _get_group_key(x):
301
+ return str(int(x)) if not df.loc[df["parent_row_id"] == x, :].empty else None
302
+
303
+ def _get_parent_row_id(path):
304
+ s = path.split("-")
305
+ instrument = s[-1]
306
+ parent_path = "-".join(s[:-1]) if len(s) > 1 else ""
307
+ dff = df.loc[(df["instrument"] == instrument) & (df["path"] == parent_path), "id"]
305
308
  if not dff.empty:
306
309
  return dff.iloc[0]
307
310
  return None
308
311
 
309
- def _get_group_key(x):
310
- return str(int(x)) if not df.loc[df["parent_row_id"] == x, :].empty else None
311
-
312
- df["parent_row_id"] = df["parent_instrument"].apply(lambda x: _get_parent_id(x))
312
+ df["parent_row_id"] = df["path"].apply(lambda x: _get_parent_row_id(x))
313
313
  df["_group_key"] = df["id"].apply(lambda x: _get_group_key(x))
314
- df = df.drop(columns=["parent_instrument"])
314
+ df = df.drop(columns=["path"])
315
315
  return df
316
316
 
317
317
  def manipulate_dataframe(self, df):
318
- df["instrument"] = df["instrument"].map(
319
- dict(Instrument.objects.filter(id__in=df["instrument"]).values_list("id", "name_repr"))
318
+ df["instrument"] = (
319
+ df["instrument"]
320
+ .astype(int)
321
+ .map(dict(Instrument.objects.filter(id__in=df["instrument"]).values_list("id", "name_repr")))
320
322
  )
321
323
  return df
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbportfolio
3
- Version: 1.45.1
3
+ Version: 1.46.1
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  License-File: LICENSE
6
6
  Requires-Dist: cryptography==3.4.*
@@ -119,7 +119,7 @@ wbportfolio/import_export/handlers/trade.py,sha256=LydAyFtqHPNfCF6j1yY9Md0xWf6NM
119
119
  wbportfolio/import_export/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
120
120
  wbportfolio/import_export/parsers/default_mapping.py,sha256=KrO-X5CvQCeQoBYzFDxavoQGriyUSeI2QDx5ar_zo7A,1405
121
121
  wbportfolio/import_export/parsers/jpmorgan/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
122
- wbportfolio/import_export/parsers/jpmorgan/customer_trade.py,sha256=UXi71aP0BGtqJpkiemWlgt0Ez3Aj5JKgjDaRPAz9vj0,2380
122
+ wbportfolio/import_export/parsers/jpmorgan/customer_trade.py,sha256=AMB7vXkhRctGBzgdCdWz0lrWVOIbmuqTjyotEJvTKYE,2859
123
123
  wbportfolio/import_export/parsers/jpmorgan/fees.py,sha256=l1eoU_QJZdE3rL2ql_s5qGUDfDqnOZKN5mrSV4qxEqs,2179
124
124
  wbportfolio/import_export/parsers/jpmorgan/strategy.py,sha256=OAIKv9NyKj8WiWLXsVIj7ujjfxBl5OLCNNXlx0oJIiE,4463
125
125
  wbportfolio/import_export/parsers/jpmorgan/valuation.py,sha256=-PGYjAjvHyW6muwPeA3HCFWtD-U7elzXThgrcOfQe58,1306
@@ -245,7 +245,7 @@ wbportfolio/models/adjustments.py,sha256=osXWkJZOiansPWYPyHtl7Z121zDWi7u1YMtrBQt
245
245
  wbportfolio/models/asset.py,sha256=V-fSAF1bsfj_Td3VfdKyhCgcwB_xPS6vTI90Dh5iDx8,37502
246
246
  wbportfolio/models/custodians.py,sha256=owTiS2Vm5CRKzh9M_P9GOVg-s-ndQ9UvRmw3yZP7cw0,3815
247
247
  wbportfolio/models/indexes.py,sha256=iLYF2gzNzX4GLj_Nh3fybUcAQ1TslnT0wgQ6mN164QI,728
248
- wbportfolio/models/portfolio.py,sha256=ao9S7qV_EmVCnWjSQ8hU3zIeMeyDdplHexEmpNbp98k,55313
248
+ wbportfolio/models/portfolio.py,sha256=-B--vg_vGsZjHtUsW1hrkmg8LoGeyBO_ilcE6BEE3e0,55459
249
249
  wbportfolio/models/portfolio_cash_flow.py,sha256=2blPiXSw7dbhUVd-7LcxDBb4v0SheNOdvRK3MFYiChA,7273
250
250
  wbportfolio/models/portfolio_cash_targets.py,sha256=WmgG-etPisZsh2yaFQpz7EkpvAudKBEzqPsO715w52U,1498
251
251
  wbportfolio/models/portfolio_relationship.py,sha256=mMb18UMRWg9kx_9uIPkMktwORuXXLjKdgRPQQvB6fVE,5486
@@ -271,8 +271,8 @@ wbportfolio/models/transactions/claim.py,sha256=agdpGqxpO0FSzYDWV-Gv1tQY46k0LN9C
271
271
  wbportfolio/models/transactions/dividends.py,sha256=naL5xeDQfUBf5KyGt7y-tTcHL22nzZumT8DV6AaG8Bg,1064
272
272
  wbportfolio/models/transactions/expiry.py,sha256=vnNHdcC1hf2HP4rAbmoGgOfagBYKNFytqOwzOI0MlVI,144
273
273
  wbportfolio/models/transactions/fees.py,sha256=ffvqo8I4A0l5rLi00jJ6sGot0jmnkoxaNsbDzdPLwCg,5712
274
- wbportfolio/models/transactions/rebalancing.py,sha256=t-bQcFTfPTD-ciCNTKEtKW0r29RZ63PmeVJmBAKyj3Y,5719
275
- wbportfolio/models/transactions/trade_proposals.py,sha256=xekzZcY1i9jsguVTTMTeLRwEMJogNWxV4OvJTF3z7uI,20275
274
+ wbportfolio/models/transactions/rebalancing.py,sha256=9EdElkaqPoH14FBSK9QOCLFzVpr4YFa3YrgCJV9XvaI,6396
275
+ wbportfolio/models/transactions/trade_proposals.py,sha256=ZpMWOKXfWo85AW2cqp0X5aUGabGECB4W1bK4RaD5-QM,20311
276
276
  wbportfolio/models/transactions/trades.py,sha256=3HthfL0wgzPmwwXRVzA5yvm-UPiQco7HhN30ztsOjvI,27529
277
277
  wbportfolio/models/transactions/transactions.py,sha256=4THsE4xqdigZAwWKYfTNRLPJlkmAmsgE70Ribp9Lnrk,7127
278
278
  wbportfolio/pms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -283,12 +283,13 @@ wbportfolio/pms/statistics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
283
283
  wbportfolio/pms/trading/__init__.py,sha256=R_yLKc54sCak8A1cW0O1Aszrcv5KV8mC_3h17Hr20e4,36
284
284
  wbportfolio/pms/trading/handler.py,sha256=Xpgo719S0jE1wUTTyGFpYccPEIg9GXghWEAdYawJbrk,7165
285
285
  wbportfolio/rebalancing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
286
- wbportfolio/rebalancing/base.py,sha256=HttfNmOjclSw9Kfm283LmyHajjBJi4CTjv25EP9_JeQ,481
286
+ wbportfolio/rebalancing/base.py,sha256=Z5IAG8zu6_RLkQE1AgIoROBUDXWza0myzKhKx_4ONwA,481
287
287
  wbportfolio/rebalancing/decorators.py,sha256=JhQ2vkcIuGBTGvNmpkQerdw2-vLq-RAb0KAPjOrETkw,515
288
- wbportfolio/rebalancing/models/__init__.py,sha256=AQHlyjyIdlBRSim6eCdWDf4-M-supY9VP-xkuyBWYnA,156
289
- wbportfolio/rebalancing/models/composite.py,sha256=H6AgpAAegW2H0pF2xbKVuAubUwbWsQU_L_4lLJBowiI,1167
290
- wbportfolio/rebalancing/models/equally_weighted.py,sha256=L_XFiVnik3HQjwA0F05FjASlQEXv_X5L8g_Rq0TvVaM,873
291
- wbportfolio/rebalancing/models/model_portfolio.py,sha256=bGkThc3t80XIvAEp9prstZHsdlt9tGUsLEenzH9XmKc,1243
288
+ wbportfolio/rebalancing/models/__init__.py,sha256=AQjG7Tu5vlmhqncVoYOjpBKU2UIvgo9FuP2_jD2w-UI,232
289
+ wbportfolio/rebalancing/models/composite.py,sha256=XAjJqLRNsV-MuBKrat3THEfAWs6PXQNSO0g8k8MtBXo,1157
290
+ wbportfolio/rebalancing/models/equally_weighted.py,sha256=U29MOHJMQMIg7Y7W_8t5K3nXjaznzt4ArIxQSiv0Xok,863
291
+ wbportfolio/rebalancing/models/market_capitalization_weighted.py,sha256=AYVoLg_bf7s7DSXVjBuIOltFpkySVmv2lrDD-pKBH9I,3643
292
+ wbportfolio/rebalancing/models/model_portfolio.py,sha256=XQdvs03-0M9YUnL4DidwZC4E6k-ANCNcZ--T_aaOXTQ,1233
292
293
  wbportfolio/reports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
293
294
  wbportfolio/reports/monthly_position_report.py,sha256=e7BzjDd6eseUOwLwQJXKvWErQ58YnCsznHU2VtR6izM,2981
294
295
  wbportfolio/risk_management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -383,7 +384,7 @@ wbportfolio/tests/models/transactions/test_trades.py,sha256=z0CCZjB648ECDSEdwmzq
383
384
  wbportfolio/tests/pms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
384
385
  wbportfolio/tests/pms/test_analytics.py,sha256=FrvVsV_uUiTgmRUfsaB-_sGzY30CqknbOY2DvmwR_70,1141
385
386
  wbportfolio/tests/rebalancing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
386
- wbportfolio/tests/rebalancing/test_models.py,sha256=I9yHsDge2MgULcSpdp2YtlqvUSsC1tLAgKUCUH2cVkc,5916
387
+ wbportfolio/tests/rebalancing/test_models.py,sha256=Zph5NFWeIJVFxv06F6CBrJC0xPr3TB5ns0EdgzKdh8U,7610
387
388
  wbportfolio/tests/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
388
389
  wbportfolio/tests/serializers/test_claims.py,sha256=vQrg73xQXRFEgvx3KI9ivFre_wpBFzdO0p0J13PkvdY,582
389
390
  wbportfolio/tests/viewsets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -403,7 +404,7 @@ wbportfolio/viewsets/portfolio_cash_flow.py,sha256=jkBfdZRQ3KsxGMJpltRjmdrZ2qEFJ
403
404
  wbportfolio/viewsets/portfolio_cash_targets.py,sha256=CvHlrDE8qnnnfRpTYnFu-Uu15MDbF5d5gTmEKth2S24,322
404
405
  wbportfolio/viewsets/portfolio_relationship.py,sha256=RGyvxd8NfFEs8YdqEvVD3VbrISvAO5UtCTlocSIuWQw,2109
405
406
  wbportfolio/viewsets/portfolio_swing_pricing.py,sha256=-57l3WLQZRslIV67OT0ucHE5JXTtTtLvd3t7MppdVn8,357
406
- wbportfolio/viewsets/portfolios.py,sha256=i4swoycpL3utZdTCgVAYSI2KE6bA2JyTetRh6taYUAc,13042
407
+ wbportfolio/viewsets/portfolios.py,sha256=ZQjlBUZYAM576V0kE8nfRdyCkOPhvt54yFz9drUZilE,13128
407
408
  wbportfolio/viewsets/positions.py,sha256=MDf_0x9La2qE6qjaIqBtfV5VC0RfJ1chZIim45Emk10,13198
408
409
  wbportfolio/viewsets/product_groups.py,sha256=YvmuXPPy98K1J_rz6YPsx9gNK-tCS2P-wc1uRYgfyo0,2399
409
410
  wbportfolio/viewsets/product_performance.py,sha256=dRfRgifjGS1RgZSu9uJRM0SmB7eLnNUkPuqARMO4gyo,28371
@@ -515,7 +516,7 @@ wbportfolio/viewsets/transactions/rebalancing.py,sha256=6rIrdK0rtKL1afJ-tYfAGdQV
515
516
  wbportfolio/viewsets/transactions/trade_proposals.py,sha256=fYTvvRk7k5xsBzbIgJvU4I4OrllF0VkhlrekD4GVgDk,4296
516
517
  wbportfolio/viewsets/transactions/trades.py,sha256=6iTIM5g7TUlRtiCjIG4EdYvyfaoB6K3UC2WRX9BD9Jg,15850
517
518
  wbportfolio/viewsets/transactions/transactions.py,sha256=ixDp-nsNA8t_A06rBCT19hOMJHy0iRmdz1XKdV1OwAs,4450
518
- wbportfolio-1.45.1.dist-info/METADATA,sha256=A-LmtAIGxt9NHM9lduI0ajteCd-0BbzXyDcQu35Jpik,734
519
- wbportfolio-1.45.1.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
520
- wbportfolio-1.45.1.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
521
- wbportfolio-1.45.1.dist-info/RECORD,,
519
+ wbportfolio-1.46.1.dist-info/METADATA,sha256=MBqT0rgbHIWQspGtldYVWSruQOouRdQDxcV4U9dunWk,734
520
+ wbportfolio-1.46.1.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
521
+ wbportfolio-1.46.1.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
522
+ wbportfolio-1.46.1.dist-info/RECORD,,