wbportfolio 1.52.0__py2.py3-none-any.whl → 1.52.2__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/admin/__init__.py +1 -1
- wbportfolio/admin/transactions/__init__.py +0 -1
- wbportfolio/admin/transactions/dividends.py +40 -4
- wbportfolio/admin/transactions/fees.py +24 -14
- wbportfolio/admin/transactions/trades.py +34 -12
- wbportfolio/defaults/fees/default.py +7 -15
- wbportfolio/factories/__init__.py +0 -1
- wbportfolio/factories/dividends.py +8 -3
- wbportfolio/factories/fees.py +8 -4
- wbportfolio/factories/trades.py +10 -3
- wbportfolio/filters/transactions/__init__.py +1 -2
- wbportfolio/filters/transactions/fees.py +5 -10
- wbportfolio/filters/transactions/trades.py +17 -8
- wbportfolio/filters/transactions/utils.py +42 -0
- wbportfolio/import_export/handlers/dividend.py +7 -7
- wbportfolio/import_export/handlers/fees.py +11 -21
- wbportfolio/import_export/handlers/trade.py +5 -7
- wbportfolio/import_export/parsers/jpmorgan/fees.py +2 -2
- wbportfolio/import_export/parsers/leonteq/customer_trade.py +5 -5
- wbportfolio/import_export/parsers/leonteq/fees.py +11 -7
- wbportfolio/import_export/parsers/leonteq/trade.py +0 -5
- wbportfolio/import_export/parsers/natixis/d1_fees.py +2 -2
- wbportfolio/import_export/parsers/natixis/dividend.py +4 -9
- wbportfolio/import_export/parsers/natixis/fees.py +7 -9
- wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py +1 -1
- wbportfolio/import_export/parsers/sg_lux/fees.py +2 -2
- wbportfolio/import_export/parsers/sg_lux/perf_fees.py +2 -2
- wbportfolio/import_export/parsers/sg_lux/utils.py +2 -2
- wbportfolio/import_export/parsers/ubs/api/fees.py +2 -2
- wbportfolio/import_export/parsers/vontobel/customer_trade.py +2 -3
- wbportfolio/import_export/parsers/vontobel/historical_customer_trade.py +0 -1
- wbportfolio/import_export/parsers/vontobel/management_fees.py +7 -7
- wbportfolio/import_export/parsers/vontobel/performance_fees.py +3 -3
- wbportfolio/jinja2/wbportfolio/sql/aum_nnm.sql +2 -2
- wbportfolio/migrations/0059_fees_unique_fees.py +1 -1
- wbportfolio/migrations/0077_remove_transaction_currency_and_more.py +622 -0
- wbportfolio/models/mixins/liquidity_stress_test.py +3 -3
- wbportfolio/models/transactions/__init__.py +0 -2
- wbportfolio/models/transactions/claim.py +1 -1
- wbportfolio/models/transactions/dividends.py +41 -5
- wbportfolio/models/transactions/fees.py +55 -22
- wbportfolio/models/transactions/trade_proposals.py +26 -6
- wbportfolio/models/transactions/trades.py +111 -50
- wbportfolio/models/transactions/transactions.py +60 -156
- wbportfolio/serializers/signals.py +15 -10
- wbportfolio/serializers/transactions/__init__.py +0 -5
- wbportfolio/serializers/transactions/dividends.py +37 -9
- wbportfolio/serializers/transactions/fees.py +39 -10
- wbportfolio/serializers/transactions/trades.py +56 -16
- wbportfolio/tasks.py +2 -2
- wbportfolio/tests/conftest.py +2 -8
- wbportfolio/tests/models/test_imports.py +2 -7
- wbportfolio/tests/models/transactions/test_fees.py +7 -13
- wbportfolio/tests/models/transactions/test_trade_proposals.py +4 -2
- wbportfolio/urls.py +3 -6
- wbportfolio/viewsets/configs/buttons/__init__.py +1 -0
- wbportfolio/viewsets/configs/buttons/mixins.py +2 -2
- wbportfolio/viewsets/configs/buttons/trades.py +8 -0
- wbportfolio/viewsets/configs/display/__init__.py +2 -3
- wbportfolio/viewsets/configs/display/fees.py +3 -3
- wbportfolio/viewsets/configs/endpoints/__init__.py +3 -4
- wbportfolio/viewsets/configs/endpoints/fees.py +2 -2
- wbportfolio/viewsets/configs/menu/__init__.py +0 -1
- wbportfolio/viewsets/configs/titles/__init__.py +2 -3
- wbportfolio/viewsets/configs/titles/fees.py +4 -8
- wbportfolio/viewsets/mixins.py +5 -1
- wbportfolio/viewsets/products.py +6 -6
- wbportfolio/viewsets/transactions/__init__.py +2 -7
- wbportfolio/viewsets/transactions/fees.py +22 -22
- wbportfolio/viewsets/transactions/trade_proposals.py +1 -0
- wbportfolio/viewsets/transactions/trades.py +2 -0
- {wbportfolio-1.52.0.dist-info → wbportfolio-1.52.2.dist-info}/METADATA +1 -1
- {wbportfolio-1.52.0.dist-info → wbportfolio-1.52.2.dist-info}/RECORD +75 -84
- wbportfolio/admin/transactions/transactions.py +0 -38
- wbportfolio/factories/transactions.py +0 -22
- wbportfolio/filters/transactions/transactions.py +0 -99
- wbportfolio/models/transactions/expiry.py +0 -7
- wbportfolio/serializers/transactions/expiry.py +0 -18
- wbportfolio/serializers/transactions/transactions.py +0 -85
- wbportfolio/viewsets/configs/display/transactions.py +0 -55
- wbportfolio/viewsets/configs/endpoints/transactions.py +0 -14
- wbportfolio/viewsets/configs/menu/transactions.py +0 -9
- wbportfolio/viewsets/configs/titles/transactions.py +0 -9
- wbportfolio/viewsets/transactions/transactions.py +0 -122
- {wbportfolio-1.52.0.dist-info → wbportfolio-1.52.2.dist-info}/WHEEL +0 -0
- {wbportfolio-1.52.0.dist-info → wbportfolio-1.52.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,7 +2,6 @@ from datetime import timedelta
|
|
|
2
2
|
from decimal import Decimal
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
|
-
from wbfdm.factories import CashFactory
|
|
6
5
|
from wbfdm.models import InstrumentPrice
|
|
7
6
|
|
|
8
7
|
from wbportfolio.models import FeeCalculation, Fees, Product
|
|
@@ -11,19 +10,14 @@ from wbportfolio.models import FeeCalculation, Fees, Product
|
|
|
11
10
|
def fees_calculation(price_id):
|
|
12
11
|
price = InstrumentPrice.objects.get(id=price_id)
|
|
13
12
|
product = Product.objects.get(id=price.instrument.id)
|
|
14
|
-
cash = CashFactory.create(currency=product.currency)
|
|
15
13
|
yield {
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"transaction_date": price.date,
|
|
14
|
+
"product": product,
|
|
15
|
+
"fee_date": price.date,
|
|
19
16
|
"transaction_subtype": Fees.Type.MANAGEMENT,
|
|
20
|
-
"underlying_instrument": cash,
|
|
21
17
|
"currency": product.currency,
|
|
22
18
|
"calculated": True,
|
|
23
19
|
"total_value": price.net_value,
|
|
24
|
-
"total_value_fx_portfolio": price.net_value,
|
|
25
20
|
"total_value_gross": price.net_value,
|
|
26
|
-
"total_value_gross_fx_portfolio": price.net_value,
|
|
27
21
|
}
|
|
28
22
|
|
|
29
23
|
|
|
@@ -38,14 +32,14 @@ class TestFeesModel:
|
|
|
38
32
|
) # no matter if estimated or not, we expect this fee to be on the resulting queryset
|
|
39
33
|
calculated_fees_d1 = fees_factory.create( # there will be a real fee for that date, type and product, so this will be filtered out
|
|
40
34
|
calculated=True,
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
product=fees_d0.product,
|
|
36
|
+
fee_date=(fees_d0.fee_date + timedelta(days=1)),
|
|
43
37
|
transaction_subtype=fees_d0.transaction_subtype,
|
|
44
38
|
)
|
|
45
39
|
real_fees_d1 = fees_factory.create(
|
|
46
40
|
calculated=False,
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
product=calculated_fees_d1.product,
|
|
42
|
+
fee_date=calculated_fees_d1.fee_date,
|
|
49
43
|
transaction_subtype=calculated_fees_d1.transaction_subtype,
|
|
50
44
|
)
|
|
51
45
|
|
|
@@ -61,6 +55,6 @@ class TestFeesModel:
|
|
|
61
55
|
price = instrument_price_factory.create(instrument=product) # post save must be called to compute fees
|
|
62
56
|
|
|
63
57
|
fees = Fees.objects.get(
|
|
64
|
-
|
|
58
|
+
product=product, fee_date=price.date, calculated=True, transaction_subtype=Fees.Type.MANAGEMENT
|
|
65
59
|
)
|
|
66
60
|
assert fees.total_value == pytest.approx(price.net_value, rel=Decimal(1e-4))
|
|
@@ -250,7 +250,7 @@ class TestTradeProposal:
|
|
|
250
250
|
assert t3.weighting == normalized_t3_weight
|
|
251
251
|
|
|
252
252
|
# Test resetting trades
|
|
253
|
-
def test_reset_trades(self, trade_proposal, instrument_factory, asset_position_factory):
|
|
253
|
+
def test_reset_trades(self, trade_proposal, instrument_factory, instrument_price_factory, asset_position_factory):
|
|
254
254
|
"""
|
|
255
255
|
Verify trades are correctly reset based on effective and target portfolios.
|
|
256
256
|
"""
|
|
@@ -260,7 +260,6 @@ class TestTradeProposal:
|
|
|
260
260
|
i1 = instrument_factory.create(currency=trade_proposal.portfolio.currency)
|
|
261
261
|
i2 = instrument_factory.create(currency=trade_proposal.portfolio.currency)
|
|
262
262
|
i3 = instrument_factory.create(currency=trade_proposal.portfolio.currency)
|
|
263
|
-
|
|
264
263
|
# Build initial effective portfolio constituting only from two positions of i1 and i2
|
|
265
264
|
asset_position_factory.create(
|
|
266
265
|
portfolio=trade_proposal.portfolio, date=effective_date, underlying_instrument=i1, weighting=Decimal("0.7")
|
|
@@ -268,6 +267,9 @@ class TestTradeProposal:
|
|
|
268
267
|
asset_position_factory.create(
|
|
269
268
|
portfolio=trade_proposal.portfolio, date=effective_date, underlying_instrument=i2, weighting=Decimal("0.3")
|
|
270
269
|
)
|
|
270
|
+
instrument_price_factory.create(instrument=i1, date=effective_date)
|
|
271
|
+
instrument_price_factory.create(instrument=i2, date=effective_date)
|
|
272
|
+
instrument_price_factory.create(instrument=i3, date=effective_date)
|
|
271
273
|
|
|
272
274
|
# build the target portfolio
|
|
273
275
|
target_portfolio = PortfolioDTO(
|
wbportfolio/urls.py
CHANGED
|
@@ -30,7 +30,6 @@ router.register(
|
|
|
30
30
|
router.register(r"portfoliorole", viewsets.PortfolioRoleModelViewSet, basename="portfoliorole")
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
router.register(r"transaction", viewsets.TransactionModelViewSet, basename="transaction")
|
|
34
33
|
router.register(r"fees", viewsets.FeesModelViewSet, basename="fees")
|
|
35
34
|
router.register(r"trade", viewsets.TradeModelViewSet, basename="trade")
|
|
36
35
|
router.register(
|
|
@@ -79,8 +78,6 @@ portfolio_router.register(
|
|
|
79
78
|
)
|
|
80
79
|
portfolio_router.register(r"asset", viewsets.AssetPositionPortfolioModelViewSet, basename="portfolio-asset")
|
|
81
80
|
portfolio_router.register(r"contributor", viewsets.ContributorPortfolioChartView, basename="portfolio-contributor")
|
|
82
|
-
portfolio_router.register(r"fees", viewsets.FeesPortfolioModelViewSet, basename="portfolio-fees")
|
|
83
|
-
portfolio_router.register(r"transaction", viewsets.TransactionPortfolioModelViewSet, basename="portfolio-transaction")
|
|
84
81
|
portfolio_router.register(r"trade", viewsets.TradePortfolioModelViewSet, basename="portfolio-trade")
|
|
85
82
|
portfolio_router.register(
|
|
86
83
|
r"instrument", viewsets.InstrumentPortfolioThroughPortfolioModelViewSet, basename="portfolio-instrument"
|
|
@@ -101,9 +98,7 @@ portfolio_router.register(
|
|
|
101
98
|
viewsets.CompositionModelPortfolioPandasView,
|
|
102
99
|
basename="portfolio-modelcompositionpandas",
|
|
103
100
|
)
|
|
104
|
-
|
|
105
|
-
r"feesaggregated", viewsets.FeesAggregatedPortfolioPandasView, basename="portfolio-feesaggregated"
|
|
106
|
-
)
|
|
101
|
+
|
|
107
102
|
portfolio_router.register(
|
|
108
103
|
r"distributionchart",
|
|
109
104
|
viewsets.DistributionChartViewSet,
|
|
@@ -139,6 +134,8 @@ product_router = WBCoreRouter()
|
|
|
139
134
|
product_router.register(r"nominalchart", viewsets.NominalProductChartView, basename="product-nominalchart")
|
|
140
135
|
product_router.register(r"aumchart", viewsets.AUMProductChartView, basename="product-aumchart")
|
|
141
136
|
product_router.register(r"claim", viewsets.ClaimProductModelViewSet, basename="product-claim")
|
|
137
|
+
product_router.register(r"feesaggregated", viewsets.FeesAggregatedProductPandasView, basename="product-feesaggregated")
|
|
138
|
+
product_router.register(r"fees", viewsets.FeesProductModelViewSet, basename="product-fees")
|
|
142
139
|
|
|
143
140
|
# Subrouter for Trade Proposal
|
|
144
141
|
trade_proposal_router = WBCoreRouter()
|
|
@@ -16,6 +16,7 @@ from .registers import RegisterButtonConfig
|
|
|
16
16
|
from .trades import (
|
|
17
17
|
TradeButtonConfig,
|
|
18
18
|
TradeInstrumentButtonConfig,
|
|
19
|
+
TradeTradeProposalButtonConfig
|
|
19
20
|
)
|
|
20
21
|
from .trade_proposals import TradeProposalButtonConfig
|
|
21
22
|
from .reconciliations import AccountReconciliationButtonViewConfig, AccountReconciliationLineButtonViewConfig
|
|
@@ -53,8 +53,8 @@ class InstrumentButtonMixin:
|
|
|
53
53
|
bt.WidgetButton(key="portfolio_trades", label="Trades"),
|
|
54
54
|
bt.WidgetButton(key="instrument_subscriptionsredemptions", label="Subscriptions/Redemptions"),
|
|
55
55
|
bt.WidgetButton(key="instrument_trades", label="Trades (Implemented)"),
|
|
56
|
-
bt.WidgetButton(key="
|
|
57
|
-
bt.WidgetButton(key="
|
|
56
|
+
bt.WidgetButton(key="product_fees", label="Fees"),
|
|
57
|
+
bt.WidgetButton(key="product_aggregatedfees", label="Aggregated Fees"),
|
|
58
58
|
bt.DropDownButton(
|
|
59
59
|
label="Charts",
|
|
60
60
|
icon=WBIcon.UNFOLD.icon,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from rest_framework.reverse import reverse
|
|
2
2
|
from wbcore.contrib.icons import WBIcon
|
|
3
3
|
from wbcore.metadata.configs import buttons as bt
|
|
4
|
+
from wbcore.metadata.configs.buttons.enums import Button
|
|
4
5
|
from wbcore.metadata.configs.buttons.view_config import ButtonViewConfig
|
|
5
6
|
from wbfdm.models import Instrument
|
|
6
7
|
|
|
@@ -58,3 +59,10 @@ class TradeInstrumentButtonConfig(TradeButtonConfig):
|
|
|
58
59
|
)
|
|
59
60
|
)
|
|
60
61
|
return res
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TradeTradeProposalButtonConfig(ButtonViewConfig):
|
|
65
|
+
def get_create_buttons(self):
|
|
66
|
+
return {
|
|
67
|
+
Button.SAVE_AND_CLOSE.value,
|
|
68
|
+
}
|
|
@@ -19,9 +19,9 @@ from .claim import (
|
|
|
19
19
|
from .custodians import CustodianDisplayConfig
|
|
20
20
|
|
|
21
21
|
from .fees import (
|
|
22
|
-
|
|
22
|
+
FeesAggregatedProductPandasDisplayConfig,
|
|
23
23
|
FeesDisplayConfig,
|
|
24
|
-
|
|
24
|
+
FeesProductDisplayConfig,
|
|
25
25
|
)
|
|
26
26
|
from .portfolios import (
|
|
27
27
|
PortfolioDisplayConfig,
|
|
@@ -52,7 +52,6 @@ from .trades import (
|
|
|
52
52
|
TradeTradeProposalDisplayConfig,
|
|
53
53
|
)
|
|
54
54
|
from .trade_proposals import TradeProposalDisplayConfig
|
|
55
|
-
from .transactions import TransactionDisplayConfig, TransactionPortfolioDisplayConfig
|
|
56
55
|
from .portfolio_relationship import (
|
|
57
56
|
PortfolioInstrumentPreferredClassificationThroughDisplayConfig,
|
|
58
57
|
InstrumentPortfolioThroughPortfolioModelDisplayConfig,
|
|
@@ -55,7 +55,7 @@ class FeesDisplayConfig(DisplayViewConfig):
|
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
|
|
58
|
-
class
|
|
58
|
+
class FeesProductDisplayConfig(DisplayViewConfig):
|
|
59
59
|
def get_list_display(self) -> Optional[dp.ListDisplay]:
|
|
60
60
|
return dp.ListDisplay(
|
|
61
61
|
fields=[
|
|
@@ -98,7 +98,7 @@ class FeesPortfolioDisplayConfig(DisplayViewConfig):
|
|
|
98
98
|
dp.Field(key="calculated", label="Calculated"),
|
|
99
99
|
dp.Field(key="currency_fx_rate", label="FX rate", show="open"),
|
|
100
100
|
dp.Field(key="currency", label="Currency", show="open"),
|
|
101
|
-
dp.Field(key="
|
|
101
|
+
dp.Field(key="product", label="Product", show="open"),
|
|
102
102
|
],
|
|
103
103
|
),
|
|
104
104
|
]
|
|
@@ -117,7 +117,7 @@ class FeesPortfolioDisplayConfig(DisplayViewConfig):
|
|
|
117
117
|
)
|
|
118
118
|
|
|
119
119
|
|
|
120
|
-
class
|
|
120
|
+
class FeesAggregatedProductPandasDisplayConfig(DisplayViewConfig):
|
|
121
121
|
def get_list_display(self) -> Optional[dp.ListDisplay]:
|
|
122
122
|
return dp.ListDisplay(
|
|
123
123
|
fields=[
|
|
@@ -28,10 +28,10 @@ from .claim import (
|
|
|
28
28
|
|
|
29
29
|
from .custodians import CustodianEndpointConfig
|
|
30
30
|
from .fees import (
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
FeesAggregatedProductPandasEndpointConfig,
|
|
32
|
+
FeesProductEndpointConfig,
|
|
33
33
|
)
|
|
34
|
-
from .fees import
|
|
34
|
+
from .fees import FeesAggregatedProductPandasEndpointConfig, FeesProductEndpointConfig, FeeEndpointConfig
|
|
35
35
|
|
|
36
36
|
from .portfolios import (
|
|
37
37
|
PortfolioEndpointConfig,
|
|
@@ -67,7 +67,6 @@ from .trades import (
|
|
|
67
67
|
TradeTradeProposalEndpointConfig,
|
|
68
68
|
SubscriptionRedemptionEndpointConfig,
|
|
69
69
|
)
|
|
70
|
-
from .transactions import TransactionEndpointConfig, TransactionPortfolioEndpointConfig
|
|
71
70
|
from .portfolio_relationship import (
|
|
72
71
|
PortfolioInstrumentPreferredClassificationThroughEndpointConfig,
|
|
73
72
|
InstrumentPortfolioThroughPortfolioModelEndpointConfig,
|
|
@@ -6,9 +6,9 @@ class FeeEndpointConfig(EndpointViewConfig):
|
|
|
6
6
|
return None
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class
|
|
9
|
+
class FeesProductEndpointConfig(FeeEndpointConfig):
|
|
10
10
|
pass
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
class
|
|
13
|
+
class FeesAggregatedProductPandasEndpointConfig(EndpointViewConfig):
|
|
14
14
|
pass
|
|
@@ -25,6 +25,5 @@ from .products import PRODUCT_MENUITEM, PRODUCTCUSTOMER_MENUITEM
|
|
|
25
25
|
from .registers import REGISTER_MENUITEM
|
|
26
26
|
from .roles import PORTFOLIOROLE_MENUITEM
|
|
27
27
|
from .trades import SUBSCRIPTION_REDEMPTION_MENUITEM, TRADE_MENUITEM
|
|
28
|
-
from .transactions import TRANSACTION_MENUITEM
|
|
29
28
|
from .portfolio_cash_flow import PORTFOLIO_DAILY_CASH_FLOW
|
|
30
29
|
from .reconciliations import ACCOUNT_RECONCILIATION_MENU_ITEM
|
|
@@ -27,8 +27,8 @@ from .claim import (
|
|
|
27
27
|
from .custodians import CustodianTitleConfig
|
|
28
28
|
|
|
29
29
|
from .fees import (
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
FeesAggregatedProductTitleConfig,
|
|
31
|
+
FeesProductTitleConfig,
|
|
32
32
|
FeesTitleConfig,
|
|
33
33
|
)
|
|
34
34
|
|
|
@@ -60,6 +60,5 @@ from .trades import (
|
|
|
60
60
|
TradePortfolioTitleConfig,
|
|
61
61
|
TradeTitleConfig,
|
|
62
62
|
)
|
|
63
|
-
from .transactions import TransactionPortfolioTitleConfig
|
|
64
63
|
from .esg import ESGMetricAggregationPortfolioPandasTitleConfig
|
|
65
64
|
from .assets_and_net_new_money_progression import AssetAndNetNewMoneyProgressionChartTitleConfig
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from wbcore.metadata.configs.titles import TitleViewConfig
|
|
2
2
|
|
|
3
|
-
from wbportfolio.models import Portfolio
|
|
4
|
-
|
|
5
3
|
|
|
6
4
|
class FeesTitleConfig(TitleViewConfig):
|
|
7
5
|
def get_list_title(self):
|
|
@@ -14,13 +12,11 @@ class FeesTitleConfig(TitleViewConfig):
|
|
|
14
12
|
return "New Fees"
|
|
15
13
|
|
|
16
14
|
|
|
17
|
-
class
|
|
15
|
+
class FeesProductTitleConfig(FeesTitleConfig):
|
|
18
16
|
def get_list_title(self):
|
|
19
|
-
|
|
20
|
-
return f"Fees: {portfolio.name}"
|
|
17
|
+
return f"Fees: {self.view.product}"
|
|
21
18
|
|
|
22
19
|
|
|
23
|
-
class
|
|
20
|
+
class FeesAggregatedProductTitleConfig(TitleViewConfig):
|
|
24
21
|
def get_list_title(self):
|
|
25
|
-
|
|
26
|
-
return f"Aggregated Fees for {portfolio.name}"
|
|
22
|
+
return f"Aggregated Fees for {self.view.product}"
|
wbportfolio/viewsets/mixins.py
CHANGED
|
@@ -2,7 +2,7 @@ from django.shortcuts import get_object_or_404
|
|
|
2
2
|
from django.utils.functional import cached_property
|
|
3
3
|
from wbfdm.models import Instrument
|
|
4
4
|
|
|
5
|
-
from wbportfolio.models import Portfolio, PortfolioRole
|
|
5
|
+
from wbportfolio.models import Portfolio, PortfolioRole, Product
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class UserPortfolioRequestPermissionMixin:
|
|
@@ -10,6 +10,10 @@ class UserPortfolioRequestPermissionMixin:
|
|
|
10
10
|
def instrument(self) -> Instrument:
|
|
11
11
|
return get_object_or_404(Instrument, pk=self.kwargs["instrument_id"])
|
|
12
12
|
|
|
13
|
+
@cached_property
|
|
14
|
+
def product(self) -> Instrument:
|
|
15
|
+
return get_object_or_404(Product, pk=self.kwargs["product_id"])
|
|
16
|
+
|
|
13
17
|
@cached_property
|
|
14
18
|
def portfolio(self) -> Portfolio:
|
|
15
19
|
return get_object_or_404(Portfolio, id=self.kwargs["portfolio_id"])
|
wbportfolio/viewsets/products.py
CHANGED
|
@@ -317,16 +317,16 @@ class ProductPerformanceFeesModelViewSet(
|
|
|
317
317
|
if date_lte is None and date_gte is None:
|
|
318
318
|
date_gte, date_lte = get_start_and_end_date_from_date(date.today())
|
|
319
319
|
|
|
320
|
-
fees = Fees.valid_objects.filter(
|
|
320
|
+
fees = Fees.valid_objects.filter(product=OuterRef("pk"))
|
|
321
321
|
if date_gte:
|
|
322
|
-
fees = fees.filter(
|
|
322
|
+
fees = fees.filter(fee_date__gte=date_gte)
|
|
323
323
|
if date_lte:
|
|
324
|
-
fees = fees.filter(
|
|
324
|
+
fees = fees.filter(fee_date__lte=date_lte)
|
|
325
325
|
|
|
326
326
|
management_fees = Coalesce(
|
|
327
327
|
Subquery(
|
|
328
328
|
fees.filter(transaction_subtype=Fees.Type.MANAGEMENT)
|
|
329
|
-
.values("
|
|
329
|
+
.values("product")
|
|
330
330
|
.annotate(sum_management_fees=Sum("total_value"))
|
|
331
331
|
.values("sum_management_fees")[:1],
|
|
332
332
|
output_field=FloatField(),
|
|
@@ -340,7 +340,7 @@ class ProductPerformanceFeesModelViewSet(
|
|
|
340
340
|
Q(transaction_subtype=Fees.Type.PERFORMANCE)
|
|
341
341
|
| Q(transaction_subtype=Fees.Type.PERFORMANCE_CRYSTALIZED)
|
|
342
342
|
)
|
|
343
|
-
.values("
|
|
343
|
+
.values("product")
|
|
344
344
|
.annotate(sum_performance_fees_net=Sum("total_value"))
|
|
345
345
|
.values("sum_performance_fees_net")[:1],
|
|
346
346
|
output_field=FloatField(),
|
|
@@ -367,7 +367,7 @@ class ProductPerformanceFeesModelViewSet(
|
|
|
367
367
|
sum_total_usd=F("sum_management_fees_usd") + F("sum_performance_fees_net_usd"),
|
|
368
368
|
)
|
|
369
369
|
.select_related("currency")
|
|
370
|
-
.prefetch_related("
|
|
370
|
+
.prefetch_related("fees", "prices")
|
|
371
371
|
)
|
|
372
372
|
return qs
|
|
373
373
|
|
|
@@ -13,9 +13,9 @@ from .claim import (
|
|
|
13
13
|
ProfitAndLossPandasView,
|
|
14
14
|
)
|
|
15
15
|
from .fees import (
|
|
16
|
-
|
|
16
|
+
FeesAggregatedProductPandasView,
|
|
17
17
|
FeesModelViewSet,
|
|
18
|
-
|
|
18
|
+
FeesProductModelViewSet,
|
|
19
19
|
)
|
|
20
20
|
from .rebalancing import RebalancingModelRepresentationViewSet, RebalancerRepresentationViewSet, RebalancerModelViewSet
|
|
21
21
|
from .trade_proposals import (
|
|
@@ -34,8 +34,3 @@ from .trades import (
|
|
|
34
34
|
TradeRepresentationViewSet,
|
|
35
35
|
TradeTradeProposalModelViewSet,
|
|
36
36
|
)
|
|
37
|
-
from .transactions import (
|
|
38
|
-
TransactionModelViewSet,
|
|
39
|
-
TransactionPortfolioModelViewSet,
|
|
40
|
-
TransactionRepresentationViewSet,
|
|
41
|
-
)
|
|
@@ -8,28 +8,28 @@ from wbcore.pandas import fields as pf
|
|
|
8
8
|
from wbcore.permissions.permissions import InternalUserPermissionMixin
|
|
9
9
|
from wbcore.serializers import decorator
|
|
10
10
|
from wbcore.utils.strings import format_number
|
|
11
|
+
from wbcore.viewsets import ModelViewSet
|
|
11
12
|
|
|
12
|
-
from wbportfolio.filters import FeesAggregatedFilter, FeesFilter,
|
|
13
|
+
from wbportfolio.filters import FeesAggregatedFilter, FeesFilter, FeesProductFilterSet
|
|
13
14
|
from wbportfolio.models import Fees
|
|
14
15
|
from wbportfolio.serializers import FeesModelSerializer
|
|
15
16
|
|
|
16
17
|
from ..configs import (
|
|
17
18
|
FeeEndpointConfig,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
FeesAggregatedProductPandasDisplayConfig,
|
|
20
|
+
FeesAggregatedProductPandasEndpointConfig,
|
|
21
|
+
FeesAggregatedProductTitleConfig,
|
|
21
22
|
FeesButtonConfig,
|
|
22
23
|
FeesDisplayConfig,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
FeesProductDisplayConfig,
|
|
25
|
+
FeesProductEndpointConfig,
|
|
26
|
+
FeesProductTitleConfig,
|
|
26
27
|
FeesTitleConfig,
|
|
27
28
|
)
|
|
28
29
|
from ..mixins import UserPortfolioRequestPermissionMixin
|
|
29
|
-
from .transactions import TransactionModelViewSet
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
class FeesModelViewSet(
|
|
32
|
+
class FeesModelViewSet(UserPortfolioRequestPermissionMixin, InternalUserPermissionMixin, ModelViewSet):
|
|
33
33
|
filter_backends = (
|
|
34
34
|
DjangoFilterBackend,
|
|
35
35
|
filters.OrderingFilter,
|
|
@@ -56,27 +56,27 @@ class FeesModelViewSet(TransactionModelViewSet):
|
|
|
56
56
|
|
|
57
57
|
def get_queryset(self):
|
|
58
58
|
if self.is_manager:
|
|
59
|
-
return super().get_queryset().select_related("import_source", "
|
|
59
|
+
return super().get_queryset().select_related("import_source", "product")
|
|
60
60
|
return Fees.objects.none()
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
class
|
|
64
|
-
IDENTIFIER = "wbportfolio:
|
|
63
|
+
class FeesProductModelViewSet(FeesModelViewSet):
|
|
64
|
+
IDENTIFIER = "wbportfolio:product-fees"
|
|
65
65
|
|
|
66
|
-
filterset_class =
|
|
66
|
+
filterset_class = FeesProductFilterSet
|
|
67
67
|
|
|
68
|
-
display_config_class =
|
|
69
|
-
title_config_class =
|
|
70
|
-
endpoint_config_class =
|
|
68
|
+
display_config_class = FeesProductDisplayConfig
|
|
69
|
+
title_config_class = FeesProductTitleConfig
|
|
70
|
+
endpoint_config_class = FeesProductEndpointConfig
|
|
71
71
|
|
|
72
72
|
def get_queryset(self):
|
|
73
73
|
if self.is_portfolio_manager:
|
|
74
|
-
return super().get_queryset().filter(
|
|
74
|
+
return super().get_queryset().filter(product=self.product)
|
|
75
75
|
|
|
76
76
|
return Fees.objects.none()
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
class
|
|
79
|
+
class FeesAggregatedProductPandasView(
|
|
80
80
|
UserPortfolioRequestPermissionMixin, InternalUserPermissionMixin, ExportPandasAPIViewSet
|
|
81
81
|
):
|
|
82
82
|
IDENTIFIER = "wbportfolio:aggregetedfees"
|
|
@@ -85,9 +85,9 @@ class FeesAggregatedPortfolioPandasView(
|
|
|
85
85
|
|
|
86
86
|
queryset = Fees.valid_objects.all()
|
|
87
87
|
|
|
88
|
-
display_config_class =
|
|
89
|
-
title_config_class =
|
|
90
|
-
endpoint_config_class =
|
|
88
|
+
display_config_class = FeesAggregatedProductPandasDisplayConfig
|
|
89
|
+
title_config_class = FeesAggregatedProductTitleConfig
|
|
90
|
+
endpoint_config_class = FeesAggregatedProductPandasEndpointConfig
|
|
91
91
|
|
|
92
92
|
pandas_fields = pf.PandasFields(
|
|
93
93
|
fields=(
|
|
@@ -153,7 +153,7 @@ class FeesAggregatedPortfolioPandasView(
|
|
|
153
153
|
|
|
154
154
|
def get_queryset(self):
|
|
155
155
|
if self.is_portfolio_manager:
|
|
156
|
-
qs = super().get_queryset().filter(
|
|
156
|
+
qs = super().get_queryset().filter(product=self.product)
|
|
157
157
|
return qs.annotate(
|
|
158
158
|
currency_fx_rate_usd=CurrencyFXRates.get_fx_rates_subquery(
|
|
159
159
|
"fee_date", currency="currency", lookup_expr="exact"
|
|
@@ -98,6 +98,7 @@ class TradeProposalModelViewSet(CloneMixin, RiskCheckViewSetMixin, InternalUserP
|
|
|
98
98
|
def reset(self, request, pk=None):
|
|
99
99
|
trade_proposal = get_object_or_404(TradeProposal, pk=pk)
|
|
100
100
|
if trade_proposal.status == TradeProposal.Status.DRAFT:
|
|
101
|
+
trade_proposal.trades.all().delete()
|
|
101
102
|
trade_proposal.reset_trades()
|
|
102
103
|
return Response({"send": True})
|
|
103
104
|
return Response({"status": "Trade proposal is not Draft"}, status=status.HTTP_400_BAD_REQUEST)
|
|
@@ -64,6 +64,7 @@ from ..configs import (
|
|
|
64
64
|
TradePortfolioEndpointConfig,
|
|
65
65
|
TradePortfolioTitleConfig,
|
|
66
66
|
TradeTitleConfig,
|
|
67
|
+
TradeTradeProposalButtonConfig,
|
|
67
68
|
TradeTradeProposalDisplayConfig,
|
|
68
69
|
TradeTradeProposalEndpointConfig,
|
|
69
70
|
)
|
|
@@ -388,6 +389,7 @@ class TradeTradeProposalModelViewSet(
|
|
|
388
389
|
display_config_class = TradeTradeProposalDisplayConfig
|
|
389
390
|
endpoint_config_class = TradeTradeProposalEndpointConfig
|
|
390
391
|
serializer_class = TradeTradeProposalModelSerializer
|
|
392
|
+
button_config_class = TradeTradeProposalButtonConfig
|
|
391
393
|
|
|
392
394
|
@cached_property
|
|
393
395
|
def trade_proposal(self):
|