wbportfolio 1.54.15__py2.py3-none-any.whl → 1.54.17__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/orders/order_proposals.py +53 -56
- wbportfolio/pms/trading/handler.py +26 -27
- wbportfolio/serializers/orders/order_proposals.py +7 -2
- wbportfolio/tests/models/orders/test_order_proposals.py +30 -4
- wbportfolio/viewsets/orders/orders.py +21 -17
- {wbportfolio-1.54.15.dist-info → wbportfolio-1.54.17.dist-info}/METADATA +1 -1
- {wbportfolio-1.54.15.dist-info → wbportfolio-1.54.17.dist-info}/RECORD +9 -9
- {wbportfolio-1.54.15.dist-info → wbportfolio-1.54.17.dist-info}/WHEEL +0 -0
- {wbportfolio-1.54.15.dist-info → wbportfolio-1.54.17.dist-info}/licenses/LICENSE +0 -0
|
@@ -24,7 +24,6 @@ from django_fsm import FSMField, transition
|
|
|
24
24
|
from pandas._libs.tslibs.offsets import BDay
|
|
25
25
|
from wbcompliance.models.risk_management.mixins import RiskCheckMixin
|
|
26
26
|
from wbcore.contrib.authentication.models import User
|
|
27
|
-
from wbcore.contrib.currency.models import Currency
|
|
28
27
|
from wbcore.contrib.icons import WBIcon
|
|
29
28
|
from wbcore.contrib.notifications.dispatch import send_notification
|
|
30
29
|
from wbcore.enums import RequestType
|
|
@@ -188,6 +187,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
188
187
|
effective_weight=Round(
|
|
189
188
|
F("contribution") / Value(portfolio_contribution), precision=Order.ORDER_WEIGHTING_PRECISION
|
|
190
189
|
),
|
|
190
|
+
tmp_effective_weight=F("contribution") / Value(portfolio_contribution),
|
|
191
191
|
target_weight=Round(F("effective_weight") + F("weighting"), precision=Order.ORDER_WEIGHTING_PRECISION),
|
|
192
192
|
effective_shares=Coalesce(
|
|
193
193
|
Subquery(
|
|
@@ -206,7 +206,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
206
206
|
)
|
|
207
207
|
total_effective_weight = orders.aggregate(s=models.Sum("effective_weight"))["s"] or Decimal("1")
|
|
208
208
|
with suppress(Order.DoesNotExist):
|
|
209
|
-
largest_order = orders.latest("
|
|
209
|
+
largest_order = orders.latest("effective_weight")
|
|
210
210
|
if quant_error := Decimal("1") - total_effective_weight:
|
|
211
211
|
orders = orders.annotate(
|
|
212
212
|
effective_weight=models.Case(
|
|
@@ -226,7 +226,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
226
226
|
def __str__(self) -> str:
|
|
227
227
|
return f"{self.portfolio.name}: {self.trade_date} ({self.status})"
|
|
228
228
|
|
|
229
|
-
def convert_to_portfolio(self, use_effective: bool = False) -> PortfolioDTO:
|
|
229
|
+
def convert_to_portfolio(self, use_effective: bool = False, with_cash: bool = True) -> PortfolioDTO:
|
|
230
230
|
"""
|
|
231
231
|
Data Transfer Object
|
|
232
232
|
Returns:
|
|
@@ -258,6 +258,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
258
258
|
except InvalidAnalyticPortfolio:
|
|
259
259
|
last_returns, portfolio_contribution = {}, 1
|
|
260
260
|
positions = []
|
|
261
|
+
total_weighting = Decimal("0")
|
|
261
262
|
for instrument, row in portfolio.items():
|
|
262
263
|
weighting = row["weighting"]
|
|
263
264
|
daily_return = Decimal(last_returns.get(instrument.id, 0))
|
|
@@ -285,6 +286,10 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
285
286
|
currency_fx_rate=row["currency_fx_rate"],
|
|
286
287
|
)
|
|
287
288
|
)
|
|
289
|
+
total_weighting += weighting
|
|
290
|
+
if with_cash and (cash_weight := Decimal("1") - total_weighting):
|
|
291
|
+
cash_position = self.get_estimated_target_cash(target_cash_weight=cash_weight)
|
|
292
|
+
positions.append(cash_position._build_dto())
|
|
288
293
|
return PortfolioDTO(positions)
|
|
289
294
|
|
|
290
295
|
# Start tools methods
|
|
@@ -325,7 +330,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
325
330
|
service = TradingService(
|
|
326
331
|
self.trade_date,
|
|
327
332
|
effective_portfolio=self._get_default_effective_portfolio(),
|
|
328
|
-
target_portfolio=self.convert_to_portfolio(),
|
|
333
|
+
target_portfolio=self.convert_to_portfolio(use_effective=False, with_cash=False),
|
|
329
334
|
total_target_weight=total_target_weight,
|
|
330
335
|
)
|
|
331
336
|
leftovers_orders = self.orders.all()
|
|
@@ -352,7 +357,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
352
357
|
return self.rebalancing_model.get_target_portfolio(
|
|
353
358
|
self.portfolio, self.trade_date, self.value_date, **params
|
|
354
359
|
)
|
|
355
|
-
return self.convert_to_portfolio()
|
|
360
|
+
return self.convert_to_portfolio(use_effective=False)
|
|
356
361
|
|
|
357
362
|
def _get_default_effective_portfolio(self):
|
|
358
363
|
return self.convert_to_portfolio(use_effective=True)
|
|
@@ -389,35 +394,34 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
389
394
|
orders = service.trades_batch.trades_map.values()
|
|
390
395
|
for order_dto in orders:
|
|
391
396
|
instrument = Instrument.objects.get(id=order_dto.underlying_instrument)
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
397
|
+
currency_fx_rate = instrument.currency.convert(
|
|
398
|
+
self.value_date, self.portfolio.currency, exact_lookup=True
|
|
399
|
+
)
|
|
400
|
+
# we cannot do a bulk-create because Order is a multi table inheritance
|
|
401
|
+
weighting = round(order_dto.delta_weight, Order.ORDER_WEIGHTING_PRECISION)
|
|
402
|
+
daily_return = order_dto.daily_return
|
|
403
|
+
try:
|
|
404
|
+
order = self.orders.get(underlying_instrument=instrument)
|
|
405
|
+
order.weighting = weighting
|
|
406
|
+
order.currency_fx_rate = currency_fx_rate
|
|
407
|
+
order.daily_return = daily_return
|
|
408
|
+
except Order.DoesNotExist:
|
|
409
|
+
order = Order(
|
|
410
|
+
underlying_instrument=instrument,
|
|
411
|
+
order_proposal=self,
|
|
412
|
+
value_date=self.trade_date,
|
|
413
|
+
weighting=weighting,
|
|
414
|
+
daily_return=daily_return,
|
|
415
|
+
currency_fx_rate=currency_fx_rate,
|
|
395
416
|
)
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
except Order.DoesNotExist:
|
|
405
|
-
order = Order(
|
|
406
|
-
underlying_instrument=instrument,
|
|
407
|
-
order_proposal=self,
|
|
408
|
-
value_date=self.trade_date,
|
|
409
|
-
weighting=weighting,
|
|
410
|
-
daily_return=daily_return,
|
|
411
|
-
currency_fx_rate=currency_fx_rate,
|
|
412
|
-
)
|
|
413
|
-
order.price = order.get_price()
|
|
414
|
-
order.order_type = Order.get_type(weighting, order_dto.previous_weight, order_dto.target_weight)
|
|
415
|
-
# if we cannot automatically find a price, we consider the stock is invalid and we sell it
|
|
416
|
-
if not order.price:
|
|
417
|
-
order.price = Decimal("0.0")
|
|
418
|
-
order.weighting = -order_dto.effective_weight
|
|
419
|
-
|
|
420
|
-
order.save()
|
|
417
|
+
order.price = order.get_price()
|
|
418
|
+
order.order_type = Order.get_type(weighting, order_dto.previous_weight, order_dto.target_weight)
|
|
419
|
+
# if we cannot automatically find a price, we consider the stock is invalid and we sell it
|
|
420
|
+
if not order.price:
|
|
421
|
+
order.price = Decimal("0.0")
|
|
422
|
+
order.weighting = -order_dto.effective_weight
|
|
423
|
+
|
|
424
|
+
order.save()
|
|
421
425
|
# final sanity check to make sure invalid order with effective and target weight of 0 are automatically removed:
|
|
422
426
|
self.get_orders().filter(target_weight=0, effective_weight=0).delete()
|
|
423
427
|
|
|
@@ -455,8 +459,8 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
455
459
|
last_order_proposal = self
|
|
456
460
|
last_order_proposal_created = False
|
|
457
461
|
while last_order_proposal and last_order_proposal.status == OrderProposal.Status.APPROVED:
|
|
462
|
+
logger.info(f"Replaying order proposal {last_order_proposal}")
|
|
458
463
|
if not last_order_proposal_created:
|
|
459
|
-
logger.info(f"Replaying order proposal {last_order_proposal}")
|
|
460
464
|
last_order_proposal.approve_workflow(
|
|
461
465
|
silent_exception=True,
|
|
462
466
|
force_reset_order=True,
|
|
@@ -557,50 +561,43 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
557
561
|
shares = math.floor(shares / round_lot_size) * round_lot_size
|
|
558
562
|
return shares
|
|
559
563
|
|
|
560
|
-
def get_estimated_target_cash(self,
|
|
564
|
+
def get_estimated_target_cash(self, target_cash_weight: Decimal | None = None) -> AssetPosition:
|
|
561
565
|
"""
|
|
562
566
|
Estimates the target cash weight and shares for a order proposal.
|
|
563
567
|
|
|
564
568
|
This method calculates the target cash weight by summing the weights of cash orders and adding any leftover weight from non-cash orders. It then estimates the target shares for this cash component if the portfolio is not only weighting-based.
|
|
565
569
|
|
|
566
570
|
Args:
|
|
567
|
-
|
|
571
|
+
target_cash_weight (Decimal): the expected target cash weight (Optional). If not provided, we estimate from the existing orders
|
|
568
572
|
|
|
569
573
|
Returns:
|
|
570
574
|
tuple[Decimal, Decimal]: A tuple containing the target cash weight and the estimated target shares.
|
|
571
575
|
"""
|
|
572
576
|
# Retrieve orders with base information
|
|
573
577
|
orders = self.get_orders()
|
|
578
|
+
currency = self.portfolio.currency
|
|
574
579
|
|
|
575
|
-
# Calculate the target
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
)
|
|
579
|
-
# if the specified currency match the portfolio's currency, we include the weight leftover to this cash compoenent
|
|
580
|
-
if currency == self.portfolio.currency:
|
|
581
|
-
# Calculate the total target weight of all orders
|
|
582
|
-
total_target_weight = orders.aggregate(s=models.Sum("target_weight"))["s"] or Decimal(0)
|
|
580
|
+
# Calculate the total target weight of all orders
|
|
581
|
+
total_target_weight = orders.exclude(underlying_instrument__is_cash=True).aggregate(
|
|
582
|
+
s=models.Sum("target_weight")
|
|
583
|
+
)["s"] or Decimal(0)
|
|
583
584
|
|
|
584
|
-
|
|
585
|
-
target_cash_weight
|
|
585
|
+
if target_cash_weight is None:
|
|
586
|
+
target_cash_weight = Decimal("1") - total_target_weight
|
|
586
587
|
|
|
587
588
|
# Initialize target shares to zero
|
|
588
589
|
total_target_shares = Decimal(0)
|
|
589
590
|
|
|
591
|
+
# Get or create a cash component for the portfolio's currency
|
|
592
|
+
cash_component = Cash.objects.get_or_create(
|
|
593
|
+
currency=currency, defaults={"is_cash": True, "name": currency.title}
|
|
594
|
+
)[0]
|
|
590
595
|
# If the portfolio is not only weighting-based, estimate the target shares for the cash component
|
|
591
596
|
if not self.portfolio.only_weighting:
|
|
592
|
-
# Get or create a cash component for the portfolio's currency
|
|
593
|
-
cash_component = Cash.objects.get_or_create(
|
|
594
|
-
currency=currency, defaults={"is_cash": True, "name": currency.title}
|
|
595
|
-
)[0]
|
|
596
|
-
|
|
597
597
|
# Estimate the target shares for the cash component
|
|
598
598
|
with suppress(ValueError):
|
|
599
599
|
total_target_shares = self.get_estimated_shares(target_cash_weight, cash_component, Decimal("1.0"))
|
|
600
600
|
|
|
601
|
-
cash_component = Cash.objects.get_or_create(
|
|
602
|
-
currency=self.portfolio.currency, defaults={"name": self.portfolio.currency.title}
|
|
603
|
-
)[0]
|
|
604
601
|
# otherwise, we create a new position
|
|
605
602
|
underlying_quote_price = InstrumentPrice.objects.get_or_create(
|
|
606
603
|
instrument=cash_component,
|
|
@@ -657,7 +654,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
657
654
|
Order.objects.bulk_update(orders, ["shares", "weighting"])
|
|
658
655
|
|
|
659
656
|
# If we estimate cash on this order proposal, we make sure to create the corresponding cash component
|
|
660
|
-
estimated_cash_position = self.get_estimated_target_cash(
|
|
657
|
+
estimated_cash_position = self.get_estimated_target_cash()
|
|
661
658
|
target_portfolio = self.validated_trading_service.trades_batch.convert_to_portfolio(
|
|
662
659
|
estimated_cash_position._build_dto()
|
|
663
660
|
)
|
|
@@ -717,7 +714,7 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
717
714
|
assets = []
|
|
718
715
|
warnings = []
|
|
719
716
|
# We do not want to create the estimated cash position if there is not orders in the order proposal (shouldn't be possible anyway)
|
|
720
|
-
estimated_cash_position = self.get_estimated_target_cash(
|
|
717
|
+
estimated_cash_position = self.get_estimated_target_cash()
|
|
721
718
|
for order in self.get_orders():
|
|
722
719
|
with suppress(ValueError):
|
|
723
720
|
asset = order.get_asset()
|
|
@@ -143,33 +143,32 @@ class TradingService:
|
|
|
143
143
|
|
|
144
144
|
trades: list[Trade] = []
|
|
145
145
|
for instrument_id, pos in instruments.items():
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
trades.append(trade)
|
|
146
|
+
previous_weight = target_weight = 0
|
|
147
|
+
effective_shares = target_shares = 0
|
|
148
|
+
daily_return = 0
|
|
149
|
+
if effective_pos := effective_portfolio.positions_map.get(instrument_id, None):
|
|
150
|
+
previous_weight = effective_pos.weighting
|
|
151
|
+
effective_shares = effective_pos.shares
|
|
152
|
+
daily_return = effective_pos.daily_return
|
|
153
|
+
if target_pos := target_portfolio.positions_map.get(instrument_id, None):
|
|
154
|
+
target_weight = target_pos.weighting
|
|
155
|
+
if target_pos.shares is not None:
|
|
156
|
+
target_shares = target_pos.shares
|
|
157
|
+
trade = Trade(
|
|
158
|
+
underlying_instrument=instrument_id,
|
|
159
|
+
previous_weight=previous_weight,
|
|
160
|
+
target_weight=target_weight,
|
|
161
|
+
effective_shares=effective_shares,
|
|
162
|
+
target_shares=target_shares,
|
|
163
|
+
date=self.trade_date,
|
|
164
|
+
instrument_type=pos.instrument_type,
|
|
165
|
+
currency=pos.currency,
|
|
166
|
+
price=Decimal(pos.price) if pos.price is not None else Decimal("0"),
|
|
167
|
+
currency_fx_rate=Decimal(pos.currency_fx_rate),
|
|
168
|
+
daily_return=Decimal(daily_return),
|
|
169
|
+
portfolio_contribution=effective_portfolio.portfolio_contribution,
|
|
170
|
+
)
|
|
171
|
+
trades.append(trade)
|
|
173
172
|
return TradeBatch(trades)
|
|
174
173
|
|
|
175
174
|
def is_valid(self, ignore_error: bool = False) -> bool:
|
|
@@ -23,7 +23,7 @@ class OrderProposalModelSerializer(wb_serializers.ModelSerializer):
|
|
|
23
23
|
rebalancing_model = wb_serializers.PrimaryKeyRelatedField(queryset=RebalancingModel.objects.all(), required=False)
|
|
24
24
|
_rebalancing_model = RebalancingModelRepresentationSerializer(source="rebalancing_model")
|
|
25
25
|
target_portfolio = wb_serializers.PrimaryKeyRelatedField(
|
|
26
|
-
queryset=Portfolio.objects.all(), write_only=True, required=False
|
|
26
|
+
queryset=Portfolio.objects.all(), write_only=True, required=False
|
|
27
27
|
)
|
|
28
28
|
_target_portfolio = PortfolioRepresentationSerializer(source="target_portfolio")
|
|
29
29
|
total_cash_weight = wb_serializers.DecimalField(
|
|
@@ -51,8 +51,13 @@ class OrderProposalModelSerializer(wb_serializers.ModelSerializer):
|
|
|
51
51
|
obj = super().create(validated_data)
|
|
52
52
|
|
|
53
53
|
target_portfolio_dto = None
|
|
54
|
-
if target_portfolio
|
|
54
|
+
if target_portfolio:
|
|
55
55
|
target_portfolio_dto = target_portfolio._build_dto(obj.trade_date)
|
|
56
|
+
elif rebalancing_model:
|
|
57
|
+
target_portfolio_dto = rebalancing_model.get_target_portfolio(
|
|
58
|
+
obj.portfolio, obj.trade_date, obj.last_effective_date
|
|
59
|
+
)
|
|
60
|
+
|
|
56
61
|
try:
|
|
57
62
|
obj.reset_orders(
|
|
58
63
|
target_portfolio=target_portfolio_dto, total_target_weight=Decimal("1.0") - total_cash_weight
|
|
@@ -248,7 +248,9 @@ class TestOrderProposal:
|
|
|
248
248
|
assert t3.weighting == normalized_t3_weight
|
|
249
249
|
|
|
250
250
|
# Test resetting orders
|
|
251
|
-
def test_reset_orders(
|
|
251
|
+
def test_reset_orders(
|
|
252
|
+
self, order_proposal, instrument_factory, cash, instrument_price_factory, asset_position_factory
|
|
253
|
+
):
|
|
252
254
|
"""
|
|
253
255
|
Verify orders are correctly reset based on effective and target portfolios.
|
|
254
256
|
"""
|
|
@@ -334,6 +336,31 @@ class TestOrderProposal:
|
|
|
334
336
|
assert t2.weighting == Decimal("0")
|
|
335
337
|
assert t3.weighting == Decimal("0.5")
|
|
336
338
|
|
|
339
|
+
# assert cash position creates a proper order
|
|
340
|
+
# build the target portfolio
|
|
341
|
+
target_portfolio_with_cash = PortfolioDTO(
|
|
342
|
+
[
|
|
343
|
+
Position(
|
|
344
|
+
underlying_instrument=i1.id,
|
|
345
|
+
date=order_proposal.trade_date,
|
|
346
|
+
weighting=Decimal("0.5"),
|
|
347
|
+
price=float(p1.net_value),
|
|
348
|
+
),
|
|
349
|
+
Position(
|
|
350
|
+
underlying_instrument=cash.id,
|
|
351
|
+
date=order_proposal.trade_date,
|
|
352
|
+
weighting=Decimal("0.5"),
|
|
353
|
+
price=1.0,
|
|
354
|
+
),
|
|
355
|
+
]
|
|
356
|
+
)
|
|
357
|
+
order_proposal.reset_orders(target_portfolio=target_portfolio_with_cash)
|
|
358
|
+
|
|
359
|
+
# Assert existing trade weights are correctly updated
|
|
360
|
+
assert order_proposal.orders.get(underlying_instrument=i1).weighting == Decimal("-0.2")
|
|
361
|
+
assert order_proposal.orders.get(underlying_instrument=i2).weighting == Decimal("-0.3")
|
|
362
|
+
assert order_proposal.orders.get(underlying_instrument=cash).weighting == Decimal("0.5")
|
|
363
|
+
|
|
337
364
|
def test_reset_orders_remove_invalid_orders(self, order_proposal, order_factory, instrument_price_factory):
|
|
338
365
|
# create a invalid trade and its price
|
|
339
366
|
invalid_trade = order_factory.create(order_proposal=order_proposal, weighting=Decimal(0))
|
|
@@ -437,9 +464,8 @@ class TestOrderProposal:
|
|
|
437
464
|
underlying_instrument=cash,
|
|
438
465
|
weighting=Decimal("0.2"),
|
|
439
466
|
)
|
|
440
|
-
|
|
441
|
-
target_cash_position
|
|
442
|
-
assert target_cash_position.weighting == Decimal("0.2") + Decimal("1.0") - (Decimal("0.7") + Decimal("0.2"))
|
|
467
|
+
target_cash_position = order_proposal.get_estimated_target_cash()
|
|
468
|
+
assert target_cash_position.weighting == Decimal("0.3")
|
|
443
469
|
assert target_cash_position.initial_shares == Decimal(1_000_000) * Decimal("0.3")
|
|
444
470
|
|
|
445
471
|
def test_order_proposal_update_inception_date(self, order_proposal_factory, portfolio, instrument_factory):
|
|
@@ -198,22 +198,26 @@ class OrderOrderProposalModelViewSet(
|
|
|
198
198
|
return Order.objects.none()
|
|
199
199
|
|
|
200
200
|
def get_queryset(self):
|
|
201
|
-
return
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
201
|
+
return (
|
|
202
|
+
self.orders.exclude(underlying_instrument__is_cash=True)
|
|
203
|
+
.annotate(
|
|
204
|
+
underlying_instrument_isin=F("underlying_instrument__isin"),
|
|
205
|
+
underlying_instrument_ticker=F("underlying_instrument__ticker"),
|
|
206
|
+
underlying_instrument_refinitiv_identifier_code=F("underlying_instrument__refinitiv_identifier_code"),
|
|
207
|
+
underlying_instrument_instrument_type=Case(
|
|
208
|
+
When(
|
|
209
|
+
underlying_instrument__parent__is_security=True,
|
|
210
|
+
then=F("underlying_instrument__parent__instrument_type__short_name"),
|
|
211
|
+
),
|
|
212
|
+
default=F("underlying_instrument__instrument_type__short_name"),
|
|
209
213
|
),
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
214
|
+
effective_total_value_fx_portfolio=F("effective_weight") * Value(self.portfolio_total_asset_value),
|
|
215
|
+
target_total_value_fx_portfolio=F("target_weight") * Value(self.portfolio_total_asset_value),
|
|
216
|
+
portfolio_currency=F("portfolio__currency__symbol"),
|
|
217
|
+
security=F("underlying_instrument__parent"),
|
|
218
|
+
company=F("underlying_instrument__parent__parent"),
|
|
219
|
+
)
|
|
220
|
+
.select_related(
|
|
221
|
+
"underlying_instrument", "underlying_instrument__parent", "underlying_instrument__parent__parent"
|
|
222
|
+
)
|
|
219
223
|
)
|
|
@@ -288,7 +288,7 @@ wbportfolio/models/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
|
288
288
|
wbportfolio/models/mixins/instruments.py,sha256=SgBreTpa_X3uyCWo7t8B0VaTtl49IjmBMe4Pab6TjAM,6796
|
|
289
289
|
wbportfolio/models/mixins/liquidity_stress_test.py,sha256=iQVzT3QM7VtHnqfj9gT6KUIe4wC4MJXery-AXJHUYns,58820
|
|
290
290
|
wbportfolio/models/orders/__init__.py,sha256=EH9UacGR3npBMje5FGTeLOh1xqFBh9kc24WbGmBIA3g,69
|
|
291
|
-
wbportfolio/models/orders/order_proposals.py,sha256=
|
|
291
|
+
wbportfolio/models/orders/order_proposals.py,sha256=U1vLwI_yS2E1TUs04NJGperusqmNyiE42XGlAVoPatk,40196
|
|
292
292
|
wbportfolio/models/orders/orders.py,sha256=hVVw7NAFmAFHosMMs39V9DjGmWyFC_msSxF8rpDDG60,9683
|
|
293
293
|
wbportfolio/models/reconciliations/__init__.py,sha256=MXH5fZIPGDRBgJkO6wVu_NLRs8fkP1im7G6d-h36lQY,127
|
|
294
294
|
wbportfolio/models/reconciliations/account_reconciliation_lines.py,sha256=QP6M7hMcyFbuXBa55Y-azui6Dl_WgbzMntEqWzQkbfM,7394
|
|
@@ -307,7 +307,7 @@ wbportfolio/pms/analytics/portfolio.py,sha256=u_S-e6HUQwAyq90gweDmxyTHWrIc5nd84s
|
|
|
307
307
|
wbportfolio/pms/analytics/utils.py,sha256=EfhKdo9B2ABaUPppb8DgZSqpNkSze8Rjej1xDjv-XcQ,282
|
|
308
308
|
wbportfolio/pms/statistics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
309
309
|
wbportfolio/pms/trading/__init__.py,sha256=R_yLKc54sCak8A1cW0O1Aszrcv5KV8mC_3h17Hr20e4,36
|
|
310
|
-
wbportfolio/pms/trading/handler.py,sha256=
|
|
310
|
+
wbportfolio/pms/trading/handler.py,sha256=o31jtevfnSSv0aSzAsnthz2luM7F-D5d061LaqfOHUw,8542
|
|
311
311
|
wbportfolio/rebalancing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
312
312
|
wbportfolio/rebalancing/base.py,sha256=wpeoxdkLz5osxm5mRjkOoML7YkYvwuAlqSLLtHBbWp8,984
|
|
313
313
|
wbportfolio/rebalancing/decorators.py,sha256=162ZmXV2YQGI830LWvEnJ95RexMzHvaCGfcVOnXTOXM,502
|
|
@@ -360,7 +360,7 @@ wbportfolio/serializers/registers.py,sha256=zhdKH_mHEBE0VOhm6xpY54bTMtcSaY5BskEa
|
|
|
360
360
|
wbportfolio/serializers/roles.py,sha256=T-9NqTldpvaEMFy-Bib5MB6MeboygEOqcMP61mzzD3Q,2146
|
|
361
361
|
wbportfolio/serializers/signals.py,sha256=hD6R4oFtwhvnsJPteytPKy2JwEelmxrapdfoLSnluaE,7053
|
|
362
362
|
wbportfolio/serializers/orders/__init__.py,sha256=PKJRksA1pWsh8nVfGASoB0m3LyUzVRnq1m9VPp90J7k,271
|
|
363
|
-
wbportfolio/serializers/orders/order_proposals.py,sha256=
|
|
363
|
+
wbportfolio/serializers/orders/order_proposals.py,sha256=FegiVe1d1AZoLtOuNOUfLN7HwGy4mKGrdYHKDwuUjPY,4430
|
|
364
364
|
wbportfolio/serializers/orders/orders.py,sha256=pAKjJLRANOo1iMlcv18twuQ0aAVDVKYt-pPx6avnWRQ,7464
|
|
365
365
|
wbportfolio/serializers/transactions/__init__.py,sha256=-7Pan4n7YI3iDvGXff6okzk4ycEURRxp5n_SHCY_g_I,493
|
|
366
366
|
wbportfolio/serializers/transactions/claim.py,sha256=kC4E2RZRrpd9i8tGfoiV-gpWDk3ikR5F1Wf0v_IGIvw,11599
|
|
@@ -403,7 +403,7 @@ wbportfolio/tests/models/test_roles.py,sha256=4Cn7WyrA2ztJNeWLk5cy9kYo5XLWMbFSvo
|
|
|
403
403
|
wbportfolio/tests/models/test_splits.py,sha256=ytKcHsI_90kj1L4s8It-KEcc24rkDcElxwQ8q0QxEvk,9689
|
|
404
404
|
wbportfolio/tests/models/utils.py,sha256=ORNJq6NMo1Za22jGZXfTfKeNEnTRlfEt_8SJ6xLaQWg,325
|
|
405
405
|
wbportfolio/tests/models/orders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
406
|
-
wbportfolio/tests/models/orders/test_order_proposals.py,sha256=
|
|
406
|
+
wbportfolio/tests/models/orders/test_order_proposals.py,sha256=7OvZn9IhTNQ66lm2nLXhrdc9OzGuklrnbRgp2WeYQms,30802
|
|
407
407
|
wbportfolio/tests/models/transactions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
408
408
|
wbportfolio/tests/models/transactions/test_claim.py,sha256=NG3BKB-FVcIDgHSJHCjImxgMM3ISVUMl24xUPmEcPec,5570
|
|
409
409
|
wbportfolio/tests/models/transactions/test_fees.py,sha256=tAp18x2wCNQr11LUnLtHNbBDbbX0v1DZnmW7i-cEi5Q,2423
|
|
@@ -532,7 +532,7 @@ wbportfolio/viewsets/configs/titles/roles.py,sha256=9LoJa3jgenXJ5UWRlIErTzdbjpSW
|
|
|
532
532
|
wbportfolio/viewsets/configs/titles/trades.py,sha256=29XCLxvY0Xe3a2tjCno3tN2rRXCr9RWpbWnzurJfnYI,1986
|
|
533
533
|
wbportfolio/viewsets/orders/__init__.py,sha256=N8v9jdEXryOzrLlc7ML3iBCO2lmNXph9_TWoQ7PTvi4,195
|
|
534
534
|
wbportfolio/viewsets/orders/order_proposals.py,sha256=mw385zzU52nCq8p6xgionh43xLmOn5aX-2BPlCHnqlE,6214
|
|
535
|
-
wbportfolio/viewsets/orders/orders.py,sha256=
|
|
535
|
+
wbportfolio/viewsets/orders/orders.py,sha256=mekuUVkG9-tEtWGrIW2QfgTOuwOZWONiRnbztYUmcVM,10875
|
|
536
536
|
wbportfolio/viewsets/orders/configs/__init__.py,sha256=5MU57JXiKi32_PicHtiNr7YHmMN020FrlF5NFJf_Wds,94
|
|
537
537
|
wbportfolio/viewsets/orders/configs/buttons/__init__.py,sha256=EHzNmAfa0UQFITEF-wxj_s4wn3Y5DE3DCbEUmmvCTIs,106
|
|
538
538
|
wbportfolio/viewsets/orders/configs/buttons/order_proposals.py,sha256=Q_7LrsuLzjSXCIoscFQXMMHl8cuCporDNM5k735W7d8,3584
|
|
@@ -550,7 +550,7 @@ wbportfolio/viewsets/transactions/claim.py,sha256=Pb1WftoO-w-ZSTbLRhmQubhy7hgd68
|
|
|
550
550
|
wbportfolio/viewsets/transactions/fees.py,sha256=WT2bWWfgozz4_rpyTKX7dgBBTXD-gu0nlsd2Nk2Zh1Q,7028
|
|
551
551
|
wbportfolio/viewsets/transactions/mixins.py,sha256=WipvJoi5hylkpD0y9VATe30WAcwIHUIroVkK10FYw7k,636
|
|
552
552
|
wbportfolio/viewsets/transactions/trades.py,sha256=xBgOGaJ8aEg-2RxEJ4FDaBs4SGwuLasun3nhpis0WQY,12363
|
|
553
|
-
wbportfolio-1.54.
|
|
554
|
-
wbportfolio-1.54.
|
|
555
|
-
wbportfolio-1.54.
|
|
556
|
-
wbportfolio-1.54.
|
|
553
|
+
wbportfolio-1.54.17.dist-info/METADATA,sha256=iv55pLD6PfL1brFSH400eFZJ1u_BieHNt_tO2nrQ3Us,703
|
|
554
|
+
wbportfolio-1.54.17.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
|
|
555
|
+
wbportfolio-1.54.17.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
|
|
556
|
+
wbportfolio-1.54.17.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|