wbportfolio 1.49.5__py2.py3-none-any.whl → 1.49.6__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 +0 -1
- wbportfolio/models/transactions/trade_proposals.py +60 -34
- wbportfolio/models/transactions/trades.py +38 -15
- wbportfolio/rebalancing/models/composite.py +6 -2
- wbportfolio/rebalancing/models/equally_weighted.py +12 -1
- wbportfolio/rebalancing/models/model_portfolio.py +11 -5
- wbportfolio/tests/models/transactions/test_rebalancing.py +6 -2
- wbportfolio/tests/rebalancing/test_models.py +18 -12
- wbportfolio/viewsets/configs/display/portfolios.py +1 -3
- {wbportfolio-1.49.5.dist-info → wbportfolio-1.49.6.dist-info}/METADATA +1 -1
- {wbportfolio-1.49.5.dist-info → wbportfolio-1.49.6.dist-info}/RECORD +13 -13
- {wbportfolio-1.49.5.dist-info → wbportfolio-1.49.6.dist-info}/WHEEL +0 -0
- {wbportfolio-1.49.5.dist-info → wbportfolio-1.49.6.dist-info}/licenses/LICENSE +0 -0
wbportfolio/models/asset.py
CHANGED
|
@@ -511,7 +511,6 @@ class AssetPosition(ImportMixin, models.Model):
|
|
|
511
511
|
self.initial_shares = new_weighting * self.get_portfolio_total_asset_value()
|
|
512
512
|
else:
|
|
513
513
|
self.initial_shares = (new_weighting / self.weighting) * self.initial_shares
|
|
514
|
-
self.save()
|
|
515
514
|
|
|
516
515
|
def get_portfolio_total_asset_value(self) -> Decimal:
|
|
517
516
|
return self.portfolio.get_total_asset_value(self.date)
|
|
@@ -80,8 +80,6 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
80
80
|
if not self.trade_date and self.portfolio.assets.exists():
|
|
81
81
|
self.trade_date = (self.portfolio.assets.latest("date").date + BDay(1)).date()
|
|
82
82
|
super().save(*args, **kwargs)
|
|
83
|
-
if self.status == TradeProposal.Status.APPROVED:
|
|
84
|
-
self.portfolio.change_at_date(self.trade_date)
|
|
85
83
|
|
|
86
84
|
@property
|
|
87
85
|
def checked_object(self):
|
|
@@ -272,35 +270,43 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
272
270
|
|
|
273
271
|
def replay(self):
|
|
274
272
|
last_trade_proposal = self
|
|
273
|
+
last_trade_proposal_created = False
|
|
275
274
|
while last_trade_proposal and last_trade_proposal.status == TradeProposal.Status.APPROVED:
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
275
|
+
if not last_trade_proposal_created:
|
|
276
|
+
logger.info(f"Replaying trade proposal {last_trade_proposal}")
|
|
277
|
+
last_trade_proposal.portfolio.assets.filter(
|
|
278
|
+
date=last_trade_proposal.trade_date
|
|
279
|
+
).delete() # we delete the existing position and we reapply the trade proposal
|
|
280
|
+
if last_trade_proposal.status == TradeProposal.Status.APPROVED:
|
|
281
|
+
logger.info("Reverting trade proposal ...")
|
|
282
|
+
last_trade_proposal.revert()
|
|
283
|
+
if last_trade_proposal.status == TradeProposal.Status.DRAFT:
|
|
284
|
+
if self.rebalancing_model: # if there is no position (for any reason) or we the trade proposal has a rebalancer model attached (trades are computed based on an aglo), we reapply this trade proposal
|
|
285
|
+
logger.info(f"Resetting trades from rebalancer model {self.rebalancing_model} ...")
|
|
286
|
+
with suppress(
|
|
287
|
+
ValidationError
|
|
288
|
+
): # we silent any validation error while setting proposal, because if this happens, we assume the current trade proposal state if valid and we continue to batch compute
|
|
289
|
+
self.reset_trades()
|
|
290
|
+
logger.info("Submitting trade proposal ...")
|
|
291
|
+
last_trade_proposal.submit()
|
|
292
|
+
if last_trade_proposal.status == TradeProposal.Status.SUBMIT:
|
|
293
|
+
logger.info("Approving trade proposal ...")
|
|
294
|
+
last_trade_proposal.approve(replay=False)
|
|
295
|
+
last_trade_proposal.save()
|
|
296
296
|
next_trade_proposal = last_trade_proposal.next_trade_proposal
|
|
297
|
+
|
|
297
298
|
next_trade_date = (
|
|
298
299
|
next_trade_proposal.trade_date - timedelta(days=1) if next_trade_proposal else date.today()
|
|
299
300
|
)
|
|
300
301
|
overriding_trade_proposal = last_trade_proposal.portfolio.batch_portfolio(
|
|
301
302
|
last_trade_proposal.trade_date, next_trade_date
|
|
302
303
|
)
|
|
303
|
-
|
|
304
|
+
if overriding_trade_proposal:
|
|
305
|
+
last_trade_proposal_created = True
|
|
306
|
+
last_trade_proposal = overriding_trade_proposal
|
|
307
|
+
else:
|
|
308
|
+
last_trade_proposal_created = False
|
|
309
|
+
last_trade_proposal = next_trade_proposal
|
|
304
310
|
|
|
305
311
|
def get_estimated_shares(self, weight: Decimal, underlying_quote: Instrument) -> Decimal:
|
|
306
312
|
"""
|
|
@@ -398,11 +404,14 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
398
404
|
},
|
|
399
405
|
)
|
|
400
406
|
def submit(self, by=None, description=None, **kwargs):
|
|
401
|
-
self.trades.update(comment="", status=Trade.Status.DRAFT)
|
|
402
407
|
self.reset_trades(target_portfolio=self._build_dto().convert_to_portfolio())
|
|
408
|
+
trades = []
|
|
403
409
|
for trade in self.trades.all():
|
|
404
|
-
trade.
|
|
405
|
-
trade.
|
|
410
|
+
trade.status = Trade.Status.SUBMIT
|
|
411
|
+
trade.comment = ""
|
|
412
|
+
trades.append(trade)
|
|
413
|
+
|
|
414
|
+
Trade.objects.bulk_update(trades, ["status", "comment"])
|
|
406
415
|
|
|
407
416
|
# If we estimate cash on this trade proposal, we make sure to create the corresponding cash component
|
|
408
417
|
cash_target_cash_weight, cash_target_cash_shares = self.get_estimated_target_cash(self.portfolio.currency)
|
|
@@ -475,11 +484,16 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
475
484
|
# We validate trade which will create or update the initial asset positions
|
|
476
485
|
if not self.portfolio.can_be_rebalanced:
|
|
477
486
|
raise ValueError("Non-Rebalanceable portfolio cannot be traded manually.")
|
|
478
|
-
|
|
479
|
-
|
|
487
|
+
trades = []
|
|
488
|
+
assets = []
|
|
480
489
|
for trade in self.trades.all():
|
|
481
|
-
|
|
482
|
-
|
|
490
|
+
with suppress(ValueError):
|
|
491
|
+
assets.append(trade.to_asset())
|
|
492
|
+
trade.status = Trade.Status.EXECUTED
|
|
493
|
+
trades.append(trade)
|
|
494
|
+
|
|
495
|
+
Trade.objects.bulk_update(trades, ["status"])
|
|
496
|
+
self.portfolio.bulk_create_positions(assets, evaluate_rebalancer=False)
|
|
483
497
|
if replay and self.portfolio.is_manageable:
|
|
484
498
|
replay_as_task.delay(self.id)
|
|
485
499
|
|
|
@@ -584,10 +598,22 @@ class TradeProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
584
598
|
def revert(self, **kwargs):
|
|
585
599
|
with suppress(KeyError):
|
|
586
600
|
del self.__dict__["validated_trading_service"]
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
601
|
+
trades = []
|
|
602
|
+
assets = []
|
|
603
|
+
for trade in self.trades.all():
|
|
604
|
+
trade.status = Trade.Status.DRAFT
|
|
605
|
+
trades.append(trade)
|
|
606
|
+
with suppress(AssetPosition.DoesNotExist):
|
|
607
|
+
asset = AssetPosition.unannotated_objects.get(
|
|
608
|
+
underlying_quote=trade.underlying_instrument,
|
|
609
|
+
portfolio=trade.portfolio,
|
|
610
|
+
date=trade.transaction_date,
|
|
611
|
+
is_estimated=False,
|
|
612
|
+
)
|
|
613
|
+
asset.set_weighting(asset.weighting - trade.weighting)
|
|
614
|
+
assets.append(asset)
|
|
615
|
+
Trade.objects.bulk_update(trades, ["status"])
|
|
616
|
+
self.portfolio.bulk_create_positions(assets, evaluate_rebalancer=False)
|
|
591
617
|
|
|
592
618
|
def can_revert(self):
|
|
593
619
|
errors = dict()
|
|
@@ -299,25 +299,47 @@ 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
|
+
|
|
302
324
|
def execute(self, **kwargs):
|
|
303
|
-
|
|
304
|
-
asset
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
325
|
+
with suppress(ValueError):
|
|
326
|
+
asset = self.to_asset()
|
|
327
|
+
AssetPosition.unannotated_objects.update_or_create(
|
|
328
|
+
underlying_quote=asset.underlying_quote,
|
|
329
|
+
portfolio_created=asset.portfolio_created,
|
|
330
|
+
portfolio=asset.portfolio,
|
|
331
|
+
date=asset.date,
|
|
309
332
|
defaults={
|
|
310
|
-
"initial_currency_fx_rate":
|
|
311
|
-
"
|
|
312
|
-
"
|
|
313
|
-
"
|
|
314
|
-
"
|
|
315
|
-
"
|
|
316
|
-
"
|
|
317
|
-
"
|
|
333
|
+
"initial_currency_fx_rate": asset.initial_currency_fx_rate,
|
|
334
|
+
"initial_price": asset.initial_price,
|
|
335
|
+
"initial_shares": asset.initial_shares,
|
|
336
|
+
"underlying_quote_price": asset.underlying_quote_price,
|
|
337
|
+
"asset_valuation_date": asset.asset_valuation_date,
|
|
338
|
+
"currency": asset.currency,
|
|
339
|
+
"is_estimated": asset.is_estimated,
|
|
340
|
+
"weighting": asset.weighting,
|
|
318
341
|
},
|
|
319
342
|
)
|
|
320
|
-
asset.set_weighting(self._target_weight)
|
|
321
343
|
|
|
322
344
|
def can_execute(self):
|
|
323
345
|
if not self.last_underlying_quote_price:
|
|
@@ -402,6 +424,7 @@ class Trade(ShareMixin, Transaction, OrderedModel, WBModel):
|
|
|
402
424
|
is_estimated=False,
|
|
403
425
|
)
|
|
404
426
|
asset.set_weighting(asset.weighting - self.weighting)
|
|
427
|
+
asset.save()
|
|
405
428
|
|
|
406
429
|
@property
|
|
407
430
|
def product(self):
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from decimal import Decimal
|
|
2
2
|
|
|
3
3
|
from django.core.exceptions import ObjectDoesNotExist
|
|
4
|
+
from wbfdm.models import InstrumentPrice
|
|
4
5
|
|
|
5
6
|
from wbportfolio.models import Trade
|
|
6
7
|
from wbportfolio.pms.typing import Portfolio, Position
|
|
@@ -20,7 +21,7 @@ class CompositeRebalancing(AbstractRebalancingModel):
|
|
|
20
21
|
"""
|
|
21
22
|
try:
|
|
22
23
|
latest_trade_proposal = self.portfolio.trade_proposals.filter(
|
|
23
|
-
status="APPROVED",
|
|
24
|
+
status="APPROVED", trade_date__lt=self.trade_date
|
|
24
25
|
).latest("trade_date")
|
|
25
26
|
return {
|
|
26
27
|
v["underlying_instrument"]: v["target_weight"]
|
|
@@ -33,7 +34,10 @@ class CompositeRebalancing(AbstractRebalancingModel):
|
|
|
33
34
|
return dict()
|
|
34
35
|
|
|
35
36
|
def is_valid(self) -> bool:
|
|
36
|
-
return
|
|
37
|
+
return (
|
|
38
|
+
len(self.base_assets.keys()) > 0
|
|
39
|
+
and InstrumentPrice.objects.filter(date=self.trade_date, instrument__in=self.base_assets.keys()).exists()
|
|
40
|
+
)
|
|
37
41
|
|
|
38
42
|
def get_target_portfolio(self) -> Portfolio:
|
|
39
43
|
positions = []
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from decimal import Decimal
|
|
2
2
|
|
|
3
|
+
from wbfdm.models import InstrumentPrice
|
|
4
|
+
|
|
3
5
|
from wbportfolio.pms.typing import Portfolio
|
|
4
6
|
from wbportfolio.rebalancing.base import AbstractRebalancingModel
|
|
5
7
|
from wbportfolio.rebalancing.decorators import register
|
|
@@ -7,8 +9,17 @@ from wbportfolio.rebalancing.decorators import register
|
|
|
7
9
|
|
|
8
10
|
@register("Equally Weighted Rebalancing")
|
|
9
11
|
class EquallyWeightedRebalancing(AbstractRebalancingModel):
|
|
12
|
+
def __init__(self, *args, **kwargs):
|
|
13
|
+
super().__init__(*args, **kwargs)
|
|
14
|
+
self.assets = self.portfolio.assets.filter(date=self.last_effective_date)
|
|
15
|
+
|
|
10
16
|
def is_valid(self) -> bool:
|
|
11
|
-
return
|
|
17
|
+
return (
|
|
18
|
+
self.assets.exists()
|
|
19
|
+
and InstrumentPrice.objects.filter(
|
|
20
|
+
date=self.trade_date, instrument__in=self.assets.values("underlying_quote")
|
|
21
|
+
).exists()
|
|
22
|
+
)
|
|
12
23
|
|
|
13
24
|
def get_target_portfolio(self) -> Portfolio:
|
|
14
25
|
positions = []
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from wbfdm.models import InstrumentPrice
|
|
2
|
+
|
|
1
3
|
from wbportfolio.pms.typing import Portfolio
|
|
2
4
|
from wbportfolio.rebalancing.base import AbstractRebalancingModel
|
|
3
5
|
from wbportfolio.rebalancing.decorators import register
|
|
@@ -18,11 +20,15 @@ class ModelPortfolioRebalancing(AbstractRebalancingModel):
|
|
|
18
20
|
return model_portfolio_rel.dependency_portfolio
|
|
19
21
|
|
|
20
22
|
def is_valid(self) -> bool:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
if model_portfolio := self.model_portfolio:
|
|
24
|
+
assets = model_portfolio.get_positions(self.last_effective_date)
|
|
25
|
+
return (
|
|
26
|
+
assets.exists()
|
|
27
|
+
and InstrumentPrice.objects.filter(
|
|
28
|
+
date=self.trade_date, instrument__in=assets.values("underlying_quote")
|
|
29
|
+
).exists()
|
|
30
|
+
)
|
|
31
|
+
return False
|
|
26
32
|
|
|
27
33
|
def get_target_portfolio(self) -> Portfolio:
|
|
28
34
|
positions = []
|
|
@@ -11,11 +11,15 @@ from wbportfolio.models import AssetPosition, TradeProposal
|
|
|
11
11
|
|
|
12
12
|
@pytest.mark.django_db
|
|
13
13
|
class TestRebalancingModel:
|
|
14
|
-
def test_get_target_portfolio(
|
|
14
|
+
def test_get_target_portfolio(
|
|
15
|
+
self, rebalancing_model, portfolio, weekday, asset_position_factory, instrument_price_factory
|
|
16
|
+
):
|
|
17
|
+
trade_date = (weekday + BDay(1)).date()
|
|
15
18
|
with pytest.raises(ValidationError): # trigger value error because rebalancing not valid (no position yet)
|
|
16
|
-
rebalancing_model.get_target_portfolio(portfolio,
|
|
19
|
+
rebalancing_model.get_target_portfolio(portfolio, trade_date, weekday)
|
|
17
20
|
a1 = asset_position_factory(weighting=0.7, portfolio=portfolio, date=weekday)
|
|
18
21
|
a2 = asset_position_factory(weighting=0.3, portfolio=portfolio, date=weekday)
|
|
22
|
+
instrument_price_factory.create(instrument=a1.underlying_quote, date=trade_date)
|
|
19
23
|
target_portfolio = rebalancing_model.get_target_portfolio(portfolio, (weekday + BDay(1)).date(), weekday)
|
|
20
24
|
target_positions = target_portfolio.positions_map
|
|
21
25
|
assert target_positions[a1.underlying_instrument.id].weighting == 0.5
|
|
@@ -16,9 +16,11 @@ class TestEquallyWeightedRebalancing:
|
|
|
16
16
|
|
|
17
17
|
return EquallyWeightedRebalancing(portfolio, (weekday + BDay(1)).date(), weekday)
|
|
18
18
|
|
|
19
|
-
def test_is_valid(self, portfolio, weekday, model, asset_position_factory):
|
|
19
|
+
def test_is_valid(self, portfolio, weekday, model, asset_position_factory, instrument_price_factory):
|
|
20
20
|
assert not model.is_valid()
|
|
21
|
-
asset_position_factory.create(portfolio=model.portfolio, date=model.last_effective_date)
|
|
21
|
+
a = asset_position_factory.create(portfolio=model.portfolio, date=model.last_effective_date)
|
|
22
|
+
assert not model.is_valid()
|
|
23
|
+
instrument_price_factory.create(instrument=a.underlying_quote, date=model.trade_date)
|
|
22
24
|
assert model.is_valid()
|
|
23
25
|
|
|
24
26
|
def test_get_target_portfolio(self, portfolio, weekday, model, asset_position_factory):
|
|
@@ -38,7 +40,7 @@ class TestModelPortfolioRebalancing:
|
|
|
38
40
|
|
|
39
41
|
return ModelPortfolioRebalancing(portfolio, (weekday + BDay(1)).date(), weekday)
|
|
40
42
|
|
|
41
|
-
def test_is_valid(self, portfolio, weekday, model, asset_position_factory):
|
|
43
|
+
def test_is_valid(self, portfolio, weekday, model, asset_position_factory, instrument_price_factory):
|
|
42
44
|
assert not model.is_valid()
|
|
43
45
|
asset_position_factory.create(portfolio=model.portfolio, date=model.last_effective_date)
|
|
44
46
|
assert not model.is_valid()
|
|
@@ -48,7 +50,9 @@ class TestModelPortfolioRebalancing:
|
|
|
48
50
|
dependency_portfolio=model_portfolio,
|
|
49
51
|
type=PortfolioPortfolioThroughModel.Type.MODEL,
|
|
50
52
|
)
|
|
51
|
-
asset_position_factory.create(portfolio=model.model_portfolio, date=model.last_effective_date)
|
|
53
|
+
a = asset_position_factory.create(portfolio=model.model_portfolio, date=model.last_effective_date)
|
|
54
|
+
assert not model.is_valid()
|
|
55
|
+
instrument_price_factory.create(instrument=a.underlying_quote, date=model.trade_date)
|
|
52
56
|
assert model.is_valid()
|
|
53
57
|
|
|
54
58
|
def test_get_target_portfolio(self, portfolio, weekday, model, asset_position_factory):
|
|
@@ -76,15 +80,15 @@ class TestCompositeRebalancing:
|
|
|
76
80
|
|
|
77
81
|
return CompositeRebalancing(portfolio, (weekday + BDay(1)).date(), weekday)
|
|
78
82
|
|
|
79
|
-
def test_is_valid(self, portfolio, weekday, model, asset_position_factory):
|
|
83
|
+
def test_is_valid(self, portfolio, weekday, model, asset_position_factory, instrument_price_factory):
|
|
80
84
|
assert not model.is_valid()
|
|
81
85
|
|
|
82
86
|
trade_proposal = TradeProposalFactory.create(
|
|
83
|
-
portfolio=model.portfolio, trade_date=model.
|
|
87
|
+
portfolio=model.portfolio, trade_date=model.last_effective_date, status=TradeProposal.Status.APPROVED
|
|
84
88
|
)
|
|
85
|
-
TradeFactory.create(
|
|
89
|
+
t1 = TradeFactory.create(
|
|
86
90
|
portfolio=model.portfolio,
|
|
87
|
-
transaction_date=model.
|
|
91
|
+
transaction_date=model.last_effective_date,
|
|
88
92
|
transaction_subtype=Trade.Type.BUY,
|
|
89
93
|
trade_proposal=trade_proposal,
|
|
90
94
|
weighting=0.7,
|
|
@@ -92,23 +96,25 @@ class TestCompositeRebalancing:
|
|
|
92
96
|
)
|
|
93
97
|
TradeFactory.create(
|
|
94
98
|
portfolio=model.portfolio,
|
|
95
|
-
transaction_date=model.
|
|
99
|
+
transaction_date=model.last_effective_date,
|
|
96
100
|
transaction_subtype=Trade.Type.BUY,
|
|
97
101
|
trade_proposal=trade_proposal,
|
|
98
102
|
weighting=0.3,
|
|
99
103
|
status=Trade.Status.EXECUTED,
|
|
100
104
|
)
|
|
105
|
+
assert not model.is_valid()
|
|
106
|
+
instrument_price_factory.create(instrument=t1.underlying_instrument, date=model.trade_date)
|
|
101
107
|
assert model.is_valid()
|
|
102
108
|
|
|
103
109
|
def test_get_target_portfolio(self, portfolio, weekday, model, asset_position_factory):
|
|
104
110
|
trade_proposal = TradeProposalFactory.create(
|
|
105
|
-
portfolio=model.portfolio, trade_date=model.
|
|
111
|
+
portfolio=model.portfolio, trade_date=model.last_effective_date, status=TradeProposal.Status.APPROVED
|
|
106
112
|
)
|
|
107
113
|
asset_position_factory(portfolio=portfolio, date=model.last_effective_date) # noise
|
|
108
114
|
asset_position_factory(portfolio=portfolio, date=model.last_effective_date) # noise
|
|
109
115
|
t1 = TradeFactory.create(
|
|
110
116
|
portfolio=model.portfolio,
|
|
111
|
-
transaction_date=model.
|
|
117
|
+
transaction_date=model.last_effective_date,
|
|
112
118
|
transaction_subtype=Trade.Type.BUY,
|
|
113
119
|
trade_proposal=trade_proposal,
|
|
114
120
|
weighting=0.8,
|
|
@@ -116,7 +122,7 @@ class TestCompositeRebalancing:
|
|
|
116
122
|
)
|
|
117
123
|
t2 = TradeFactory.create(
|
|
118
124
|
portfolio=model.portfolio,
|
|
119
|
-
transaction_date=model.
|
|
125
|
+
transaction_date=model.last_effective_date,
|
|
120
126
|
transaction_subtype=Trade.Type.BUY,
|
|
121
127
|
trade_proposal=trade_proposal,
|
|
122
128
|
weighting=0.2,
|
|
@@ -205,13 +205,11 @@ class TopDownPortfolioCompositionPandasDisplayConfig(DisplayViewConfig):
|
|
|
205
205
|
effective_column_label += f" ({self.view.last_effective_date:%Y-%m-%d})"
|
|
206
206
|
return dp.ListDisplay(
|
|
207
207
|
fields=[
|
|
208
|
-
dp.Field(key="instrument", label="Instrument"),
|
|
208
|
+
dp.Field(key="instrument", label="Instrument", pinned="left"),
|
|
209
209
|
dp.Field(key="rebalancing_weights", label=rebalancing_column_label),
|
|
210
210
|
dp.Field(key="effective_weights", label=effective_column_label),
|
|
211
211
|
],
|
|
212
212
|
tree=True,
|
|
213
|
-
tree_group_pinned="left",
|
|
214
213
|
tree_group_field="instrument",
|
|
215
|
-
tree_group_label="Instrument",
|
|
216
214
|
tree_group_parent_pointer="parent_row_id",
|
|
217
215
|
)
|
|
@@ -245,7 +245,7 @@ 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=QftWva0GmlStpspcp33TOMZf2t_PTeEhwWdp6xUbQNU,37738
|
|
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
|
|
@@ -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=nrBi6x6PssCtkLtOpV-2OoAHDUKnnYyrM6xH1PmnjSo,7240
|
|
279
|
-
wbportfolio/models/transactions/trade_proposals.py,sha256=
|
|
280
|
-
wbportfolio/models/transactions/trades.py,sha256=
|
|
279
|
+
wbportfolio/models/transactions/trade_proposals.py,sha256=qpjjQMK7G5sX7iiCnXBt07EH-ztxZz-l842yfYrBLQw,27764
|
|
280
|
+
wbportfolio/models/transactions/trades.py,sha256=NA_hUpFKQ4H-w3B0gMZ5IFkZo6WJTH2S7GBUQC1jUpU,28922
|
|
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
|
|
@@ -290,10 +290,10 @@ wbportfolio/rebalancing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
|
290
290
|
wbportfolio/rebalancing/base.py,sha256=NwTGZtBm1f35gj5Jp6iTyyFvDT1GSIztN990cKBvYzQ,637
|
|
291
291
|
wbportfolio/rebalancing/decorators.py,sha256=JhQ2vkcIuGBTGvNmpkQerdw2-vLq-RAb0KAPjOrETkw,515
|
|
292
292
|
wbportfolio/rebalancing/models/__init__.py,sha256=AQjG7Tu5vlmhqncVoYOjpBKU2UIvgo9FuP2_jD2w-UI,232
|
|
293
|
-
wbportfolio/rebalancing/models/composite.py,sha256=
|
|
294
|
-
wbportfolio/rebalancing/models/equally_weighted.py,sha256=
|
|
293
|
+
wbportfolio/rebalancing/models/composite.py,sha256=XEgK3oMurrE_d_l5uN0stBKRrtvnKQzRWyXNXuBYfmc,1818
|
|
294
|
+
wbportfolio/rebalancing/models/equally_weighted.py,sha256=FCpSKOs49ckNYVgoYIiHB0BqPT9OeCMuFoet4Ixbp-Y,1210
|
|
295
295
|
wbportfolio/rebalancing/models/market_capitalization_weighted.py,sha256=6ZsR8iJg6l89CsxHqoxJSXlTaj8Pmb8_bFPrXhnxaRs,5295
|
|
296
|
-
wbportfolio/rebalancing/models/model_portfolio.py,sha256=
|
|
296
|
+
wbportfolio/rebalancing/models/model_portfolio.py,sha256=DNg9vEDYDUwXTOnIpk26FQSPHC0qxkuvW2sJWX0VodQ,1489
|
|
297
297
|
wbportfolio/reports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
298
298
|
wbportfolio/reports/monthly_position_report.py,sha256=e7BzjDd6eseUOwLwQJXKvWErQ58YnCsznHU2VtR6izM,2981
|
|
299
299
|
wbportfolio/risk_management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -383,13 +383,13 @@ wbportfolio/tests/models/utils.py,sha256=ORNJq6NMo1Za22jGZXfTfKeNEnTRlfEt_8SJ6xL
|
|
|
383
383
|
wbportfolio/tests/models/transactions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
384
384
|
wbportfolio/tests/models/transactions/test_claim.py,sha256=NG3BKB-FVcIDgHSJHCjImxgMM3ISVUMl24xUPmEcPec,5570
|
|
385
385
|
wbportfolio/tests/models/transactions/test_fees.py,sha256=1gp_h_CCC4Z_cWHUgrZCjGAxYuT2u8FZdw0krDpESiY,2801
|
|
386
|
-
wbportfolio/tests/models/transactions/test_rebalancing.py,sha256=
|
|
386
|
+
wbportfolio/tests/models/transactions/test_rebalancing.py,sha256=fZ5tx6kByEGXD6nhapYdvk9HOjYlmjhU2w6KlQJ6QE4,4061
|
|
387
387
|
wbportfolio/tests/models/transactions/test_trade_proposals.py,sha256=sqC8uT46Znb03ZwWxIJOaFNBENKunfA5U73xjWbdj80,17053
|
|
388
388
|
wbportfolio/tests/models/transactions/test_trades.py,sha256=vqvOqUY_uXvBp8YOKR0Wq9ycA2oeeEBhO3dzV7sbXEU,9863
|
|
389
389
|
wbportfolio/tests/pms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
390
390
|
wbportfolio/tests/pms/test_analytics.py,sha256=fAuY1zcXibttFpBh2GhKVyzdYfi1kz_b7SPa9xZQXY0,1086
|
|
391
391
|
wbportfolio/tests/rebalancing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
392
|
-
wbportfolio/tests/rebalancing/test_models.py,sha256=
|
|
392
|
+
wbportfolio/tests/rebalancing/test_models.py,sha256=_gT_7UtpOWceDwT7FbTUW6P6ZpCVLBpgXWM0goIljWc,8090
|
|
393
393
|
wbportfolio/tests/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
394
394
|
wbportfolio/tests/serializers/test_claims.py,sha256=vQrg73xQXRFEgvx3KI9ivFre_wpBFzdO0p0J13PkvdY,582
|
|
395
395
|
wbportfolio/tests/viewsets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -444,7 +444,7 @@ wbportfolio/viewsets/configs/display/esg.py,sha256=W8uetCPN2TjHtU2kvQjKOmkq7uoaY
|
|
|
444
444
|
wbportfolio/viewsets/configs/display/fees.py,sha256=_8HDJADh5bA5VMVPkhKObPmNNdPHWR8Oz23PEgd04F0,5834
|
|
445
445
|
wbportfolio/viewsets/configs/display/portfolio_cash_flow.py,sha256=CS_UGhKZSXnX-0SZ0RXzbG1WMJm6NX3GUljwIa4RWBk,5025
|
|
446
446
|
wbportfolio/viewsets/configs/display/portfolio_relationship.py,sha256=8DvsYWFCX29qj5wUf1wCtjlPwzKRd_E7JDuo_CopaJ0,1294
|
|
447
|
-
wbportfolio/viewsets/configs/display/portfolios.py,sha256=
|
|
447
|
+
wbportfolio/viewsets/configs/display/portfolios.py,sha256=FDL0wucfqnyehrPg_vJqZ1JJ_1S37C_TAbON165-07w,10660
|
|
448
448
|
wbportfolio/viewsets/configs/display/positions.py,sha256=yolWLxzGPIpSQSiVhVQChURqbomPt5kSjkYrmXT1Mik,3123
|
|
449
449
|
wbportfolio/viewsets/configs/display/product_groups.py,sha256=PwI-A0_ofShT2pub9-C1HqreiqpHxKMHd51JYwEzvbM,2500
|
|
450
450
|
wbportfolio/viewsets/configs/display/product_performance.py,sha256=6Mme48JBn_okwClR44dBK2OK26ejvdasDvBa5DI33_0,10070
|
|
@@ -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=xeEzx7GP34aBNPlDmiUmT86labsbb8_f1U2RCN1Jatg,21494
|
|
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.6.dist-info/METADATA,sha256=KMGYS4c7qMFyTTr5hrm23nCftQHaW2JSIRsfp0SetKA,734
|
|
525
|
+
wbportfolio-1.49.6.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
|
|
526
|
+
wbportfolio-1.49.6.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
|
|
527
|
+
wbportfolio-1.49.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|