wbportfolio 1.46.5__py2.py3-none-any.whl → 1.46.7__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/trade.py +3 -1
- wbportfolio/models/transactions/trade_proposals.py +10 -3
- wbportfolio/models/transactions/trades.py +10 -5
- wbportfolio/pms/typing.py +6 -7
- wbportfolio/rebalancing/models/market_capitalization_weighted.py +11 -14
- wbportfolio/serializers/transactions/trade_proposals.py +2 -0
- wbportfolio/tests/rebalancing/test_models.py +7 -8
- wbportfolio/viewsets/assets.py +1 -1
- wbportfolio/viewsets/configs/buttons/__init__.py +1 -2
- wbportfolio/viewsets/configs/buttons/trade_proposals.py +53 -12
- wbportfolio/viewsets/configs/buttons/trades.py +0 -85
- wbportfolio/viewsets/transactions/trade_proposals.py +5 -0
- wbportfolio/viewsets/transactions/trades.py +0 -2
- {wbportfolio-1.46.5.dist-info → wbportfolio-1.46.7.dist-info}/METADATA +1 -1
- {wbportfolio-1.46.5.dist-info → wbportfolio-1.46.7.dist-info}/RECORD +17 -17
- {wbportfolio-1.46.5.dist-info → wbportfolio-1.46.7.dist-info}/WHEEL +0 -0
- {wbportfolio-1.46.5.dist-info → wbportfolio-1.46.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -144,7 +144,9 @@ class TradeImportHandler(ImportExportHandler):
|
|
|
144
144
|
|
|
145
145
|
def _get_history(self, history: Dict[str, Any]) -> models.QuerySet:
|
|
146
146
|
if trade_proposal_id := history.get("trade_proposal_id"):
|
|
147
|
-
|
|
147
|
+
# if a trade proposal is provided, we delete the existing history first as otherwise, it would mess with the target weight computation
|
|
148
|
+
self.model.objects.filter(trade_proposal_id=trade_proposal_id).delete()
|
|
149
|
+
trades = self.model.objects.none()
|
|
148
150
|
else:
|
|
149
151
|
trades = self.model.objects.filter(
|
|
150
152
|
exclude_from_history=False,
|
|
@@ -184,14 +184,21 @@ class TradeProposal(RiskCheckMixin, WBModel):
|
|
|
184
184
|
service = TradingService(self.trade_date, trades_batch=self._build_dto())
|
|
185
185
|
service.normalize()
|
|
186
186
|
leftovers_trades = self.trades.all()
|
|
187
|
-
|
|
187
|
+
total_target_weight = Decimal("0.0")
|
|
188
|
+
for underlying_instrument_id, trade_dto in service.trades_batch.trades_map.items():
|
|
188
189
|
with suppress(Trade.DoesNotExist):
|
|
189
|
-
trade =
|
|
190
|
-
trade.weighting =
|
|
190
|
+
trade = self.trades.get(underlying_instrument_id=underlying_instrument_id)
|
|
191
|
+
trade.weighting = round(trade_dto.delta_weight, 6)
|
|
191
192
|
trade.shares = self.estimate_shares(trade)
|
|
192
193
|
trade.save()
|
|
194
|
+
total_target_weight += trade._target_weight
|
|
193
195
|
leftovers_trades = leftovers_trades.exclude(id=trade.id)
|
|
194
196
|
leftovers_trades.delete()
|
|
197
|
+
# we handle quantization error due to the decimal max digits. In that case, we take the biggest trade (highest weight) and we remove the quantization error
|
|
198
|
+
if quantize_error := (total_target_weight - Decimal("1.0")):
|
|
199
|
+
biggest_trade = self.trades.latest("weighting")
|
|
200
|
+
biggest_trade.weighting -= quantize_error
|
|
201
|
+
biggest_trade.save()
|
|
195
202
|
|
|
196
203
|
def _get_target_portfolio(self, **kwargs) -> PortfolioDTO:
|
|
197
204
|
if self.rebalancing_model:
|
|
@@ -406,7 +406,7 @@ class Trade(ShareMixin, Transaction, OrderedModel, WBModel):
|
|
|
406
406
|
except Product.DoesNotExist:
|
|
407
407
|
return None
|
|
408
408
|
|
|
409
|
-
@
|
|
409
|
+
@property
|
|
410
410
|
@admin.display(description="Last Effective Date")
|
|
411
411
|
def _last_effective_date(self) -> date:
|
|
412
412
|
if hasattr(self, "last_effective_date"):
|
|
@@ -420,7 +420,7 @@ class Trade(ShareMixin, Transaction, OrderedModel, WBModel):
|
|
|
420
420
|
).exists():
|
|
421
421
|
return assets.latest("date").date
|
|
422
422
|
|
|
423
|
-
@
|
|
423
|
+
@property
|
|
424
424
|
@admin.display(description="Effective Weight")
|
|
425
425
|
def _effective_weight(self) -> Decimal:
|
|
426
426
|
return getattr(
|
|
@@ -434,7 +434,7 @@ class Trade(ShareMixin, Transaction, OrderedModel, WBModel):
|
|
|
434
434
|
or Decimal(0),
|
|
435
435
|
)
|
|
436
436
|
|
|
437
|
-
@
|
|
437
|
+
@property
|
|
438
438
|
@admin.display(description="Effective Shares")
|
|
439
439
|
def _effective_shares(self) -> Decimal:
|
|
440
440
|
return getattr(
|
|
@@ -448,12 +448,12 @@ class Trade(ShareMixin, Transaction, OrderedModel, WBModel):
|
|
|
448
448
|
or Decimal(0),
|
|
449
449
|
)
|
|
450
450
|
|
|
451
|
-
@
|
|
451
|
+
@property
|
|
452
452
|
@admin.display(description="Target Weight")
|
|
453
453
|
def _target_weight(self) -> Decimal:
|
|
454
454
|
return getattr(self, "target_weight", self._effective_weight + self.weighting)
|
|
455
455
|
|
|
456
|
-
@
|
|
456
|
+
@property
|
|
457
457
|
@admin.display(description="Target Shares")
|
|
458
458
|
def _target_shares(self) -> Decimal:
|
|
459
459
|
return getattr(self, "target_shares", self._effective_shares + self.shares)
|
|
@@ -483,6 +483,11 @@ class Trade(ShareMixin, Transaction, OrderedModel, WBModel):
|
|
|
483
483
|
]
|
|
484
484
|
# notification_email_template = "portfolio/email/trade_notification.html"
|
|
485
485
|
|
|
486
|
+
def __init__(self, *args, target_weight: Decimal | None = None, **kwargs):
|
|
487
|
+
super().__init__(*args, **kwargs)
|
|
488
|
+
if target_weight is not None: # if target weight is provided, we guess the corresponding weighting
|
|
489
|
+
self.weighting = Decimal(target_weight) - self._effective_weight
|
|
490
|
+
|
|
486
491
|
def save(self, *args, **kwargs):
|
|
487
492
|
if self.trade_proposal:
|
|
488
493
|
self.portfolio = self.trade_proposal.portfolio
|
wbportfolio/pms/typing.py
CHANGED
|
@@ -4,7 +4,6 @@ from decimal import Decimal
|
|
|
4
4
|
|
|
5
5
|
import pandas as pd
|
|
6
6
|
from django.core.exceptions import ValidationError
|
|
7
|
-
from django.utils.functional import cached_property
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
@dataclass(frozen=True)
|
|
@@ -60,11 +59,11 @@ class Portfolio:
|
|
|
60
59
|
positions_map[pos.underlying_instrument] = pos
|
|
61
60
|
object.__setattr__(self, "positions_map", positions_map)
|
|
62
61
|
|
|
63
|
-
@
|
|
62
|
+
@property
|
|
64
63
|
def total_weight(self):
|
|
65
64
|
return round(sum([pos.weighting for pos in self.positions]), 6)
|
|
66
65
|
|
|
67
|
-
@
|
|
66
|
+
@property
|
|
68
67
|
def total_shares(self):
|
|
69
68
|
return sum([pos.target_shares for pos in self.positions if pos.target_shares is not None])
|
|
70
69
|
|
|
@@ -108,7 +107,7 @@ class Trade:
|
|
|
108
107
|
},
|
|
109
108
|
)
|
|
110
109
|
|
|
111
|
-
@
|
|
110
|
+
@property
|
|
112
111
|
def delta_weight(self) -> Decimal:
|
|
113
112
|
return self.target_weight - self.effective_weight
|
|
114
113
|
|
|
@@ -141,15 +140,15 @@ class TradeBatch:
|
|
|
141
140
|
trade_map[trade.underlying_instrument] = trade
|
|
142
141
|
object.__setattr__(self, "trades_map", trade_map)
|
|
143
142
|
|
|
144
|
-
@
|
|
143
|
+
@property
|
|
145
144
|
def total_target_weight(self) -> Decimal:
|
|
146
145
|
return round(sum([trade.target_weight for trade in self.trades]), 6)
|
|
147
146
|
|
|
148
|
-
@
|
|
147
|
+
@property
|
|
149
148
|
def total_effective_weight(self) -> Decimal:
|
|
150
149
|
return round(sum([trade.effective_weight for trade in self.trades]), 6)
|
|
151
150
|
|
|
152
|
-
@
|
|
151
|
+
@property
|
|
153
152
|
def totat_abs_delta_weight(self) -> Decimal:
|
|
154
153
|
return sum([abs(trade.delta_weight) for trade in self.trades])
|
|
155
154
|
|
|
@@ -26,21 +26,22 @@ class MarketCapitalizationRebalancing(AbstractRebalancingModel):
|
|
|
26
26
|
self.market_cap_df = pd.DataFrame(
|
|
27
27
|
instruments.dl.market_data(
|
|
28
28
|
values=[MarketData.MARKET_CAPITALIZATION],
|
|
29
|
-
|
|
30
|
-
to_date=self.trade_date,
|
|
29
|
+
exact_date=self.last_effective_date,
|
|
31
30
|
target_currency=self.TARGET_CURRENCY,
|
|
32
31
|
)
|
|
33
32
|
)
|
|
34
33
|
self.exchange_df = pd.DataFrame(
|
|
35
34
|
instruments.values_list("id", "exchange"), columns=["id", "exchange"]
|
|
36
35
|
).set_index("id")
|
|
36
|
+
instrument_ids = list(instruments.values_list("id", flat=True))
|
|
37
37
|
try:
|
|
38
|
-
self.market_cap_df =
|
|
39
|
-
["
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
self.market_cap_df = (
|
|
39
|
+
self.market_cap_df[["market_capitalization", "instrument_id"]]
|
|
40
|
+
.set_index("instrument_id")["market_capitalization"]
|
|
41
|
+
.reindex(instrument_ids)
|
|
42
|
+
)
|
|
42
43
|
except (IndexError, KeyError):
|
|
43
|
-
self.market_cap_df = pd.
|
|
44
|
+
self.market_cap_df = pd.Series(dtype="float64", index=instrument_ids)
|
|
44
45
|
|
|
45
46
|
def _get_instruments(
|
|
46
47
|
self,
|
|
@@ -90,11 +91,8 @@ class MarketCapitalizationRebalancing(AbstractRebalancingModel):
|
|
|
90
91
|
|
|
91
92
|
def is_valid(self) -> bool:
|
|
92
93
|
if not self.market_cap_df.empty:
|
|
93
|
-
trade_date_mktp_cap = (
|
|
94
|
-
self.market_cap_df.loc[self.trade_date, :].transpose().rename("market_capitalization")
|
|
95
|
-
)
|
|
96
94
|
df = pd.concat(
|
|
97
|
-
[
|
|
95
|
+
[self.market_cap_df, self.exchange_df], axis=1
|
|
98
96
|
) # if we are missing any market cap for not-delisted instrument, we consider the rebalancing not valid
|
|
99
97
|
df = df.groupby("exchange", dropna=False)["market_capitalization"].any()
|
|
100
98
|
missing_exchanges = Exchange.objects.filter(id__in=df[~df].index.to_list())
|
|
@@ -109,10 +107,9 @@ class MarketCapitalizationRebalancing(AbstractRebalancingModel):
|
|
|
109
107
|
|
|
110
108
|
def get_target_portfolio(self) -> Portfolio:
|
|
111
109
|
positions = []
|
|
112
|
-
|
|
113
|
-
total_market_cap = market_cap_df.sum()
|
|
110
|
+
total_market_cap = self.market_cap_df.sum()
|
|
114
111
|
|
|
115
|
-
for underlying_instrument, market_cap in market_cap_df.to_dict().items():
|
|
112
|
+
for underlying_instrument, market_cap in self.market_cap_df.to_dict().items():
|
|
116
113
|
positions.append(
|
|
117
114
|
Position(
|
|
118
115
|
underlying_instrument=underlying_instrument,
|
|
@@ -56,6 +56,8 @@ class TradeProposalModelSerializer(wb_serializers.ModelSerializer):
|
|
|
56
56
|
res["replay"] = reverse("wbportfolio:tradeproposal-replay", args=[instance.id], request=request)
|
|
57
57
|
if instance.status == TradeProposal.Status.DRAFT:
|
|
58
58
|
res["reset"] = reverse("wbportfolio:tradeproposal-reset", args=[instance.id], request=request)
|
|
59
|
+
res["normalize"] = reverse("wbportfolio:tradeproposal-normalize", args=[instance.id], request=request)
|
|
60
|
+
res["deleteall"] = reverse("wbportfolio:tradeproposal-deleteall", args=[instance.id], request=request)
|
|
59
61
|
res["trades"] = reverse(
|
|
60
62
|
"wbportfolio:tradeproposal-trade-list",
|
|
61
63
|
args=[instance.id],
|
|
@@ -139,20 +139,19 @@ class TestMarketCapitalizationRebalancing:
|
|
|
139
139
|
i1 = instrument_factory()
|
|
140
140
|
i2 = instrument_factory()
|
|
141
141
|
instrument_price_factory.create(instrument=i1, date=last_effective_date)
|
|
142
|
-
instrument_price_factory.create(instrument=
|
|
143
|
-
instrument_price_factory.create(instrument=i2, date=last_effective_date) # weekday is ffill
|
|
142
|
+
instrument_price_factory.create(instrument=i2, date=last_effective_date)
|
|
144
143
|
return MarketCapitalizationRebalancing(portfolio, weekday, last_effective_date, instrument_ids=[i1.id, i2.id])
|
|
145
144
|
|
|
146
145
|
def test_is_valid(self, portfolio, weekday, model, instrument_factory, instrument_price_factory):
|
|
147
|
-
assert not model.is_valid()
|
|
148
|
-
i2 = model.market_cap_df.columns[1]
|
|
149
|
-
model.market_cap_df.loc[weekday, i2] = 1000 # some value
|
|
150
146
|
assert model.is_valid()
|
|
147
|
+
i2 = model.market_cap_df.index[1]
|
|
148
|
+
model.market_cap_df.loc[i2] = None # some value
|
|
149
|
+
assert not model.is_valid()
|
|
151
150
|
|
|
152
151
|
def test_get_target_portfolio(self, portfolio, weekday, model, asset_position_factory):
|
|
153
|
-
i1 = model.market_cap_df.
|
|
154
|
-
i2 = model.market_cap_df.
|
|
155
|
-
mkt12 = InstrumentPrice.objects.get(instrument_id=i1, date=model.
|
|
152
|
+
i1 = model.market_cap_df.index[0]
|
|
153
|
+
i2 = model.market_cap_df.index[1]
|
|
154
|
+
mkt12 = InstrumentPrice.objects.get(instrument_id=i1, date=model.last_effective_date).market_capitalization
|
|
156
155
|
mkt21 = InstrumentPrice.objects.get(instrument_id=i2, date=model.last_effective_date).market_capitalization
|
|
157
156
|
|
|
158
157
|
target_portfolio = model.get_target_portfolio()
|
wbportfolio/viewsets/assets.py
CHANGED
|
@@ -546,7 +546,7 @@ class CompositionModelPortfolioPandasView(
|
|
|
546
546
|
)
|
|
547
547
|
)
|
|
548
548
|
)
|
|
549
|
-
df = pd.DataFrame(rows)
|
|
549
|
+
df = pd.DataFrame(rows, columns=["underlying_instrument", "weighting", "shares", "portfolio"])
|
|
550
550
|
df = df.pivot_table(
|
|
551
551
|
index="underlying_instrument",
|
|
552
552
|
columns=["portfolio"],
|
|
@@ -16,8 +16,7 @@ from .registers import RegisterButtonConfig
|
|
|
16
16
|
from .trades import (
|
|
17
17
|
TradeButtonConfig,
|
|
18
18
|
TradeInstrumentButtonConfig,
|
|
19
|
-
TradeProposalButtonConfig,
|
|
20
|
-
TradeTradeProposalButtonConfig,
|
|
21
19
|
)
|
|
20
|
+
from .trade_proposals import TradeProposalButtonConfig
|
|
22
21
|
from .reconciliations import AccountReconciliationButtonViewConfig, AccountReconciliationLineButtonViewConfig
|
|
23
22
|
from .signals import *
|
|
@@ -7,18 +7,59 @@ from wbcore.metadata.configs.buttons.view_config import ButtonViewConfig
|
|
|
7
7
|
class TradeProposalButtonConfig(ButtonViewConfig):
|
|
8
8
|
def get_custom_list_instance_buttons(self):
|
|
9
9
|
return {
|
|
10
|
-
bt.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
10
|
+
bt.DropDownButton(
|
|
11
|
+
label="Tools",
|
|
12
|
+
buttons=(
|
|
13
|
+
bt.ActionButton(
|
|
14
|
+
method=RequestType.PATCH,
|
|
15
|
+
identifiers=("wbportfolio:tradeproposal",),
|
|
16
|
+
key="replay",
|
|
17
|
+
icon=WBIcon.SYNCHRONIZE.icon,
|
|
18
|
+
label="Replay Trades",
|
|
19
|
+
description_fields="""
|
|
20
|
+
<p>Replay Trades. It will recompute all assets positions until next trade proposal day (or today otherwise) </p>
|
|
21
|
+
""",
|
|
22
|
+
action_label="Replay Trade",
|
|
23
|
+
title="Replay Trade",
|
|
24
|
+
),
|
|
25
|
+
bt.ActionButton(
|
|
26
|
+
method=RequestType.PATCH,
|
|
27
|
+
identifiers=("wbportfolio:tradeproposal",),
|
|
28
|
+
key="reset",
|
|
29
|
+
icon=WBIcon.REGENERATE.icon,
|
|
30
|
+
label="Reset Trades",
|
|
31
|
+
description_fields="""
|
|
32
|
+
<p>Delete and recreate initial trades to from its associated model portfolio</p>
|
|
33
|
+
""",
|
|
34
|
+
action_label="Reset Trades",
|
|
35
|
+
title="Reset Trades",
|
|
36
|
+
),
|
|
37
|
+
bt.ActionButton(
|
|
38
|
+
method=RequestType.PATCH,
|
|
39
|
+
identifiers=("wbportfolio:tradeproposal",),
|
|
40
|
+
key="normalize",
|
|
41
|
+
icon=WBIcon.EDIT.icon,
|
|
42
|
+
label="Normalize Trades",
|
|
43
|
+
description_fields="""
|
|
44
|
+
<p>Make sure all trades normalize to a total target weight of 100%</p>
|
|
45
|
+
""",
|
|
46
|
+
action_label="Normalize Trades",
|
|
47
|
+
title="Normalize Trades",
|
|
48
|
+
),
|
|
49
|
+
bt.ActionButton(
|
|
50
|
+
method=RequestType.PATCH,
|
|
51
|
+
identifiers=("wbportfolio:tradeproposal",),
|
|
52
|
+
key="deleteall",
|
|
53
|
+
icon=WBIcon.DELETE.icon,
|
|
54
|
+
label="Delete All Trades",
|
|
55
|
+
description_fields="""
|
|
56
|
+
<p>Delete all trades from this trade proposal?</p>
|
|
57
|
+
""",
|
|
58
|
+
action_label="Delete All Trades",
|
|
59
|
+
title="Delete All Trades",
|
|
60
|
+
),
|
|
61
|
+
),
|
|
62
|
+
),
|
|
22
63
|
}
|
|
23
64
|
|
|
24
65
|
def get_custom_instance_buttons(self):
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
from rest_framework.reverse import reverse
|
|
2
2
|
from wbcore.contrib.icons import WBIcon
|
|
3
|
-
from wbcore.enums import RequestType
|
|
4
3
|
from wbcore.metadata.configs import buttons as bt
|
|
5
4
|
from wbcore.metadata.configs.buttons.view_config import ButtonViewConfig
|
|
6
5
|
from wbfdm.models import Instrument
|
|
7
6
|
|
|
8
|
-
from wbportfolio.models import TradeProposal
|
|
9
|
-
|
|
10
7
|
|
|
11
8
|
class TradeButtonConfig(ButtonViewConfig):
|
|
12
9
|
def get_custom_list_instance_buttons(self):
|
|
@@ -61,85 +58,3 @@ class TradeInstrumentButtonConfig(TradeButtonConfig):
|
|
|
61
58
|
)
|
|
62
59
|
)
|
|
63
60
|
return res
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
class TradeTradeProposalButtonConfig(ButtonViewConfig):
|
|
67
|
-
def get_custom_buttons(self):
|
|
68
|
-
if trade_proposal_id := self.view.kwargs.get("trade_proposal_id", None):
|
|
69
|
-
trade_proposal = TradeProposal.objects.get(id=trade_proposal_id)
|
|
70
|
-
if trade_proposal.status == TradeProposal.Status.DRAFT:
|
|
71
|
-
return {
|
|
72
|
-
bt.DropDownButton(
|
|
73
|
-
label="Tools",
|
|
74
|
-
buttons=(
|
|
75
|
-
bt.ActionButton(
|
|
76
|
-
method=RequestType.PATCH,
|
|
77
|
-
identifiers=("wbportfolio:tradeproposal",),
|
|
78
|
-
endpoint=reverse(
|
|
79
|
-
"wbportfolio:tradeproposal-reset", args=[trade_proposal_id], request=self.request
|
|
80
|
-
),
|
|
81
|
-
icon=WBIcon.REGENERATE.icon,
|
|
82
|
-
label="Reset Trade",
|
|
83
|
-
description_fields="""
|
|
84
|
-
<p>Delete and recreate initial trades to from its associated model portfolio</p>
|
|
85
|
-
""",
|
|
86
|
-
action_label="Reset Trade",
|
|
87
|
-
title="Reset Trade",
|
|
88
|
-
),
|
|
89
|
-
bt.ActionButton(
|
|
90
|
-
method=RequestType.PATCH,
|
|
91
|
-
identifiers=("wbportfolio:tradeproposal",),
|
|
92
|
-
endpoint=reverse(
|
|
93
|
-
"wbportfolio:tradeproposal-normalize",
|
|
94
|
-
args=[trade_proposal_id],
|
|
95
|
-
request=self.request,
|
|
96
|
-
),
|
|
97
|
-
icon=WBIcon.EDIT.icon,
|
|
98
|
-
label="Normalize Trades",
|
|
99
|
-
description_fields="""
|
|
100
|
-
<p>Make sure all trades normalize to a total target weight of 100%</p>
|
|
101
|
-
""",
|
|
102
|
-
action_label="Normalize Trades",
|
|
103
|
-
title="Normalize Trades",
|
|
104
|
-
),
|
|
105
|
-
bt.ActionButton(
|
|
106
|
-
method=RequestType.PATCH,
|
|
107
|
-
identifiers=("wbportfolio:tradeproposal",),
|
|
108
|
-
endpoint=reverse(
|
|
109
|
-
"wbportfolio:tradeproposal-deleteall",
|
|
110
|
-
args=[trade_proposal_id],
|
|
111
|
-
request=self.request,
|
|
112
|
-
),
|
|
113
|
-
icon=WBIcon.DELETE.icon,
|
|
114
|
-
label="Delete All Trades",
|
|
115
|
-
description_fields="""
|
|
116
|
-
<p>Delete all trades from this trade proposal?</p>
|
|
117
|
-
""",
|
|
118
|
-
action_label="Delete All Trades",
|
|
119
|
-
title="Delete All Trades",
|
|
120
|
-
),
|
|
121
|
-
),
|
|
122
|
-
)
|
|
123
|
-
}
|
|
124
|
-
return {}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
class TradeProposalButtonConfig(ButtonViewConfig):
|
|
128
|
-
def get_custom_list_instance_buttons(self):
|
|
129
|
-
return {
|
|
130
|
-
bt.ActionButton(
|
|
131
|
-
method=RequestType.PATCH,
|
|
132
|
-
identifiers=("wbportfolio:tradeproposal",),
|
|
133
|
-
key="replay",
|
|
134
|
-
icon=WBIcon.SYNCHRONIZE.icon,
|
|
135
|
-
label="Replay Trades",
|
|
136
|
-
description_fields="""
|
|
137
|
-
<p>Replay Trades. It will recompute all assets positions until next trade proposal day (or today otherwise) </p>
|
|
138
|
-
""",
|
|
139
|
-
action_label="Replay Trade",
|
|
140
|
-
title="Replay Trade",
|
|
141
|
-
)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
def get_custom_instance_buttons(self):
|
|
145
|
-
return self.get_custom_list_instance_buttons()
|
|
@@ -4,6 +4,7 @@ from datetime import date
|
|
|
4
4
|
from django.shortcuts import get_object_or_404
|
|
5
5
|
from django.utils.functional import cached_property
|
|
6
6
|
from pandas._libs.tslibs.offsets import BDay
|
|
7
|
+
from rest_framework import status
|
|
7
8
|
from rest_framework.decorators import action
|
|
8
9
|
from rest_framework.response import Response
|
|
9
10
|
from wbcompliance.viewsets.risk_management.mixins import RiskCheckViewSetMixin
|
|
@@ -78,6 +79,7 @@ class TradeProposalModelViewSet(CloneMixin, RiskCheckViewSetMixin, InternalUserP
|
|
|
78
79
|
if trade_proposal.status == TradeProposal.Status.DRAFT:
|
|
79
80
|
trade_proposal.reset_trades()
|
|
80
81
|
return Response({"send": True})
|
|
82
|
+
return Response({"status": "Trade proposal is not Draft"}, status=status.HTTP_400_BAD_REQUEST)
|
|
81
83
|
|
|
82
84
|
@action(detail=True, methods=["PATCH"])
|
|
83
85
|
def normalize(self, request, pk=None):
|
|
@@ -85,6 +87,7 @@ class TradeProposalModelViewSet(CloneMixin, RiskCheckViewSetMixin, InternalUserP
|
|
|
85
87
|
if trade_proposal.status == TradeProposal.Status.DRAFT:
|
|
86
88
|
trade_proposal.normalize_trades()
|
|
87
89
|
return Response({"send": True})
|
|
90
|
+
return Response({"status": "Trade proposal is not Draft"}, status=status.HTTP_400_BAD_REQUEST)
|
|
88
91
|
|
|
89
92
|
@action(detail=True, methods=["PATCH"])
|
|
90
93
|
def replay(self, request, pk=None):
|
|
@@ -92,6 +95,7 @@ class TradeProposalModelViewSet(CloneMixin, RiskCheckViewSetMixin, InternalUserP
|
|
|
92
95
|
if trade_proposal.portfolio.is_manageable:
|
|
93
96
|
replay_as_task.delay(trade_proposal.id)
|
|
94
97
|
return Response({"send": True})
|
|
98
|
+
return Response({"status": "Trade proposal is not Draft"}, status=status.HTTP_400_BAD_REQUEST)
|
|
95
99
|
|
|
96
100
|
@action(detail=True, methods=["PATCH"])
|
|
97
101
|
def deleteall(self, request, pk=None):
|
|
@@ -99,6 +103,7 @@ class TradeProposalModelViewSet(CloneMixin, RiskCheckViewSetMixin, InternalUserP
|
|
|
99
103
|
if trade_proposal.status == TradeProposal.Status.DRAFT:
|
|
100
104
|
trade_proposal.trades.all().delete()
|
|
101
105
|
return Response({"send": True})
|
|
106
|
+
return Response({"status": "Trade proposal is not Draft"}, status=status.HTTP_400_BAD_REQUEST)
|
|
102
107
|
|
|
103
108
|
|
|
104
109
|
class TradeProposalPortfolioModelViewSet(UserPortfolioRequestPermissionMixin, TradeProposalModelViewSet):
|
|
@@ -63,7 +63,6 @@ from ..configs import (
|
|
|
63
63
|
TradePortfolioEndpointConfig,
|
|
64
64
|
TradePortfolioTitleConfig,
|
|
65
65
|
TradeTitleConfig,
|
|
66
|
-
TradeTradeProposalButtonConfig,
|
|
67
66
|
TradeTradeProposalDisplayConfig,
|
|
68
67
|
TradeTradeProposalEndpointConfig,
|
|
69
68
|
)
|
|
@@ -376,7 +375,6 @@ class TradeTradeProposalModelViewSet(
|
|
|
376
375
|
display_config_class = TradeTradeProposalDisplayConfig
|
|
377
376
|
endpoint_config_class = TradeTradeProposalEndpointConfig
|
|
378
377
|
serializer_class = TradeTradeProposalModelSerializer
|
|
379
|
-
button_config_class = TradeTradeProposalButtonConfig
|
|
380
378
|
|
|
381
379
|
@cached_property
|
|
382
380
|
def trade_proposal(self):
|
|
@@ -115,7 +115,7 @@ wbportfolio/import_export/handlers/dividend.py,sha256=tftdVdAzNpKSSvouOtvJfzWL36
|
|
|
115
115
|
wbportfolio/import_export/handlers/fees.py,sha256=XYH752IkNGYhhhwatp8nYa1zG1-YZFDkYW15dyQgOIg,2824
|
|
116
116
|
wbportfolio/import_export/handlers/portfolio_cash_flow.py,sha256=2ODaquuC83RfmNwmQ-8TdhiASObfIems_B1g0yqaYTs,2733
|
|
117
117
|
wbportfolio/import_export/handlers/register.py,sha256=sYyXkE8b1DPZ5monxylZn0kjxLVdNYYZR-p61dwEoDM,2271
|
|
118
|
-
wbportfolio/import_export/handlers/trade.py,sha256=
|
|
118
|
+
wbportfolio/import_export/handlers/trade.py,sha256=DArRPXWjH2YhqPGtbZw7Nyt3JjQNEltx1RyQ3-4FzKs,10829
|
|
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
|
|
@@ -273,11 +273,11 @@ wbportfolio/models/transactions/dividends.py,sha256=naL5xeDQfUBf5KyGt7y-tTcHL22n
|
|
|
273
273
|
wbportfolio/models/transactions/expiry.py,sha256=vnNHdcC1hf2HP4rAbmoGgOfagBYKNFytqOwzOI0MlVI,144
|
|
274
274
|
wbportfolio/models/transactions/fees.py,sha256=ffvqo8I4A0l5rLi00jJ6sGot0jmnkoxaNsbDzdPLwCg,5712
|
|
275
275
|
wbportfolio/models/transactions/rebalancing.py,sha256=vEYdYhog9jiPw_xQd_XQunNzcpHZ8Zk18kQhsN4HuhA,6596
|
|
276
|
-
wbportfolio/models/transactions/trade_proposals.py,sha256=
|
|
277
|
-
wbportfolio/models/transactions/trades.py,sha256=
|
|
276
|
+
wbportfolio/models/transactions/trade_proposals.py,sha256=gsvgEI5yqmFb_bCN4FsgfyQqslMXLQlzTe3G7odwTCA,21366
|
|
277
|
+
wbportfolio/models/transactions/trades.py,sha256=gbXvxiyh8bvg6ldyd8qBwRPv4t0Tf5cbZXBnZXPIomc,27861
|
|
278
278
|
wbportfolio/models/transactions/transactions.py,sha256=4THsE4xqdigZAwWKYfTNRLPJlkmAmsgE70Ribp9Lnrk,7127
|
|
279
279
|
wbportfolio/pms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
280
|
-
wbportfolio/pms/typing.py,sha256=
|
|
280
|
+
wbportfolio/pms/typing.py,sha256=WKP5tYyYt7DbMo25VM99V4IAM9oDSIPZyR3yXCzeZEA,5920
|
|
281
281
|
wbportfolio/pms/analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
282
282
|
wbportfolio/pms/analytics/portfolio.py,sha256=93hcHEuBOPi534El2HVCIyjs9MBQMX7dIZ97JIpNV1c,1535
|
|
283
283
|
wbportfolio/pms/statistics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -289,7 +289,7 @@ wbportfolio/rebalancing/decorators.py,sha256=JhQ2vkcIuGBTGvNmpkQerdw2-vLq-RAb0KA
|
|
|
289
289
|
wbportfolio/rebalancing/models/__init__.py,sha256=AQjG7Tu5vlmhqncVoYOjpBKU2UIvgo9FuP2_jD2w-UI,232
|
|
290
290
|
wbportfolio/rebalancing/models/composite.py,sha256=XAjJqLRNsV-MuBKrat3THEfAWs6PXQNSO0g8k8MtBXo,1157
|
|
291
291
|
wbportfolio/rebalancing/models/equally_weighted.py,sha256=U29MOHJMQMIg7Y7W_8t5K3nXjaznzt4ArIxQSiv0Xok,863
|
|
292
|
-
wbportfolio/rebalancing/models/market_capitalization_weighted.py,sha256=
|
|
292
|
+
wbportfolio/rebalancing/models/market_capitalization_weighted.py,sha256=RTEVAwOh7ec5MqCFYd-9lSNv4eNhchD1aAyEnJeyZwQ,4825
|
|
293
293
|
wbportfolio/rebalancing/models/model_portfolio.py,sha256=XQdvs03-0M9YUnL4DidwZC4E6k-ANCNcZ--T_aaOXTQ,1233
|
|
294
294
|
wbportfolio/reports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
295
295
|
wbportfolio/reports/monthly_position_report.py,sha256=e7BzjDd6eseUOwLwQJXKvWErQ58YnCsznHU2VtR6izM,2981
|
|
@@ -339,7 +339,7 @@ wbportfolio/serializers/transactions/claim.py,sha256=kC4E2RZRrpd9i8tGfoiV-gpWDk3
|
|
|
339
339
|
wbportfolio/serializers/transactions/dividends.py,sha256=EULwKDumHBv4r2HsdEGZMZGFaye4dRUNNyXg6-wZXzc,520
|
|
340
340
|
wbportfolio/serializers/transactions/expiry.py,sha256=K3XOSbCyef-xRzOjCr4Qg_YFJ_JuuiJ9u6tDS86l0hg,477
|
|
341
341
|
wbportfolio/serializers/transactions/fees.py,sha256=uPmSWuCeoV2bwVS6RmEz3a0VRBWJHIQr0WhklYc1UAI,1068
|
|
342
|
-
wbportfolio/serializers/transactions/trade_proposals.py,sha256=
|
|
342
|
+
wbportfolio/serializers/transactions/trade_proposals.py,sha256=2-GLD70swYlLVEIiWWTNcgN83EbPtLyFSO9wKBwjUm4,3644
|
|
343
343
|
wbportfolio/serializers/transactions/trades.py,sha256=pONV5NSqrXUnoTEoAxovnnQqu37cZGuB33TYvIOK3rE,10009
|
|
344
344
|
wbportfolio/serializers/transactions/transactions.py,sha256=O137zeCndK-nxIWSRLEj7bXbBZDGa4d6qK6pJIIYK3g,4170
|
|
345
345
|
wbportfolio/static/wbportfolio/css/macro_review.css,sha256=FAVVO8nModxwPXcTKpcfzVxBGPZGJVK1Xn-0dkSfGyc,233
|
|
@@ -385,7 +385,7 @@ wbportfolio/tests/models/transactions/test_trades.py,sha256=z0CCZjB648ECDSEdwmzq
|
|
|
385
385
|
wbportfolio/tests/pms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
386
386
|
wbportfolio/tests/pms/test_analytics.py,sha256=FrvVsV_uUiTgmRUfsaB-_sGzY30CqknbOY2DvmwR_70,1141
|
|
387
387
|
wbportfolio/tests/rebalancing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
388
|
-
wbportfolio/tests/rebalancing/test_models.py,sha256=
|
|
388
|
+
wbportfolio/tests/rebalancing/test_models.py,sha256=wdlCfc6YxVT0oLxr45Q-LPKy4LjGbylkdxcfZozS0Fk,7565
|
|
389
389
|
wbportfolio/tests/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
390
390
|
wbportfolio/tests/serializers/test_claims.py,sha256=vQrg73xQXRFEgvx3KI9ivFre_wpBFzdO0p0J13PkvdY,582
|
|
391
391
|
wbportfolio/tests/viewsets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -396,7 +396,7 @@ wbportfolio/tests/viewsets/transactions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J
|
|
|
396
396
|
wbportfolio/tests/viewsets/transactions/test_claims.py,sha256=QEZfMAW07dyoZ63t2umSwGOqvaTULfYfbN_F4ZoSAcw,6368
|
|
397
397
|
wbportfolio/viewsets/__init__.py,sha256=3kUaQ66ybvROwejd3bEcSt4XKzfOlPDaeoStMvlz7qY,2294
|
|
398
398
|
wbportfolio/viewsets/adjustments.py,sha256=5hWCjxSgUIsrPOmJKoDYK3gywdMTI0aYDorEj1FXRVc,1429
|
|
399
|
-
wbportfolio/viewsets/assets.py,sha256=
|
|
399
|
+
wbportfolio/viewsets/assets.py,sha256=vwfrJaws7aVOUd-4FTlZVahpiMGgIEt1rcUumvWIKss,22734
|
|
400
400
|
wbportfolio/viewsets/assets_and_net_new_money_progression.py,sha256=Jl4vEQP4N2OFL5IGBXoKcj-0qaPviU0I8npvQLw4Io0,4464
|
|
401
401
|
wbportfolio/viewsets/custodians.py,sha256=CTFqkqVP1R3AV7lhdvcdICxB5DfwDYCyikNSI5kbYEo,2322
|
|
402
402
|
wbportfolio/viewsets/esg.py,sha256=27MxxdXQH3Cq_1UEYmcrF7htUOg6i81fUpbVQXAAKJI,6985
|
|
@@ -417,7 +417,7 @@ wbportfolio/viewsets/signals.py,sha256=URxNz-7tKNBgvaFIE3FItfao3Md0dKQN_eVFwxxiE
|
|
|
417
417
|
wbportfolio/viewsets/charts/__init__.py,sha256=mNSpDrsqK-afRxGBfEsfgupUc20xU-JW08FpxIyudok,71
|
|
418
418
|
wbportfolio/viewsets/charts/assets.py,sha256=rZM4Ul0ZXVr7uz3SU44J5IYU7-FivjDgR1it_4ewk2o,10547
|
|
419
419
|
wbportfolio/viewsets/configs/__init__.py,sha256=K5opfVQvgGwSyw3XwDGkv3lo5i9jBxOJXYhLqyCdfeQ,137
|
|
420
|
-
wbportfolio/viewsets/configs/buttons/__init__.py,sha256=
|
|
420
|
+
wbportfolio/viewsets/configs/buttons/__init__.py,sha256=99WAr_tftIXJbQPINkwy8xKI509d1zlatDeGu0zHMjU,846
|
|
421
421
|
wbportfolio/viewsets/configs/buttons/adjustments.py,sha256=sUY_3vxqP0kuqs8i5hklfboZI6QiAOrmu30eb29Xupo,492
|
|
422
422
|
wbportfolio/viewsets/configs/buttons/assets.py,sha256=6HqSyUpczK66iL0Jg8BbvAJuci3jm8OsB9p9Nt_o14Q,5680
|
|
423
423
|
wbportfolio/viewsets/configs/buttons/claims.py,sha256=G1GFK1jQyR9M0dyXthYA6krG2JFvHhc3D7pFh6IUQ30,3684
|
|
@@ -429,8 +429,8 @@ wbportfolio/viewsets/configs/buttons/products.py,sha256=HfMQjpgGAgvTDLEFQRwbHE82
|
|
|
429
429
|
wbportfolio/viewsets/configs/buttons/reconciliations.py,sha256=lw4r22GHpqKPUF1MrB6P9dOkL-FHe5iiBJ0--f8D74E,3145
|
|
430
430
|
wbportfolio/viewsets/configs/buttons/registers.py,sha256=aS89TsYHql83k-NHojOrLDqtBpnpsUUO8x63PiMfrXM,445
|
|
431
431
|
wbportfolio/viewsets/configs/buttons/signals.py,sha256=6sKBQI_eDvZuZR5bUUwvur5R67A3oChAGxPfayWUelE,2739
|
|
432
|
-
wbportfolio/viewsets/configs/buttons/trade_proposals.py,sha256=
|
|
433
|
-
wbportfolio/viewsets/configs/buttons/trades.py,sha256=
|
|
432
|
+
wbportfolio/viewsets/configs/buttons/trade_proposals.py,sha256=eZBfYk5ZhancCVcu7bvRKPGBKTl_tGlgz6BZrGPpPxQ,2953
|
|
433
|
+
wbportfolio/viewsets/configs/buttons/trades.py,sha256=X2B1l0iEIdHb3ZMf9rLVoiX_H8lSyLD12wopgumOIX4,2318
|
|
434
434
|
wbportfolio/viewsets/configs/display/__init__.py,sha256=SmazY-YEp-Xf8G08Uz1-CzePZRCRtHrziRMIYYIGpCk,2176
|
|
435
435
|
wbportfolio/viewsets/configs/display/adjustments.py,sha256=jIOEc23OCYBguLaZRlZxC916kocYT35ZV9Jsiocs9nk,3334
|
|
436
436
|
wbportfolio/viewsets/configs/display/assets.py,sha256=8yLAXyrZLl7L1m1rhBnkh4uaqVpSQJhRVkcM7_AHBsc,10943
|
|
@@ -514,10 +514,10 @@ wbportfolio/viewsets/transactions/claim.py,sha256=m_Fy4J_QZSve1VlR_sPQrVBDopgCqq
|
|
|
514
514
|
wbportfolio/viewsets/transactions/fees.py,sha256=7VUXIogmRrXCz_D9tvDiiTae0t5j09W9zPUzxXzBGTE,7031
|
|
515
515
|
wbportfolio/viewsets/transactions/mixins.py,sha256=WipvJoi5hylkpD0y9VATe30WAcwIHUIroVkK10FYw7k,636
|
|
516
516
|
wbportfolio/viewsets/transactions/rebalancing.py,sha256=6rIrdK0rtKL1afJ-tYfAGdQVTN2MH1kG_yCeVkmyK8k,1263
|
|
517
|
-
wbportfolio/viewsets/transactions/trade_proposals.py,sha256=
|
|
518
|
-
wbportfolio/viewsets/transactions/trades.py,sha256=
|
|
517
|
+
wbportfolio/viewsets/transactions/trade_proposals.py,sha256=gXdJJ00D6UavZfBczvZQb5cYPQlU6-xOg_-TnMBzEO0,4742
|
|
518
|
+
wbportfolio/viewsets/transactions/trades.py,sha256=mo5b1wFm0twvGVp-CYnzpGLYMqPcHN8GjH4G_WwFFwc,16237
|
|
519
519
|
wbportfolio/viewsets/transactions/transactions.py,sha256=ixDp-nsNA8t_A06rBCT19hOMJHy0iRmdz1XKdV1OwAs,4450
|
|
520
|
-
wbportfolio-1.46.
|
|
521
|
-
wbportfolio-1.46.
|
|
522
|
-
wbportfolio-1.46.
|
|
523
|
-
wbportfolio-1.46.
|
|
520
|
+
wbportfolio-1.46.7.dist-info/METADATA,sha256=nAZz3VADXG2Z3fYmWJ3oqXrVIQa1HAaUtMQpX3e-V68,734
|
|
521
|
+
wbportfolio-1.46.7.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
|
|
522
|
+
wbportfolio-1.46.7.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
|
|
523
|
+
wbportfolio-1.46.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|