wbcommission 2.2.1__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 wbcommission might be problematic. Click here for more details.

Files changed (76) hide show
  1. wbcommission/__init__.py +1 -0
  2. wbcommission/admin/__init__.py +4 -0
  3. wbcommission/admin/accounts.py +22 -0
  4. wbcommission/admin/commission.py +85 -0
  5. wbcommission/admin/rebate.py +7 -0
  6. wbcommission/analytics/__init__.py +0 -0
  7. wbcommission/analytics/marginality.py +181 -0
  8. wbcommission/apps.py +5 -0
  9. wbcommission/dynamic_preferences_registry.py +0 -0
  10. wbcommission/factories/__init__.py +9 -0
  11. wbcommission/factories/commission.py +100 -0
  12. wbcommission/factories/rebate.py +16 -0
  13. wbcommission/filters/__init__.py +7 -0
  14. wbcommission/filters/rebate.py +187 -0
  15. wbcommission/filters/signals.py +44 -0
  16. wbcommission/generators/__init__.py +2 -0
  17. wbcommission/generators/rebate_generator.py +93 -0
  18. wbcommission/migrations/0001_initial.py +299 -0
  19. wbcommission/migrations/0002_commissionrule_remove_accountcustomer_account_and_more.py +395 -0
  20. wbcommission/migrations/0003_alter_commission_account.py +24 -0
  21. wbcommission/migrations/0004_rebate_audit_log.py +19 -0
  22. wbcommission/migrations/0005_alter_rebate_audit_log.py +20 -0
  23. wbcommission/migrations/0006_commissionrule_consider_zero_percent_for_exclusion.py +21 -0
  24. wbcommission/migrations/0007_remove_commission_unique_crm_recipient_account_and_more.py +50 -0
  25. wbcommission/migrations/0008_alter_commission_options_alter_commission_order.py +26 -0
  26. wbcommission/migrations/__init__.py +0 -0
  27. wbcommission/models/__init__.py +9 -0
  28. wbcommission/models/account_service.py +217 -0
  29. wbcommission/models/commission.py +679 -0
  30. wbcommission/models/rebate.py +319 -0
  31. wbcommission/models/signals.py +45 -0
  32. wbcommission/permissions.py +6 -0
  33. wbcommission/reports/__init__.py +0 -0
  34. wbcommission/reports/audit_report.py +51 -0
  35. wbcommission/reports/customer_report.py +299 -0
  36. wbcommission/reports/utils.py +30 -0
  37. wbcommission/serializers/__init__.py +3 -0
  38. wbcommission/serializers/commissions.py +26 -0
  39. wbcommission/serializers/rebate.py +87 -0
  40. wbcommission/serializers/signals.py +27 -0
  41. wbcommission/tests/__init__.py +0 -0
  42. wbcommission/tests/analytics/__init__.py +0 -0
  43. wbcommission/tests/analytics/test_marginality.py +253 -0
  44. wbcommission/tests/conftest.py +89 -0
  45. wbcommission/tests/models/__init__.py +0 -0
  46. wbcommission/tests/models/mixins.py +22 -0
  47. wbcommission/tests/models/test_account_service.py +293 -0
  48. wbcommission/tests/models/test_commission.py +587 -0
  49. wbcommission/tests/models/test_rebate.py +136 -0
  50. wbcommission/tests/signals.py +0 -0
  51. wbcommission/tests/test_permissions.py +66 -0
  52. wbcommission/tests/viewsets/__init__.py +0 -0
  53. wbcommission/tests/viewsets/test_rebate.py +76 -0
  54. wbcommission/urls.py +42 -0
  55. wbcommission/viewsets/__init__.py +7 -0
  56. wbcommission/viewsets/buttons/__init__.py +2 -0
  57. wbcommission/viewsets/buttons/rebate.py +46 -0
  58. wbcommission/viewsets/buttons/signals.py +53 -0
  59. wbcommission/viewsets/commissions.py +21 -0
  60. wbcommission/viewsets/display/__init__.py +5 -0
  61. wbcommission/viewsets/display/commissions.py +21 -0
  62. wbcommission/viewsets/display/rebate.py +117 -0
  63. wbcommission/viewsets/endpoints/__init__.py +4 -0
  64. wbcommission/viewsets/endpoints/commissions.py +0 -0
  65. wbcommission/viewsets/endpoints/rebate.py +21 -0
  66. wbcommission/viewsets/menu/__init__.py +1 -0
  67. wbcommission/viewsets/menu/commissions.py +0 -0
  68. wbcommission/viewsets/menu/rebate.py +13 -0
  69. wbcommission/viewsets/mixins.py +39 -0
  70. wbcommission/viewsets/rebate.py +481 -0
  71. wbcommission/viewsets/titles/__init__.py +1 -0
  72. wbcommission/viewsets/titles/commissions.py +0 -0
  73. wbcommission/viewsets/titles/rebate.py +11 -0
  74. wbcommission-2.2.1.dist-info/METADATA +11 -0
  75. wbcommission-2.2.1.dist-info/RECORD +76 -0
  76. wbcommission-2.2.1.dist-info/WHEEL +5 -0
@@ -0,0 +1,253 @@
1
+ import pytest
2
+ from faker import Faker
3
+ from pandas.tseries.offsets import BDay
4
+ from rest_framework.reverse import reverse
5
+ from rest_framework.test import APIClient, APIRequestFactory
6
+ from wbcommission.analytics.marginality import MarginalityCalculator
7
+ from wbcommission.factories import CommissionTypeFactory, RebateFactory
8
+ from wbcommission.viewsets.rebate import RebateProductMarginalityViewSet
9
+ from wbportfolio.factories import FeesFactory, InstrumentPriceFactory, ProductFactory
10
+ from wbportfolio.models import Product
11
+
12
+ fake = Faker()
13
+
14
+
15
+ def _create_fixture(product, val_date, net_value=100, outstanding_shares=100):
16
+ management = CommissionTypeFactory.create(key="management")
17
+ perforance = CommissionTypeFactory.create(key="performance")
18
+ i1 = InstrumentPriceFactory.create(
19
+ instrument=product, net_value=net_value, outstanding_shares=outstanding_shares, calculated=False, date=val_date
20
+ ).net_value # create a price of AUM 100*100
21
+ management_fees_1 = FeesFactory.create(
22
+ linked_product=product, transaction_date=val_date, transaction_subtype="MANAGEMENT", calculated=False
23
+ ).total_value
24
+ performance_fees_1 = FeesFactory.create(
25
+ linked_product=product, transaction_date=val_date, transaction_subtype="PERFORMANCE", calculated=False
26
+ ).total_value
27
+ performance_crys_fees_1 = FeesFactory.create(
28
+ linked_product=product,
29
+ transaction_date=val_date,
30
+ transaction_subtype="PERFORMANCE_CRYSTALIZED",
31
+ calculated=False,
32
+ ).total_value
33
+ management_rebate_1 = RebateFactory.create(product=product, date=val_date, commission_type=management).value
34
+ performance_rebate_1 = RebateFactory.create(product=product, date=val_date, commission_type=perforance).value
35
+ if val_date.weekday() == 0:
36
+ return (
37
+ i1,
38
+ management_fees_1 / 3.0,
39
+ performance_fees_1 / 3.0,
40
+ performance_crys_fees_1 / 3.0,
41
+ float(management_rebate_1) / 3.0,
42
+ float(performance_rebate_1) / 3.0,
43
+ )
44
+ else:
45
+ return (
46
+ i1,
47
+ management_fees_1,
48
+ performance_fees_1,
49
+ performance_crys_fees_1,
50
+ float(management_rebate_1),
51
+ float(performance_rebate_1),
52
+ )
53
+
54
+
55
+ @pytest.mark.django_db
56
+ class TestMarginalityCalculator:
57
+ @pytest.fixture
58
+ def marginality_calculator(self):
59
+ product = ProductFactory.create()
60
+ start = (fake.date_object() + BDay(0)).date()
61
+ end = (start + BDay(1)).date()
62
+
63
+ (
64
+ self.i1,
65
+ self.management_fees_1,
66
+ self.performance_fees_1,
67
+ self.performance_crys_fees_1,
68
+ self.management_rebate_1,
69
+ self.performance_rebate_1,
70
+ ) = _create_fixture(product, start)
71
+ self.product_id = product.id
72
+ self.start = start
73
+ self.end = end
74
+ return MarginalityCalculator(Product.objects.filter(id=product.id), start, end)
75
+
76
+ def test_fees(self, marginality_calculator):
77
+ assert marginality_calculator.management_fees.loc[self.product_id] == pytest.approx(
78
+ self.management_fees_1, rel=1e-4
79
+ )
80
+ assert marginality_calculator.performance_fees.loc[self.product_id] == pytest.approx(
81
+ self.performance_fees_1 + self.performance_crys_fees_1, rel=1e-4
82
+ )
83
+ assert marginality_calculator.total_fees.loc[self.product_id] == pytest.approx(
84
+ self.performance_fees_1 + self.performance_crys_fees_1 + self.management_fees_1, rel=1e-4
85
+ )
86
+
87
+ def test_rebates(self, marginality_calculator):
88
+ assert marginality_calculator.management_rebates.loc[self.product_id] == pytest.approx(
89
+ self.management_rebate_1, rel=1e-4
90
+ )
91
+ assert marginality_calculator.performance_rebates.loc[self.product_id] == pytest.approx(
92
+ self.performance_rebate_1, rel=1e-4
93
+ )
94
+ assert marginality_calculator.total_rebates.loc[self.product_id] == pytest.approx(
95
+ self.performance_rebate_1 + self.management_rebate_1, rel=1e-4
96
+ )
97
+
98
+ def test_fees_usd(self, marginality_calculator):
99
+ assert marginality_calculator.management_fees_usd.loc[self.product_id] == pytest.approx(
100
+ self.management_fees_1, rel=1e-4
101
+ )
102
+ assert marginality_calculator.performance_fees_usd.loc[self.product_id] == pytest.approx(
103
+ self.performance_fees_1 + self.performance_crys_fees_1, rel=1e-4
104
+ )
105
+ assert marginality_calculator.total_fees_usd.loc[self.product_id] == pytest.approx(
106
+ self.performance_fees_1 + self.performance_crys_fees_1 + self.management_fees_1, rel=1e-4
107
+ )
108
+
109
+ def test_rebates_usd(self, marginality_calculator):
110
+ assert marginality_calculator.management_rebates_usd.loc[self.product_id] == pytest.approx(
111
+ self.management_rebate_1, rel=1e-4
112
+ )
113
+ assert marginality_calculator.performance_rebates_usd.loc[self.product_id] == pytest.approx(
114
+ self.performance_rebate_1, rel=1e-4
115
+ )
116
+ assert marginality_calculator.total_rebates_usd.loc[self.product_id] == pytest.approx(
117
+ self.performance_rebate_1 + self.management_rebate_1, rel=1e-4
118
+ )
119
+
120
+ def test_marginality(self, marginality_calculator):
121
+ assert marginality_calculator.management_marginality.loc[self.product_id] == pytest.approx(
122
+ self.management_fees_1 - self.management_rebate_1, rel=1e-4
123
+ )
124
+ assert marginality_calculator.performance_marginality.loc[self.product_id] == pytest.approx(
125
+ self.performance_fees_1 + self.performance_crys_fees_1 - self.performance_rebate_1, rel=1e-4
126
+ )
127
+ assert marginality_calculator.total_marginality.loc[self.product_id] == pytest.approx(
128
+ (self.performance_fees_1 + self.performance_crys_fees_1 + self.management_fees_1)
129
+ - (self.performance_rebate_1 + self.management_rebate_1),
130
+ rel=1e-4,
131
+ )
132
+
133
+ def test_marginality_usd(self, marginality_calculator):
134
+ assert marginality_calculator.management_marginality_usd.loc[self.product_id] == pytest.approx(
135
+ marginality_calculator.management_marginality.loc[self.product_id], rel=1e-4
136
+ )
137
+ assert marginality_calculator.performance_marginality_usd.loc[self.product_id] == pytest.approx(
138
+ marginality_calculator.performance_marginality.loc[self.product_id], rel=1e-4
139
+ )
140
+ assert marginality_calculator.total_marginality_usd.loc[self.product_id] == pytest.approx(
141
+ marginality_calculator.total_marginality.loc[self.product_id], rel=1e-4
142
+ )
143
+
144
+ def test_marginality_percent(self, marginality_calculator):
145
+ assert marginality_calculator.management_marginality_percent.loc[self.product_id] == pytest.approx(
146
+ (self.management_fees_1 - float(self.management_rebate_1)) / self.management_fees_1, rel=1e-4
147
+ )
148
+ assert marginality_calculator.performance_marginality_percent.loc[self.product_id] == pytest.approx(
149
+ (self.performance_fees_1 + self.performance_crys_fees_1 - float(self.performance_rebate_1))
150
+ / (self.performance_fees_1 + self.performance_crys_fees_1),
151
+ rel=1e-4,
152
+ )
153
+ assert marginality_calculator.total_marginality_percent.loc[self.product_id] == pytest.approx(
154
+ (
155
+ (self.performance_fees_1 + self.performance_crys_fees_1 + self.management_fees_1)
156
+ - (float(self.performance_rebate_1) + float(self.management_rebate_1))
157
+ )
158
+ / (self.performance_fees_1 + self.performance_crys_fees_1 + self.management_fees_1),
159
+ rel=1e-4,
160
+ )
161
+
162
+ def test_marginality_percent_usd(self, marginality_calculator):
163
+ assert marginality_calculator.management_marginality_percent_usd.loc[self.product_id] == pytest.approx(
164
+ marginality_calculator.management_marginality_percent.loc[self.product_id], rel=1e-4
165
+ )
166
+ assert marginality_calculator.performance_marginality_percent_usd.loc[self.product_id] == pytest.approx(
167
+ marginality_calculator.performance_marginality_percent.loc[self.product_id], rel=1e-4
168
+ )
169
+ assert marginality_calculator.total_marginality_percent_usd.loc[self.product_id] == pytest.approx(
170
+ marginality_calculator.total_marginality_percent.loc[self.product_id], rel=1e-4
171
+ )
172
+
173
+ def test_get_net_marginality(self, marginality_calculator):
174
+ assert marginality_calculator.get_net_marginality("management").loc[self.product_id] == pytest.approx(
175
+ (self.management_fees_1 - float(self.management_rebate_1)) / (100 * 100) * 360, rel=1e-4
176
+ )
177
+
178
+ def test_get_aggregated_net_marginality(self, marginality_calculator):
179
+ product_1 = ProductFactory.create()
180
+ product_2 = ProductFactory.create()
181
+ start = (fake.date_object() + BDay(0)).date()
182
+ end = (start + BDay(1)).date()
183
+
184
+ (
185
+ i1,
186
+ management_fees_1,
187
+ performance_fees_1,
188
+ performance_crys_fees_1,
189
+ management_rebate_1,
190
+ performance_rebate_1,
191
+ ) = _create_fixture(product_1, start)
192
+ (
193
+ i2,
194
+ management_fees_2,
195
+ performance_fees_2,
196
+ performance_crys_fees_2,
197
+ management_rebate_2,
198
+ performance_rebate_2,
199
+ ) = _create_fixture(product_2, start, outstanding_shares=1000)
200
+ total_aum = 100 * 100 + 100 * 1000
201
+ calculator = MarginalityCalculator(Product.objects.filter(id__in=[product_1.id, product_2.id]), start, end)
202
+ assert calculator.get_aggregated_net_marginality("management") == pytest.approx(
203
+ ((management_fees_1 + management_fees_2) - (management_rebate_1 + management_rebate_2)) / total_aum * 360,
204
+ rel=1e-4,
205
+ )
206
+
207
+ def test_rebate_marginality_view(self, marginality_calculator, super_user):
208
+ request = APIRequestFactory().get("")
209
+ request.query_params = {}
210
+ request.GET = {"date_gte": self.start.strftime("%Y-%m-%d"), "date_lte": self.end.strftime("%Y-%m-%d")}
211
+ request.user = super_user
212
+ viewset = RebateProductMarginalityViewSet(request=request)
213
+ assert set(viewset._get_dataframe().columns) == {
214
+ "id",
215
+ "title",
216
+ "currency_symbol",
217
+ "base_management_fees_percent",
218
+ "management_fees",
219
+ "management_rebates",
220
+ "management_marginality",
221
+ "management_marginality_percent",
222
+ "base_performance_fees_percent",
223
+ "performance_fees",
224
+ "performance_rebates",
225
+ "performance_marginality",
226
+ "total_fees",
227
+ "total_rebates",
228
+ "total_marginality_percent",
229
+ "total_fees_usd",
230
+ "total_rebates_usd",
231
+ "total_marginality_usd",
232
+ "net_management_marginality",
233
+ "net_performance_marginality",
234
+ }
235
+
236
+ # regression tests to check that without date range the view doesn't break
237
+ url = reverse("wbcommission:rebatemarginalitytable-list", args=[])
238
+ api_client = APIClient()
239
+ api_client.force_authenticate(super_user)
240
+
241
+ response = api_client.options(url)
242
+ assert response.status_code == 200
243
+
244
+ response = api_client.get(url)
245
+ assert response.status_code == 200
246
+
247
+ # check that with proper date range the viewset return some content
248
+ response = api_client.options(url, data=request.GET)
249
+ assert response.status_code == 200
250
+
251
+ response = api_client.get(url, data=request.GET)
252
+ assert response.status_code == 200
253
+ assert response.json()["results"]
@@ -0,0 +1,89 @@
1
+ from wbcore.tests.conftest import * # isort: skip # type: ignore
2
+ from django.apps import apps
3
+ from django.db.models.signals import pre_migrate
4
+ from pytest_factoryboy import register
5
+ from wbcore.contrib.authentication.factories import (
6
+ AuthenticatedPersonFactory,
7
+ SuperUserFactory,
8
+ UserFactory,
9
+ )
10
+ from wbcore.contrib.currency.factories import CurrencyFactory, CurrencyFXRatesFactory
11
+ from wbcore.contrib.directory.factories.entries import (
12
+ CompanyFactory,
13
+ CompanyTypeFactory,
14
+ CustomerStatusFactory,
15
+ EntryFactory,
16
+ PersonFactory,
17
+ )
18
+ from wbcore.contrib.geography.factories import (
19
+ CityFactory,
20
+ ContinentFactory,
21
+ CountryFactory,
22
+ StateFactory,
23
+ )
24
+ from wbcore.contrib.geography.tests.signals import app_pre_migration
25
+ from wbcrm.factories import AccountFactory, AccountRoleFactory, AccountRoleTypeFactory
26
+ from wbfdm.factories import ExchangeFactory, InstrumentFactory, InstrumentTypeFactory
27
+ from wbportfolio.factories import (
28
+ ClaimFactory,
29
+ CustomerTradeFactory,
30
+ FeesFactory,
31
+ InstrumentPriceFactory,
32
+ PortfolioFactory,
33
+ ProductFactory,
34
+ ProductPortfolioRoleFactory,
35
+ )
36
+
37
+ from ..factories import (
38
+ AccountTypeRoleCommissionFactory,
39
+ CommissionExclusionRuleFactory,
40
+ CommissionFactory,
41
+ CommissionRoleFactory,
42
+ CommissionTypeFactory,
43
+ PortfolioRoleCommissionFactory,
44
+ RebateFactory,
45
+ )
46
+
47
+ register(AccountFactory)
48
+ register(AccountRoleFactory)
49
+ register(AccountRoleTypeFactory)
50
+
51
+ register(InstrumentFactory)
52
+ register(InstrumentTypeFactory)
53
+ register(ExchangeFactory)
54
+ register(ProductFactory)
55
+ register(ProductPortfolioRoleFactory)
56
+ register(FeesFactory)
57
+ register(PortfolioFactory)
58
+ register(InstrumentPriceFactory)
59
+ register(ClaimFactory)
60
+ register(CustomerTradeFactory)
61
+ register(CurrencyFXRatesFactory)
62
+
63
+ register(CurrencyFactory)
64
+ register(CityFactory)
65
+ register(StateFactory)
66
+ register(CountryFactory)
67
+ register(ContinentFactory)
68
+
69
+ register(CompanyFactory)
70
+ register(EntryFactory)
71
+ register(PersonFactory)
72
+ register(CustomerStatusFactory)
73
+ register(CompanyTypeFactory)
74
+
75
+ register(AuthenticatedPersonFactory, "authenticated_person")
76
+ register(UserFactory)
77
+ register(SuperUserFactory, "superuser")
78
+
79
+ register(RebateFactory)
80
+ register(CommissionTypeFactory)
81
+ register(CommissionFactory)
82
+ register(CommissionExclusionRuleFactory)
83
+ register(AccountTypeRoleCommissionFactory, "account_role_type_commission")
84
+ register(CommissionRoleFactory)
85
+ register(PortfolioRoleCommissionFactory, "portfolio_role_commission")
86
+ from .signals import *
87
+
88
+ pre_migrate.connect(app_pre_migration, sender=apps.get_app_config("wbportfolio"))
89
+ from .signals import * # noqa: F401
File without changes
@@ -0,0 +1,22 @@
1
+ import pytest
2
+ from wbcommission.factories import CommissionTypeFactory
3
+ from wbcommission.models.account_service import AccountRebateManager
4
+ from wbcrm.factories import AccountFactory
5
+
6
+
7
+ class AccountManagerFixture:
8
+ @pytest.fixture()
9
+ def performance_account_manager(self):
10
+ commission_type = CommissionTypeFactory.create(name="PERFORMANCE")
11
+ account_manager = AccountRebateManager(AccountFactory.create(), commission_type.key)
12
+ account_manager.initialize()
13
+
14
+ return account_manager
15
+
16
+ @pytest.fixture()
17
+ def management_account_manager(self):
18
+ commission_type = CommissionTypeFactory.create(name="MANAGEMENT")
19
+ account_manager = AccountRebateManager(AccountFactory.create(), commission_type.key)
20
+ account_manager.initialize()
21
+
22
+ return account_manager
@@ -0,0 +1,293 @@
1
+ import random
2
+ from decimal import Decimal
3
+
4
+ import pytest
5
+ from faker import Faker
6
+ from pandas.tseries.offsets import BDay
7
+ from wbcommission.models.account_service import AccountRebateManager
8
+ from wbportfolio.models import Claim
9
+
10
+ from .mixins import AccountManagerFixture
11
+
12
+ fake = Faker()
13
+
14
+
15
+ @pytest.mark.django_db
16
+ class TestAccountService(AccountManagerFixture):
17
+ def test_get_commission_pool(
18
+ self, management_account_manager, fees_factory, claim_factory, customer_trade_factory
19
+ ):
20
+ mngt_fees = fees_factory.create(transaction_subtype="MANAGEMENT")
21
+ perf_fees = fees_factory.create( # noqa
22
+ transaction_subtype="PERFORMANCE",
23
+ transaction_date=mngt_fees.transaction_date,
24
+ linked_product=mngt_fees.linked_product,
25
+ ) # noqa
26
+ claim_factory.create(
27
+ account=management_account_manager.root_account,
28
+ trade=customer_trade_factory.create(underlying_instrument=mngt_fees.linked_product),
29
+ status="APPROVED",
30
+ )
31
+ management_account_manager.initialize()
32
+
33
+ assert (
34
+ management_account_manager.get_commission_pool(mngt_fees.linked_product, mngt_fees.transaction_date)
35
+ == mngt_fees.total_value
36
+ )
37
+
38
+ def test_get_commission_pool_performance(
39
+ self, performance_account_manager, fees_factory, claim_factory, customer_trade_factory
40
+ ):
41
+ mngt_fees = fees_factory.create(transaction_subtype="MANAGEMENT")
42
+ perf_fees = fees_factory.create(
43
+ transaction_subtype="PERFORMANCE",
44
+ transaction_date=mngt_fees.transaction_date,
45
+ linked_product=mngt_fees.linked_product,
46
+ )
47
+ perf2_fees = fees_factory.create(
48
+ transaction_subtype="PERFORMANCE_CRYSTALIZED",
49
+ transaction_date=mngt_fees.transaction_date,
50
+ linked_product=mngt_fees.linked_product,
51
+ )
52
+
53
+ claim_factory.create(
54
+ account=performance_account_manager.root_account,
55
+ trade=customer_trade_factory.create(underlying_instrument=mngt_fees.linked_product),
56
+ status="APPROVED",
57
+ )
58
+ performance_account_manager.initialize()
59
+
60
+ assert (
61
+ performance_account_manager.get_commission_pool(mngt_fees.linked_product, mngt_fees.transaction_date)
62
+ == perf_fees.total_value + perf2_fees.total_value
63
+ )
64
+
65
+ def test_get_commission_pool_with_calculated(
66
+ self, management_account_manager, fees_factory, claim_factory, customer_trade_factory
67
+ ):
68
+ # check that in case there is only calculate, we return the calculated fees value, but in case there is calculated and real fees, we use the real fees value
69
+ calculated_fees = fees_factory.create(calculated=True, transaction_subtype="MANAGEMENT")
70
+ claim_factory.create(
71
+ account=management_account_manager.root_account,
72
+ trade=customer_trade_factory.create(underlying_instrument=calculated_fees.linked_product),
73
+ status="APPROVED",
74
+ )
75
+ management_account_manager.initialize()
76
+
77
+ assert (
78
+ management_account_manager.get_commission_pool(
79
+ calculated_fees.linked_product, calculated_fees.transaction_date
80
+ )
81
+ == calculated_fees.total_value
82
+ )
83
+ # Check that is there is a non calculated fees, it is used instead of the calculated one
84
+ fees = fees_factory.create(transaction_subtype=calculated_fees.transaction_subtype, calculated=False)
85
+ claim_factory.create(
86
+ account=management_account_manager.root_account,
87
+ trade=customer_trade_factory.create(underlying_instrument=fees.linked_product),
88
+ status="APPROVED",
89
+ )
90
+ management_account_manager.initialize()
91
+
92
+ assert (
93
+ management_account_manager.get_commission_pool(fees.linked_product, fees.transaction_date)
94
+ == fees.total_value
95
+ )
96
+
97
+ @pytest.mark.parametrize("val_date", [fake.past_date()])
98
+ def test_get_terminal_account_holding_ratio(
99
+ self, account_factory, claim_factory, instrument_price_factory, product_factory, val_date, commission_type
100
+ ):
101
+ product1 = product_factory.create()
102
+ product2 = product_factory.create()
103
+ val_date = (val_date + BDay(0)).date()
104
+ next_val_date = (val_date + BDay(1)).date()
105
+
106
+ product1_price_val_date = instrument_price_factory.create(
107
+ date=val_date, instrument=product1, outstanding_shares=Decimal(2e4), calculated=True
108
+ )
109
+
110
+ product1_price_next_val_date = instrument_price_factory.create(
111
+ date=next_val_date, instrument=product1, outstanding_shares=Decimal(2e4), calculated=True
112
+ )
113
+
114
+ product2_price_val_date = instrument_price_factory.create(
115
+ date=val_date, instrument=product2, outstanding_shares=Decimal(2e4), calculated=True
116
+ )
117
+ product2_price_next_val_date = instrument_price_factory.create(
118
+ date=next_val_date, instrument=product2, outstanding_shares=Decimal(2e4), calculated=True
119
+ )
120
+
121
+ parent_account = account_factory.create(is_terminal_account=False, is_active=True)
122
+ claim_factory.create(
123
+ trade__value_date=val_date,
124
+ trade__transaction_date=(val_date - BDay(1)).date(),
125
+ trade__underlying_instrument=product1,
126
+ status=Claim.Status.APPROVED,
127
+ account=parent_account,
128
+ ) # ignore this claim because it's attached to a non terminal account
129
+
130
+ child_terminal_account = account_factory.create(
131
+ parent=parent_account, is_terminal_account=True, is_active=True
132
+ )
133
+ claim_factory.create( # ignore this claim because it's not approved
134
+ trade__value_date=val_date,
135
+ trade__transaction_date=(val_date - BDay(1)).date(),
136
+ account=child_terminal_account,
137
+ trade__underlying_instrument=product1,
138
+ status=random.choice([Claim.Status.DRAFT, Claim.Status.WITHDRAWN, Claim.Status.PENDING]),
139
+ )
140
+ unvalid_child_terminal_account = account_factory.create(parent=parent_account, is_active=False)
141
+ claim_factory.create(
142
+ trade__value_date=val_date,
143
+ trade__transaction_date=(val_date - BDay(1)).date(),
144
+ trade__underlying_instrument=product1,
145
+ status=Claim.Status.APPROVED,
146
+ account=unvalid_child_terminal_account,
147
+ ) # ignore this claim as the attached account is not active
148
+
149
+ valid_claim1 = claim_factory.create(
150
+ trade__value_date=val_date,
151
+ trade__transaction_date=(val_date - BDay(1)).date(),
152
+ trade__underlying_instrument=product1,
153
+ status=Claim.Status.APPROVED,
154
+ account=child_terminal_account,
155
+ )
156
+ valid_claim2 = claim_factory.create(
157
+ trade__value_date=next_val_date,
158
+ trade__transaction_date=(next_val_date - BDay(1)).date(),
159
+ trade__underlying_instrument=product1,
160
+ status=Claim.Status.APPROVED,
161
+ account=child_terminal_account,
162
+ )
163
+ valid_claim3 = claim_factory.create(
164
+ trade__value_date=val_date,
165
+ trade__transaction_date=(val_date - BDay(1)).date(),
166
+ trade__underlying_instrument=product2,
167
+ status=Claim.Status.APPROVED,
168
+ account=child_terminal_account,
169
+ )
170
+ product1_price_val_date.refresh_from_db()
171
+ product1_price_next_val_date.refresh_from_db()
172
+ product2_price_val_date.refresh_from_db()
173
+ product2_price_next_val_date.refresh_from_db()
174
+
175
+ account_manager = AccountRebateManager(parent_account, commission_type.key)
176
+ account_manager.initialize()
177
+ assert account_manager.get_terminal_account_holding_ratio(
178
+ child_terminal_account, product1, val_date
179
+ ) == pytest.approx((valid_claim1.shares / product1_price_val_date.outstanding_shares), rel=Decimal(1e-6))
180
+ assert account_manager.get_terminal_account_holding_ratio(
181
+ child_terminal_account, product1, next_val_date
182
+ ) == pytest.approx(
183
+ (valid_claim1.shares + valid_claim2.shares) / product1_price_next_val_date.outstanding_shares,
184
+ rel=Decimal(1e-6),
185
+ )
186
+ assert account_manager.get_terminal_account_holding_ratio(
187
+ child_terminal_account, product2, val_date
188
+ ) == pytest.approx((valid_claim3.shares / product2_price_val_date.outstanding_shares), rel=Decimal(1e-6))
189
+ assert account_manager.get_terminal_account_holding_ratio(
190
+ child_terminal_account, product2, next_val_date
191
+ ) == pytest.approx((valid_claim3.shares / product2_price_next_val_date.outstanding_shares), rel=Decimal(1e-6))
192
+
193
+ @pytest.mark.parametrize("val_date", [fake.date_object()])
194
+ def test_get_root_account_total_holding(
195
+ self, account_factory, claim_factory, product_factory, instrument_price_factory, val_date, commission_type
196
+ ):
197
+ product1 = product_factory.create()
198
+ product2 = product_factory.create()
199
+ val_date = (val_date + BDay(0)).date()
200
+ next_val_date = (val_date + BDay(1)).date()
201
+ product1_price_val_date = instrument_price_factory.create(date=val_date, instrument=product1, calculated=False)
202
+ product1_price_next_val_date = instrument_price_factory.create(
203
+ date=next_val_date, instrument=product1, calculated=False
204
+ )
205
+ product2_price_val_date = instrument_price_factory.create(date=val_date, instrument=product2, calculated=False)
206
+ product2_price_next_val_date = instrument_price_factory.create(
207
+ date=next_val_date, instrument=product2, calculated=False
208
+ )
209
+
210
+ parent_account = account_factory.create(is_terminal_account=False, is_active=True)
211
+ claim_factory.create(
212
+ trade__value_date=val_date,
213
+ trade__transaction_date=(val_date - BDay(1)).date(),
214
+ trade__underlying_instrument=product1,
215
+ status=Claim.Status.APPROVED,
216
+ account=parent_account,
217
+ ) # ignore this claim because it's attached to a non terminal account
218
+
219
+ child_terminal_account = account_factory.create(
220
+ parent=parent_account, is_terminal_account=True, is_active=True
221
+ )
222
+ claim_factory.create( # ignore this claim because it's not approved
223
+ trade__value_date=val_date,
224
+ trade__transaction_date=(val_date - BDay(1)).date(),
225
+ account=child_terminal_account,
226
+ trade__underlying_instrument=product1,
227
+ status=random.choice([Claim.Status.DRAFT, Claim.Status.WITHDRAWN, Claim.Status.PENDING]),
228
+ )
229
+ unvalid_child_terminal_account = account_factory.create(parent=parent_account, is_active=False)
230
+ claim_factory.create(
231
+ trade__value_date=val_date,
232
+ trade__transaction_date=(val_date - BDay(1)).date(),
233
+ trade__underlying_instrument=product1,
234
+ status=Claim.Status.APPROVED,
235
+ account=unvalid_child_terminal_account,
236
+ ) # ignore this claim as the attached account is not active
237
+
238
+ valid_claim1 = claim_factory.create(
239
+ trade__value_date=val_date,
240
+ trade__transaction_date=(val_date - BDay(1)).date(),
241
+ trade__underlying_instrument=product1,
242
+ status=Claim.Status.APPROVED,
243
+ account=child_terminal_account,
244
+ )
245
+ valid_claim2 = claim_factory.create(
246
+ trade__value_date=next_val_date,
247
+ trade__transaction_date=(next_val_date - BDay(1)).date(),
248
+ trade__underlying_instrument=product1,
249
+ status=Claim.Status.APPROVED,
250
+ account=child_terminal_account,
251
+ )
252
+ valid_claim3 = claim_factory.create(
253
+ trade__value_date=val_date,
254
+ trade__transaction_date=(val_date - BDay(1)).date(),
255
+ trade__underlying_instrument=product2,
256
+ status=Claim.Status.APPROVED,
257
+ account=child_terminal_account,
258
+ )
259
+
260
+ account_manager = AccountRebateManager(parent_account, commission_type.key)
261
+ account_manager.initialize()
262
+
263
+ valid_claim1_aum = ( # noqa
264
+ valid_claim1.shares
265
+ * product1_price_val_date.net_value
266
+ * product1_price_val_date.currency_fx_rate_to_usd.value
267
+ )
268
+ valid_claim2_aum = ( # noqa
269
+ valid_claim2.shares
270
+ * product1_price_next_val_date.net_value
271
+ * product1_price_next_val_date.currency_fx_rate_to_usd.value
272
+ )
273
+ valid_claim3_aum = ( # noqa
274
+ valid_claim3.shares
275
+ * product2_price_val_date.net_value
276
+ * product2_price_val_date.currency_fx_rate_to_usd.value
277
+ )
278
+ assert account_manager.get_root_account_total_holding(val_date) == (
279
+ pytest.approx(
280
+ (
281
+ valid_claim1.shares * product1_price_val_date._net_value_usd
282
+ + valid_claim3.shares * product2_price_val_date._net_value_usd
283
+ ),
284
+ rel=Decimal(1e-6),
285
+ )
286
+ )
287
+ assert account_manager.get_root_account_total_holding(next_val_date) == (
288
+ pytest.approx(
289
+ (valid_claim1.shares + valid_claim2.shares) * product1_price_next_val_date._net_value_usd
290
+ + valid_claim3.shares * product2_price_next_val_date._net_value_usd,
291
+ rel=Decimal(1e-8),
292
+ )
293
+ )