wbportfolio 1.54.16__py2.py3-none-any.whl → 1.54.18__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/import_export/handlers/orders.py +59 -0
- wbportfolio/import_export/handlers/trade.py +42 -70
- wbportfolio/models/orders/order_proposals.py +30 -29
- wbportfolio/models/orders/orders.py +2 -7
- wbportfolio/tests/models/orders/test_order_proposals.py +36 -2
- wbportfolio/viewsets/orders/orders.py +21 -17
- {wbportfolio-1.54.16.dist-info → wbportfolio-1.54.18.dist-info}/METADATA +1 -1
- {wbportfolio-1.54.16.dist-info → wbportfolio-1.54.18.dist-info}/RECORD +10 -9
- {wbportfolio-1.54.16.dist-info → wbportfolio-1.54.18.dist-info}/WHEEL +0 -0
- {wbportfolio-1.54.16.dist-info → wbportfolio-1.54.18.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from decimal import Decimal
|
|
2
|
+
from typing import Any, Dict
|
|
3
|
+
|
|
4
|
+
from django.db import models
|
|
5
|
+
from wbcore.contrib.io.exceptions import DeserializationError
|
|
6
|
+
from wbcore.contrib.io.imports import ImportExportHandler, ImportState
|
|
7
|
+
from wbcore.contrib.io.utils import nest_row
|
|
8
|
+
from wbfdm.import_export.handlers.instrument import InstrumentImportHandler
|
|
9
|
+
|
|
10
|
+
from wbportfolio.pms.typing import Portfolio, Position
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OrderImportHandler(ImportExportHandler):
|
|
14
|
+
MODEL_APP_LABEL: str = "wbportfolio.Order"
|
|
15
|
+
|
|
16
|
+
def __init__(self, *args, **kwargs):
|
|
17
|
+
super().__init__(*args, **kwargs)
|
|
18
|
+
self.instrument_handler = InstrumentImportHandler(self.import_source)
|
|
19
|
+
self.order_proposal = None
|
|
20
|
+
|
|
21
|
+
def process_object(
|
|
22
|
+
self,
|
|
23
|
+
data: Dict[str, Any],
|
|
24
|
+
**kwargs,
|
|
25
|
+
):
|
|
26
|
+
from wbportfolio.models import OrderProposal
|
|
27
|
+
|
|
28
|
+
data = nest_row(data)
|
|
29
|
+
underlying_instrument = self.instrument_handler.process_object(
|
|
30
|
+
data["underlying_instrument"], only_security=False, read_only=True
|
|
31
|
+
)[0]
|
|
32
|
+
self.order_proposal = OrderProposal.objects.get(id=data.pop("order_proposal_id"))
|
|
33
|
+
weighting = data.get("target_weight", data.get("weighting"))
|
|
34
|
+
shares = data.get("target_shares", data.get("shares", 0))
|
|
35
|
+
if not weighting:
|
|
36
|
+
raise DeserializationError("We couldn't figure out the target weight column")
|
|
37
|
+
position_dto = Position(
|
|
38
|
+
underlying_instrument=underlying_instrument.id,
|
|
39
|
+
instrument_type=underlying_instrument.instrument_type.id,
|
|
40
|
+
weighting=Decimal(weighting),
|
|
41
|
+
shares=Decimal(shares),
|
|
42
|
+
currency=underlying_instrument.currency,
|
|
43
|
+
date=self.order_proposal.trade_date,
|
|
44
|
+
is_cash=underlying_instrument.is_cash,
|
|
45
|
+
)
|
|
46
|
+
return position_dto, ImportState.CREATED
|
|
47
|
+
|
|
48
|
+
def _get_history(self, history: Dict[str, Any]) -> models.QuerySet:
|
|
49
|
+
from wbportfolio.models.orders.order_proposals import OrderProposal
|
|
50
|
+
|
|
51
|
+
if order_proposal_id := history.get("order_proposal_id"):
|
|
52
|
+
# if a order proposal is provided, we delete the existing history first as otherwise, it would mess with the target weight computation
|
|
53
|
+
order_proposal = OrderProposal.objects.get(id=order_proposal_id)
|
|
54
|
+
order_proposal.orders.all().delete()
|
|
55
|
+
return self.model.objects.none()
|
|
56
|
+
|
|
57
|
+
def _post_processing_objects(self, positions: list[Position], *args, **kwargs):
|
|
58
|
+
target_portfolio = Portfolio(positions)
|
|
59
|
+
self.order_proposal.reset_orders(target_portfolio=target_portfolio)
|
|
@@ -25,7 +25,6 @@ class TradeImportHandler(ImportExportHandler):
|
|
|
25
25
|
self.instrument_handler = InstrumentImportHandler(self.import_source)
|
|
26
26
|
self.register_handler = RegisterImportHandler(self.import_source)
|
|
27
27
|
self.currency_handler = CurrencyImportHandler(self.import_source)
|
|
28
|
-
self.order_proposals = set()
|
|
29
28
|
|
|
30
29
|
def _data_changed(self, _object, change_data: Dict[str, Any], initial_data: Dict[str, Any], **kwargs):
|
|
31
30
|
if (new_register := change_data.get("register")) and (current_register := _object.register):
|
|
@@ -35,41 +34,32 @@ class TradeImportHandler(ImportExportHandler):
|
|
|
35
34
|
return super()._data_changed(_object, change_data, initial_data, **kwargs)
|
|
36
35
|
|
|
37
36
|
def _deserialize(self, data: Dict[str, Any]):
|
|
38
|
-
from wbportfolio.models import
|
|
37
|
+
from wbportfolio.models import Product
|
|
39
38
|
|
|
40
39
|
if underlying_instrument := data.get("underlying_instrument", None):
|
|
41
40
|
data["underlying_instrument"] = self.instrument_handler.process_object(
|
|
42
41
|
underlying_instrument, only_security=False, read_only=True
|
|
43
42
|
)[0]
|
|
44
43
|
|
|
45
|
-
if
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
data["
|
|
49
|
-
|
|
50
|
-
data["
|
|
51
|
-
|
|
52
|
-
data["
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if transaction_date_str := data.get("transaction_date", None):
|
|
57
|
-
data["transaction_date"] = datetime.strptime(transaction_date_str, "%Y-%m-%d").date()
|
|
58
|
-
if value_date_str := data.get("value_date", None):
|
|
59
|
-
data["value_date"] = datetime.strptime(value_date_str, "%Y-%m-%d").date()
|
|
60
|
-
if book_date_str := data.get("book_date", None):
|
|
61
|
-
data["book_date"] = datetime.strptime(book_date_str, "%Y-%m-%d").date()
|
|
62
|
-
data["portfolio"] = Portfolio._get_or_create_portfolio(
|
|
63
|
-
self.instrument_handler, data.get("portfolio", data["underlying_instrument"])
|
|
64
|
-
)
|
|
44
|
+
if external_id_alternative := data.get("external_id_alternative", None):
|
|
45
|
+
data["external_id_alternative"] = str(external_id_alternative)
|
|
46
|
+
if transaction_date_str := data.get("transaction_date", None):
|
|
47
|
+
data["transaction_date"] = datetime.strptime(transaction_date_str, "%Y-%m-%d").date()
|
|
48
|
+
if value_date_str := data.get("value_date", None):
|
|
49
|
+
data["value_date"] = datetime.strptime(value_date_str, "%Y-%m-%d").date()
|
|
50
|
+
if book_date_str := data.get("book_date", None):
|
|
51
|
+
data["book_date"] = datetime.strptime(book_date_str, "%Y-%m-%d").date()
|
|
52
|
+
data["portfolio"] = Portfolio._get_or_create_portfolio(
|
|
53
|
+
self.instrument_handler, data.get("portfolio", data["underlying_instrument"])
|
|
54
|
+
)
|
|
65
55
|
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
if currency_data := data.get("currency", None):
|
|
57
|
+
data["currency"] = self.currency_handler.process_object(currency_data, read_only=True)[0]
|
|
68
58
|
|
|
69
|
-
|
|
70
|
-
|
|
59
|
+
if register_data := data.get("register", None):
|
|
60
|
+
data["register"] = self.register_handler.process_object(register_data)[0]
|
|
71
61
|
|
|
72
|
-
|
|
62
|
+
data["marked_for_deletion"] = data.get("marked_for_deletion", False)
|
|
73
63
|
if underlying_instrument := data.get("underlying_instrument"):
|
|
74
64
|
if nominal := data.pop("nominal", None):
|
|
75
65
|
try:
|
|
@@ -160,39 +150,30 @@ class TradeImportHandler(ImportExportHandler):
|
|
|
160
150
|
self.import_source.log += "\nNo trade was successfully matched."
|
|
161
151
|
|
|
162
152
|
def _get_history(self, history: Dict[str, Any]) -> models.QuerySet:
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
trades =
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if transaction_date := history.get("transaction_date"):
|
|
181
|
-
trades = trades.filter(transaction_date__lte=transaction_date)
|
|
182
|
-
elif book_date := history.get("book_date"):
|
|
183
|
-
trades = trades.filter(book_date__lte=book_date)
|
|
184
|
-
if underlying_instrument_data := history.get("underlying_instrument"):
|
|
185
|
-
if isinstance(underlying_instrument_data, dict):
|
|
186
|
-
trades = trades.filter(
|
|
187
|
-
**{f"underlying_instrument__{k}": v for k, v in underlying_instrument_data.items()}
|
|
188
|
-
)
|
|
189
|
-
else:
|
|
190
|
-
trades = trades.filter(underlying_instrument__id=underlying_instrument_data)
|
|
191
|
-
|
|
192
|
-
elif "underlying_instruments" in history:
|
|
193
|
-
trades = trades.filter(underlying_instrument__id__in=history["underlying_instruments"])
|
|
153
|
+
trades = self.model.objects.filter(
|
|
154
|
+
exclude_from_history=False,
|
|
155
|
+
pending=False,
|
|
156
|
+
transaction_subtype__in=[
|
|
157
|
+
self.model.Type.SUBSCRIPTION,
|
|
158
|
+
self.model.Type.REDEMPTION,
|
|
159
|
+
], # we cannot exclude marked for deleted trade because otherwise they are never consider in the history
|
|
160
|
+
)
|
|
161
|
+
if transaction_date := history.get("transaction_date"):
|
|
162
|
+
trades = trades.filter(transaction_date__lte=transaction_date)
|
|
163
|
+
elif book_date := history.get("book_date"):
|
|
164
|
+
trades = trades.filter(book_date__lte=book_date)
|
|
165
|
+
if underlying_instrument_data := history.get("underlying_instrument"):
|
|
166
|
+
if isinstance(underlying_instrument_data, dict):
|
|
167
|
+
trades = trades.filter(
|
|
168
|
+
**{f"underlying_instrument__{k}": v for k, v in underlying_instrument_data.items()}
|
|
169
|
+
)
|
|
194
170
|
else:
|
|
195
|
-
|
|
171
|
+
trades = trades.filter(underlying_instrument__id=underlying_instrument_data)
|
|
172
|
+
|
|
173
|
+
elif "underlying_instruments" in history:
|
|
174
|
+
trades = trades.filter(underlying_instrument__id__in=history["underlying_instruments"])
|
|
175
|
+
else:
|
|
176
|
+
raise ValueError("We cannot estimate history without at least the underlying instrument")
|
|
196
177
|
return trades
|
|
197
178
|
|
|
198
179
|
def _post_processing_objects(
|
|
@@ -201,18 +182,12 @@ class TradeImportHandler(ImportExportHandler):
|
|
|
201
182
|
modified_objs: list[models.Model],
|
|
202
183
|
unmodified_objs: list[models.Model],
|
|
203
184
|
):
|
|
204
|
-
from wbportfolio.models.orders.order_proposals import replay_as_task
|
|
205
|
-
|
|
206
185
|
for instrument in set(
|
|
207
186
|
map(lambda x: x.underlying_instrument, filter(lambda t: t.is_customer_trade, created_objs + modified_objs))
|
|
208
187
|
):
|
|
209
188
|
if instrument.instrument_type.key == "product":
|
|
210
189
|
update_outstanding_shares_as_task.delay(instrument.id)
|
|
211
190
|
|
|
212
|
-
# if the trade import relates to a order proposal, we reset the TP after the import to ensure it contains the deleted positions (often forgotten by user)
|
|
213
|
-
for changed_order_proposal in self.order_proposals:
|
|
214
|
-
replay_as_task.delay(changed_order_proposal.id)
|
|
215
|
-
|
|
216
191
|
def _post_processing_updated_object(self, _object):
|
|
217
192
|
if _object.marked_for_deletion:
|
|
218
193
|
_object.marked_for_deletion = False
|
|
@@ -231,8 +206,5 @@ class TradeImportHandler(ImportExportHandler):
|
|
|
231
206
|
self.import_source.log += (
|
|
232
207
|
f"{trade.transaction_date:%d.%m.%Y}: {trade.shares} {trade.bank} ==> Marked for deletion"
|
|
233
208
|
)
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
else:
|
|
237
|
-
trade.marked_for_deletion = True
|
|
238
|
-
trade.save()
|
|
209
|
+
trade.marked_for_deletion = True
|
|
210
|
+
trade.save()
|
|
@@ -394,35 +394,34 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
394
394
|
orders = service.trades_batch.trades_map.values()
|
|
395
395
|
for order_dto in orders:
|
|
396
396
|
instrument = Instrument.objects.get(id=order_dto.underlying_instrument)
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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,
|
|
400
416
|
)
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
except Order.DoesNotExist:
|
|
410
|
-
order = Order(
|
|
411
|
-
underlying_instrument=instrument,
|
|
412
|
-
order_proposal=self,
|
|
413
|
-
value_date=self.trade_date,
|
|
414
|
-
weighting=weighting,
|
|
415
|
-
daily_return=daily_return,
|
|
416
|
-
currency_fx_rate=currency_fx_rate,
|
|
417
|
-
)
|
|
418
|
-
order.price = order.get_price()
|
|
419
|
-
order.order_type = Order.get_type(weighting, order_dto.previous_weight, order_dto.target_weight)
|
|
420
|
-
# if we cannot automatically find a price, we consider the stock is invalid and we sell it
|
|
421
|
-
if not order.price:
|
|
422
|
-
order.price = Decimal("0.0")
|
|
423
|
-
order.weighting = -order_dto.effective_weight
|
|
424
|
-
|
|
425
|
-
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()
|
|
426
425
|
# final sanity check to make sure invalid order with effective and target weight of 0 are automatically removed:
|
|
427
426
|
self.get_orders().filter(target_weight=0, effective_weight=0).delete()
|
|
428
427
|
|
|
@@ -579,7 +578,9 @@ class OrderProposal(CloneMixin, RiskCheckMixin, WBModel):
|
|
|
579
578
|
currency = self.portfolio.currency
|
|
580
579
|
|
|
581
580
|
# Calculate the total target weight of all orders
|
|
582
|
-
total_target_weight = 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
|
if target_cash_weight is None:
|
|
585
586
|
target_cash_weight = Decimal("1") - total_target_weight
|
|
@@ -9,13 +9,13 @@ from django.db.models import (
|
|
|
9
9
|
from ordered_model.models import OrderedModel
|
|
10
10
|
from wbcore.contrib.io.mixins import ImportMixin
|
|
11
11
|
|
|
12
|
-
from wbportfolio.import_export.handlers.
|
|
12
|
+
from wbportfolio.import_export.handlers.orders import OrderImportHandler
|
|
13
13
|
from wbportfolio.models.asset import AssetPosition
|
|
14
14
|
from wbportfolio.models.transactions.transactions import TransactionMixin
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class Order(TransactionMixin, ImportMixin, OrderedModel, models.Model):
|
|
18
|
-
import_export_handler_class =
|
|
18
|
+
import_export_handler_class = OrderImportHandler
|
|
19
19
|
|
|
20
20
|
ORDER_WEIGHTING_PRECISION = (
|
|
21
21
|
8 # we need to match the asset position weighting. Skfolio advices using a even smaller number (5)
|
|
@@ -145,11 +145,6 @@ class Order(TransactionMixin, ImportMixin, OrderedModel, models.Model):
|
|
|
145
145
|
self, "target_weight", round(self._effective_weight + self.weighting, self.ORDER_WEIGHTING_PRECISION)
|
|
146
146
|
)
|
|
147
147
|
|
|
148
|
-
@_target_weight.setter
|
|
149
|
-
def _target_weight(self, target_weight):
|
|
150
|
-
self.weighting = Decimal(target_weight) - self._effective_weight
|
|
151
|
-
self.order_type = self.get_type(self.weighting, self._previous_weight, self._target_weight)
|
|
152
|
-
|
|
153
148
|
@property
|
|
154
149
|
@admin.display(description="Target Shares")
|
|
155
150
|
def _target_shares(self) -> Decimal:
|
|
@@ -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))
|
|
@@ -423,13 +450,20 @@ class TestOrderProposal:
|
|
|
423
450
|
order_proposal.portfolio.only_weighting = False
|
|
424
451
|
order_proposal.portfolio.save()
|
|
425
452
|
mock_fct.return_value = Decimal(1_000_000) # 1 million cash
|
|
453
|
+
cash = cash_factory.create(currency=order_proposal.portfolio.currency)
|
|
426
454
|
order_factory.create( # equity trade
|
|
427
455
|
order_proposal=order_proposal,
|
|
428
456
|
value_date=order_proposal.trade_date,
|
|
429
457
|
portfolio=order_proposal.portfolio,
|
|
430
458
|
weighting=Decimal("0.7"),
|
|
431
459
|
)
|
|
432
|
-
|
|
460
|
+
order_factory.create( # cash trade
|
|
461
|
+
order_proposal=order_proposal,
|
|
462
|
+
value_date=order_proposal.trade_date,
|
|
463
|
+
portfolio=order_proposal.portfolio,
|
|
464
|
+
underlying_instrument=cash,
|
|
465
|
+
weighting=Decimal("0.2"),
|
|
466
|
+
)
|
|
433
467
|
target_cash_position = order_proposal.get_estimated_target_cash()
|
|
434
468
|
assert target_cash_position.weighting == Decimal("0.3")
|
|
435
469
|
assert target_cash_position.initial_shares == Decimal(1_000_000) * Decimal("0.3")
|
|
@@ -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
|
)
|
|
@@ -119,9 +119,10 @@ wbportfolio/import_export/handlers/adjustment.py,sha256=6bdTIYFmc8_HFxcdwtnYwglM
|
|
|
119
119
|
wbportfolio/import_export/handlers/asset_position.py,sha256=UZBDlEK5mxtGu9cZvwiQS6J8GNkMJqDZb7KZ3uwex0g,8698
|
|
120
120
|
wbportfolio/import_export/handlers/dividend.py,sha256=F0oLfNt2B_QQAjHBCRpxa5HSkfkAYdal_NjLJGtVckY,4408
|
|
121
121
|
wbportfolio/import_export/handlers/fees.py,sha256=BOFHAvSTlvVLaxnm6KD_fcza1TlPc02HOR9J0_jjswI,2495
|
|
122
|
+
wbportfolio/import_export/handlers/orders.py,sha256=gECIGItR63oNOWXKgImdjJv6drGgzJOcJG0bu5vygr4,2565
|
|
122
123
|
wbportfolio/import_export/handlers/portfolio_cash_flow.py,sha256=W7QPNqEvvsq0RS016EAFBp1ezvc6G9Rk-hviRZh8o6Y,2737
|
|
123
124
|
wbportfolio/import_export/handlers/register.py,sha256=sYyXkE8b1DPZ5monxylZn0kjxLVdNYYZR-p61dwEoDM,2271
|
|
124
|
-
wbportfolio/import_export/handlers/trade.py,sha256=
|
|
125
|
+
wbportfolio/import_export/handlers/trade.py,sha256=g2jAYYeuhZv_DuvM6zlROcq6rUlSbGaQ3tO4u6wkSRU,11140
|
|
125
126
|
wbportfolio/import_export/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
126
127
|
wbportfolio/import_export/parsers/default_mapping.py,sha256=KrO-X5CvQCeQoBYzFDxavoQGriyUSeI2QDx5ar_zo7A,1405
|
|
127
128
|
wbportfolio/import_export/parsers/jpmorgan/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -288,8 +289,8 @@ wbportfolio/models/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
|
288
289
|
wbportfolio/models/mixins/instruments.py,sha256=SgBreTpa_X3uyCWo7t8B0VaTtl49IjmBMe4Pab6TjAM,6796
|
|
289
290
|
wbportfolio/models/mixins/liquidity_stress_test.py,sha256=iQVzT3QM7VtHnqfj9gT6KUIe4wC4MJXery-AXJHUYns,58820
|
|
290
291
|
wbportfolio/models/orders/__init__.py,sha256=EH9UacGR3npBMje5FGTeLOh1xqFBh9kc24WbGmBIA3g,69
|
|
291
|
-
wbportfolio/models/orders/order_proposals.py,sha256=
|
|
292
|
-
wbportfolio/models/orders/orders.py,sha256=
|
|
292
|
+
wbportfolio/models/orders/order_proposals.py,sha256=U1vLwI_yS2E1TUs04NJGperusqmNyiE42XGlAVoPatk,40196
|
|
293
|
+
wbportfolio/models/orders/orders.py,sha256=Rl54ON-b8K277rKeXf53-PHn98nQFYBl4Q_I0Bo6WqE,9438
|
|
293
294
|
wbportfolio/models/reconciliations/__init__.py,sha256=MXH5fZIPGDRBgJkO6wVu_NLRs8fkP1im7G6d-h36lQY,127
|
|
294
295
|
wbportfolio/models/reconciliations/account_reconciliation_lines.py,sha256=QP6M7hMcyFbuXBa55Y-azui6Dl_WgbzMntEqWzQkbfM,7394
|
|
295
296
|
wbportfolio/models/reconciliations/account_reconciliations.py,sha256=rofSxetFfEJov6mPyoTvGxELA16HILyJZtQvm_kwYU0,4405
|
|
@@ -403,7 +404,7 @@ wbportfolio/tests/models/test_roles.py,sha256=4Cn7WyrA2ztJNeWLk5cy9kYo5XLWMbFSvo
|
|
|
403
404
|
wbportfolio/tests/models/test_splits.py,sha256=ytKcHsI_90kj1L4s8It-KEcc24rkDcElxwQ8q0QxEvk,9689
|
|
404
405
|
wbportfolio/tests/models/utils.py,sha256=ORNJq6NMo1Za22jGZXfTfKeNEnTRlfEt_8SJ6xLaQWg,325
|
|
405
406
|
wbportfolio/tests/models/orders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
406
|
-
wbportfolio/tests/models/orders/test_order_proposals.py,sha256=
|
|
407
|
+
wbportfolio/tests/models/orders/test_order_proposals.py,sha256=7OvZn9IhTNQ66lm2nLXhrdc9OzGuklrnbRgp2WeYQms,30802
|
|
407
408
|
wbportfolio/tests/models/transactions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
408
409
|
wbportfolio/tests/models/transactions/test_claim.py,sha256=NG3BKB-FVcIDgHSJHCjImxgMM3ISVUMl24xUPmEcPec,5570
|
|
409
410
|
wbportfolio/tests/models/transactions/test_fees.py,sha256=tAp18x2wCNQr11LUnLtHNbBDbbX0v1DZnmW7i-cEi5Q,2423
|
|
@@ -532,7 +533,7 @@ wbportfolio/viewsets/configs/titles/roles.py,sha256=9LoJa3jgenXJ5UWRlIErTzdbjpSW
|
|
|
532
533
|
wbportfolio/viewsets/configs/titles/trades.py,sha256=29XCLxvY0Xe3a2tjCno3tN2rRXCr9RWpbWnzurJfnYI,1986
|
|
533
534
|
wbportfolio/viewsets/orders/__init__.py,sha256=N8v9jdEXryOzrLlc7ML3iBCO2lmNXph9_TWoQ7PTvi4,195
|
|
534
535
|
wbportfolio/viewsets/orders/order_proposals.py,sha256=mw385zzU52nCq8p6xgionh43xLmOn5aX-2BPlCHnqlE,6214
|
|
535
|
-
wbportfolio/viewsets/orders/orders.py,sha256=
|
|
536
|
+
wbportfolio/viewsets/orders/orders.py,sha256=mekuUVkG9-tEtWGrIW2QfgTOuwOZWONiRnbztYUmcVM,10875
|
|
536
537
|
wbportfolio/viewsets/orders/configs/__init__.py,sha256=5MU57JXiKi32_PicHtiNr7YHmMN020FrlF5NFJf_Wds,94
|
|
537
538
|
wbportfolio/viewsets/orders/configs/buttons/__init__.py,sha256=EHzNmAfa0UQFITEF-wxj_s4wn3Y5DE3DCbEUmmvCTIs,106
|
|
538
539
|
wbportfolio/viewsets/orders/configs/buttons/order_proposals.py,sha256=Q_7LrsuLzjSXCIoscFQXMMHl8cuCporDNM5k735W7d8,3584
|
|
@@ -550,7 +551,7 @@ wbportfolio/viewsets/transactions/claim.py,sha256=Pb1WftoO-w-ZSTbLRhmQubhy7hgd68
|
|
|
550
551
|
wbportfolio/viewsets/transactions/fees.py,sha256=WT2bWWfgozz4_rpyTKX7dgBBTXD-gu0nlsd2Nk2Zh1Q,7028
|
|
551
552
|
wbportfolio/viewsets/transactions/mixins.py,sha256=WipvJoi5hylkpD0y9VATe30WAcwIHUIroVkK10FYw7k,636
|
|
552
553
|
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.
|
|
554
|
+
wbportfolio-1.54.18.dist-info/METADATA,sha256=az3x7f9w0PGxU-naki13snEuZMzaKqZhC6Dv82qwcJE,703
|
|
555
|
+
wbportfolio-1.54.18.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
|
|
556
|
+
wbportfolio-1.54.18.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
|
|
557
|
+
wbportfolio-1.54.18.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|