wbportfolio 1.52.0__py2.py3-none-any.whl → 1.59.4__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.

Files changed (273) hide show
  1. wbportfolio/admin/__init__.py +3 -1
  2. wbportfolio/admin/indexes.py +1 -1
  3. wbportfolio/admin/orders/__init__.py +2 -0
  4. wbportfolio/admin/orders/order_proposals.py +16 -0
  5. wbportfolio/admin/orders/orders.py +32 -0
  6. wbportfolio/admin/portfolio.py +11 -5
  7. wbportfolio/admin/product_groups.py +1 -1
  8. wbportfolio/admin/products.py +2 -1
  9. wbportfolio/admin/{transactions/rebalancing.py → rebalancing.py} +1 -1
  10. wbportfolio/admin/transactions/__init__.py +0 -2
  11. wbportfolio/admin/transactions/dividends.py +40 -4
  12. wbportfolio/admin/transactions/fees.py +24 -14
  13. wbportfolio/admin/transactions/trades.py +34 -27
  14. wbportfolio/analysis/claims.py +5 -6
  15. wbportfolio/api_clients/ubs.py +162 -0
  16. wbportfolio/constants.py +1 -0
  17. wbportfolio/contrib/company_portfolio/configs/display.py +22 -10
  18. wbportfolio/contrib/company_portfolio/configs/previews.py +3 -3
  19. wbportfolio/contrib/company_portfolio/filters.py +10 -10
  20. wbportfolio/contrib/company_portfolio/models.py +69 -39
  21. wbportfolio/contrib/company_portfolio/scripts.py +7 -2
  22. wbportfolio/contrib/company_portfolio/serializers.py +32 -22
  23. wbportfolio/contrib/company_portfolio/tasks.py +12 -1
  24. wbportfolio/contrib/company_portfolio/tests/conftest.py +2 -2
  25. wbportfolio/defaults/fees/default.py +7 -15
  26. wbportfolio/factories/__init__.py +2 -2
  27. wbportfolio/factories/assets.py +1 -1
  28. wbportfolio/factories/dividends.py +8 -3
  29. wbportfolio/factories/fees.py +8 -4
  30. wbportfolio/factories/orders/__init__.py +2 -0
  31. wbportfolio/factories/orders/order_proposals.py +21 -0
  32. wbportfolio/factories/orders/orders.py +34 -0
  33. wbportfolio/factories/portfolios.py +2 -1
  34. wbportfolio/factories/product_groups.py +3 -3
  35. wbportfolio/factories/products.py +3 -3
  36. wbportfolio/factories/rebalancing.py +1 -1
  37. wbportfolio/factories/trades.py +12 -16
  38. wbportfolio/filters/assets.py +18 -4
  39. wbportfolio/filters/orders/__init__.py +2 -0
  40. wbportfolio/filters/orders/order_proposals.py +55 -0
  41. wbportfolio/filters/orders/orders.py +11 -0
  42. wbportfolio/filters/portfolios.py +38 -1
  43. wbportfolio/filters/positions.py +0 -1
  44. wbportfolio/filters/transactions/__init__.py +1 -2
  45. wbportfolio/filters/transactions/fees.py +5 -12
  46. wbportfolio/filters/transactions/trades.py +16 -8
  47. wbportfolio/filters/transactions/utils.py +42 -0
  48. wbportfolio/import_export/backends/ubs/__init__.py +1 -0
  49. wbportfolio/import_export/backends/ubs/asset_position.py +6 -7
  50. wbportfolio/import_export/backends/ubs/fees.py +10 -20
  51. wbportfolio/import_export/backends/ubs/instrument_price.py +6 -6
  52. wbportfolio/import_export/backends/ubs/trade.py +48 -0
  53. wbportfolio/import_export/backends/utils.py +0 -17
  54. wbportfolio/import_export/handlers/asset_position.py +22 -10
  55. wbportfolio/import_export/handlers/dividend.py +8 -8
  56. wbportfolio/import_export/handlers/fees.py +13 -23
  57. wbportfolio/import_export/handlers/orders.py +71 -0
  58. wbportfolio/import_export/handlers/trade.py +53 -77
  59. wbportfolio/import_export/parsers/default_mapping.py +1 -1
  60. wbportfolio/import_export/parsers/jpmorgan/customer_trade.py +2 -2
  61. wbportfolio/import_export/parsers/jpmorgan/fees.py +4 -4
  62. wbportfolio/import_export/parsers/jpmorgan/strategy.py +59 -85
  63. wbportfolio/import_export/parsers/jpmorgan/valuation.py +2 -2
  64. wbportfolio/import_export/parsers/leonteq/customer_trade.py +5 -5
  65. wbportfolio/import_export/parsers/leonteq/fees.py +11 -7
  66. wbportfolio/import_export/parsers/leonteq/trade.py +2 -6
  67. wbportfolio/import_export/parsers/natixis/d1_fees.py +2 -2
  68. wbportfolio/import_export/parsers/natixis/dividend.py +4 -9
  69. wbportfolio/import_export/parsers/natixis/equity.py +22 -4
  70. wbportfolio/import_export/parsers/natixis/fees.py +7 -9
  71. wbportfolio/import_export/parsers/natixis/utils.py +13 -19
  72. wbportfolio/import_export/parsers/sg_lux/customer_trade_pending_slk.py +1 -1
  73. wbportfolio/import_export/parsers/sg_lux/equity.py +10 -10
  74. wbportfolio/import_export/parsers/sg_lux/fees.py +2 -2
  75. wbportfolio/import_export/parsers/sg_lux/perf_fees.py +2 -2
  76. wbportfolio/import_export/parsers/sg_lux/sylk.py +12 -11
  77. wbportfolio/import_export/parsers/sg_lux/utils.py +2 -2
  78. wbportfolio/import_export/parsers/sg_lux/valuation.py +4 -2
  79. wbportfolio/import_export/parsers/societe_generale/strategy.py +5 -5
  80. wbportfolio/import_export/parsers/tellco/customer_trade.py +2 -1
  81. wbportfolio/import_export/parsers/tellco/valuation.py +4 -3
  82. wbportfolio/import_export/parsers/ubs/api/fees.py +2 -2
  83. wbportfolio/import_export/parsers/ubs/api/trade.py +39 -0
  84. wbportfolio/import_export/parsers/ubs/customer_trade.py +7 -5
  85. wbportfolio/import_export/parsers/ubs/equity.py +3 -2
  86. wbportfolio/import_export/parsers/ubs/valuation.py +2 -1
  87. wbportfolio/import_export/parsers/vontobel/customer_trade.py +2 -3
  88. wbportfolio/import_export/parsers/vontobel/historical_customer_trade.py +0 -1
  89. wbportfolio/import_export/parsers/vontobel/management_fees.py +12 -20
  90. wbportfolio/import_export/parsers/vontobel/performance_fees.py +5 -8
  91. wbportfolio/import_export/parsers/vontobel/valuation_api.py +4 -1
  92. wbportfolio/import_export/resources/trades.py +3 -3
  93. wbportfolio/import_export/utils.py +3 -1
  94. wbportfolio/jinja2/wbportfolio/sql/aum_nnm.sql +2 -2
  95. wbportfolio/metric/backends/base.py +2 -2
  96. wbportfolio/migrations/0059_fees_unique_fees.py +1 -1
  97. wbportfolio/migrations/0077_remove_transaction_currency_and_more.py +622 -0
  98. wbportfolio/migrations/0078_trade_drift_factor.py +26 -0
  99. wbportfolio/migrations/0079_alter_trade_drift_factor.py +19 -0
  100. wbportfolio/migrations/0080_alter_trade_drift_factor_alter_trade_weighting.py +19 -0
  101. wbportfolio/migrations/0081_alter_trade_drift_factor.py +19 -0
  102. wbportfolio/migrations/0082_remove_tradeproposal_creator_and_more.py +93 -0
  103. wbportfolio/migrations/0083_order_alter_trade_options_and_more.py +181 -0
  104. wbportfolio/migrations/0084_orderproposal_min_order_value.py +25 -0
  105. wbportfolio/migrations/0085_order_desired_target_weight.py +26 -0
  106. wbportfolio/migrations/0086_orderproposal_total_cash_weight.py +19 -0
  107. wbportfolio/migrations/0087_product_order_routing_custodian_adapter.py +94 -0
  108. wbportfolio/migrations/0088_orderproposal_total_effective_portfolio_contribution.py +19 -0
  109. wbportfolio/migrations/0089_orderproposal_min_weighting.py +71 -0
  110. wbportfolio/migrations/0090_dividendtransaction_price_fx_portfolio_and_more.py +44 -0
  111. wbportfolio/migrations/0091_remove_order_execution_confirmed_and_more.py +32 -0
  112. wbportfolio/migrations/0092_order_quantization_error_alter_orderproposal_status.py +49 -0
  113. wbportfolio/migrations/0093_remove_portfolioportfoliothroughmodel_unique_primary_and_more.py +35 -0
  114. wbportfolio/models/__init__.py +2 -0
  115. wbportfolio/models/adjustments.py +1 -1
  116. wbportfolio/models/asset.py +28 -170
  117. wbportfolio/models/builder.py +323 -0
  118. wbportfolio/models/custodians.py +3 -3
  119. wbportfolio/models/exceptions.py +1 -1
  120. wbportfolio/models/graphs/portfolio.py +1 -1
  121. wbportfolio/models/graphs/utils.py +11 -11
  122. wbportfolio/models/mixins/instruments.py +7 -0
  123. wbportfolio/models/mixins/liquidity_stress_test.py +4 -4
  124. wbportfolio/models/orders/__init__.py +2 -0
  125. wbportfolio/models/orders/order_proposals.py +1414 -0
  126. wbportfolio/models/orders/orders.py +410 -0
  127. wbportfolio/models/portfolio.py +311 -289
  128. wbportfolio/models/portfolio_relationship.py +6 -0
  129. wbportfolio/models/products.py +12 -0
  130. wbportfolio/models/{transactions/rebalancing.py → rebalancing.py} +40 -27
  131. wbportfolio/models/roles.py +4 -10
  132. wbportfolio/models/transactions/__init__.py +0 -4
  133. wbportfolio/models/transactions/claim.py +7 -6
  134. wbportfolio/models/transactions/dividends.py +42 -5
  135. wbportfolio/models/transactions/fees.py +55 -22
  136. wbportfolio/models/transactions/trades.py +121 -442
  137. wbportfolio/models/transactions/transactions.py +78 -158
  138. wbportfolio/models/utils.py +100 -1
  139. wbportfolio/order_routing/__init__.py +35 -0
  140. wbportfolio/order_routing/adapters/__init__.py +65 -0
  141. wbportfolio/order_routing/adapters/ubs.py +195 -0
  142. wbportfolio/order_routing/router.py +33 -0
  143. wbportfolio/order_routing/tests/__init__.py +0 -0
  144. wbportfolio/order_routing/tests/test_router.py +110 -0
  145. wbportfolio/permissions.py +7 -0
  146. wbportfolio/pms/analytics/portfolio.py +17 -9
  147. wbportfolio/pms/analytics/utils.py +9 -0
  148. wbportfolio/pms/trading/__init__.py +0 -1
  149. wbportfolio/pms/trading/optimizer.py +61 -0
  150. wbportfolio/pms/typing.py +198 -63
  151. wbportfolio/rebalancing/base.py +12 -1
  152. wbportfolio/rebalancing/decorators.py +1 -1
  153. wbportfolio/rebalancing/models/composite.py +4 -8
  154. wbportfolio/rebalancing/models/equally_weighted.py +13 -11
  155. wbportfolio/rebalancing/models/market_capitalization_weighted.py +21 -14
  156. wbportfolio/rebalancing/models/model_portfolio.py +14 -18
  157. wbportfolio/risk_management/backends/__init__.py +1 -0
  158. wbportfolio/risk_management/backends/controversy_portfolio.py +2 -2
  159. wbportfolio/risk_management/backends/esg_aggregation_portfolio.py +64 -0
  160. wbportfolio/risk_management/backends/exposure_portfolio.py +4 -4
  161. wbportfolio/risk_management/backends/instrument_list_portfolio.py +3 -3
  162. wbportfolio/risk_management/tests/test_esg_aggregation_portfolio.py +49 -0
  163. wbportfolio/risk_management/tests/test_exposure_portfolio.py +1 -1
  164. wbportfolio/risk_management/tests/test_stop_loss_instrument.py +2 -2
  165. wbportfolio/risk_management/tests/test_stop_loss_portfolio.py +1 -1
  166. wbportfolio/serializers/__init__.py +1 -0
  167. wbportfolio/serializers/orders/__init__.py +2 -0
  168. wbportfolio/serializers/orders/order_proposals.py +115 -0
  169. wbportfolio/serializers/orders/orders.py +283 -0
  170. wbportfolio/serializers/portfolios.py +7 -7
  171. wbportfolio/serializers/positions.py +2 -2
  172. wbportfolio/serializers/rebalancing.py +1 -1
  173. wbportfolio/serializers/signals.py +9 -12
  174. wbportfolio/serializers/transactions/__init__.py +1 -10
  175. wbportfolio/serializers/transactions/claim.py +2 -2
  176. wbportfolio/serializers/transactions/dividends.py +37 -9
  177. wbportfolio/serializers/transactions/fees.py +39 -10
  178. wbportfolio/serializers/transactions/trades.py +55 -157
  179. wbportfolio/tasks.py +43 -5
  180. wbportfolio/tests/analysis/__init__.py +0 -0
  181. wbportfolio/tests/analysis/test_claims.py +85 -0
  182. wbportfolio/tests/conftest.py +12 -12
  183. wbportfolio/tests/models/orders/__init__.py +0 -0
  184. wbportfolio/tests/models/orders/test_order_proposals.py +1046 -0
  185. wbportfolio/tests/models/test_assets.py +7 -3
  186. wbportfolio/tests/models/test_imports.py +9 -13
  187. wbportfolio/tests/models/test_portfolios.py +102 -95
  188. wbportfolio/tests/models/test_products.py +11 -0
  189. wbportfolio/tests/models/test_splits.py +1 -6
  190. wbportfolio/tests/models/test_utils.py +140 -0
  191. wbportfolio/tests/models/transactions/test_fees.py +7 -13
  192. wbportfolio/tests/models/transactions/test_rebalancing.py +5 -5
  193. wbportfolio/tests/models/transactions/test_trades.py +0 -20
  194. wbportfolio/tests/pms/test_analytics.py +22 -3
  195. wbportfolio/tests/rebalancing/test_models.py +51 -57
  196. wbportfolio/tests/signals.py +10 -20
  197. wbportfolio/tests/tests.py +3 -1
  198. wbportfolio/tests/viewsets/test_products.py +1 -0
  199. wbportfolio/urls.py +10 -13
  200. wbportfolio/viewsets/__init__.py +9 -4
  201. wbportfolio/viewsets/assets.py +3 -204
  202. wbportfolio/viewsets/charts/__init__.py +6 -1
  203. wbportfolio/viewsets/charts/assets.py +344 -154
  204. wbportfolio/viewsets/configs/buttons/__init__.py +2 -2
  205. wbportfolio/viewsets/configs/buttons/assets.py +1 -1
  206. wbportfolio/viewsets/configs/buttons/mixins.py +4 -4
  207. wbportfolio/viewsets/configs/buttons/portfolios.py +45 -1
  208. wbportfolio/viewsets/configs/buttons/products.py +32 -2
  209. wbportfolio/viewsets/configs/display/__init__.py +2 -5
  210. wbportfolio/viewsets/configs/display/assets.py +6 -19
  211. wbportfolio/viewsets/configs/display/fees.py +3 -3
  212. wbportfolio/viewsets/configs/display/portfolios.py +5 -5
  213. wbportfolio/viewsets/configs/display/products.py +1 -1
  214. wbportfolio/viewsets/configs/display/rebalancing.py +2 -2
  215. wbportfolio/viewsets/configs/display/reconciliations.py +4 -4
  216. wbportfolio/viewsets/configs/display/trades.py +1 -189
  217. wbportfolio/viewsets/configs/endpoints/__init__.py +3 -7
  218. wbportfolio/viewsets/configs/endpoints/fees.py +2 -2
  219. wbportfolio/viewsets/configs/endpoints/trades.py +0 -41
  220. wbportfolio/viewsets/configs/menu/__init__.py +1 -1
  221. wbportfolio/viewsets/configs/menu/orders.py +11 -0
  222. wbportfolio/viewsets/configs/titles/__init__.py +2 -3
  223. wbportfolio/viewsets/configs/titles/fees.py +4 -8
  224. wbportfolio/viewsets/esg.py +3 -5
  225. wbportfolio/viewsets/mixins.py +5 -1
  226. wbportfolio/viewsets/orders/__init__.py +6 -0
  227. wbportfolio/viewsets/orders/configs/__init__.py +4 -0
  228. wbportfolio/viewsets/orders/configs/buttons/__init__.py +2 -0
  229. wbportfolio/viewsets/orders/configs/buttons/order_proposals.py +188 -0
  230. wbportfolio/viewsets/orders/configs/buttons/orders.py +113 -0
  231. wbportfolio/viewsets/orders/configs/displays/__init__.py +2 -0
  232. wbportfolio/viewsets/orders/configs/displays/order_proposals.py +157 -0
  233. wbportfolio/viewsets/orders/configs/displays/orders.py +232 -0
  234. wbportfolio/viewsets/orders/configs/endpoints/__init__.py +2 -0
  235. wbportfolio/viewsets/orders/configs/endpoints/order_proposals.py +21 -0
  236. wbportfolio/viewsets/orders/configs/endpoints/orders.py +28 -0
  237. wbportfolio/viewsets/orders/configs/titles/__init__.py +0 -0
  238. wbportfolio/viewsets/orders/configs/titles/orders.py +0 -0
  239. wbportfolio/viewsets/orders/order_proposals.py +252 -0
  240. wbportfolio/viewsets/orders/orders.py +277 -0
  241. wbportfolio/viewsets/portfolios.py +36 -12
  242. wbportfolio/viewsets/positions.py +3 -2
  243. wbportfolio/viewsets/products.py +6 -6
  244. wbportfolio/viewsets/{transactions/rebalancing.py → rebalancing.py} +2 -2
  245. wbportfolio/viewsets/transactions/__init__.py +3 -14
  246. wbportfolio/viewsets/transactions/fees.py +22 -22
  247. wbportfolio/viewsets/transactions/trades.py +1 -180
  248. {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/METADATA +3 -1
  249. {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/RECORD +252 -203
  250. {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/WHEEL +1 -1
  251. wbportfolio/admin/transactions/transactions.py +0 -38
  252. wbportfolio/factories/transactions.py +0 -22
  253. wbportfolio/fdm/tasks.py +0 -13
  254. wbportfolio/filters/transactions/transactions.py +0 -99
  255. wbportfolio/models/transactions/expiry.py +0 -7
  256. wbportfolio/models/transactions/trade_proposals.py +0 -704
  257. wbportfolio/pms/trading/handler.py +0 -161
  258. wbportfolio/serializers/transactions/expiry.py +0 -18
  259. wbportfolio/serializers/transactions/trade_proposals.py +0 -76
  260. wbportfolio/serializers/transactions/transactions.py +0 -85
  261. wbportfolio/tests/models/transactions/test_trade_proposals.py +0 -410
  262. wbportfolio/viewsets/configs/buttons/trade_proposals.py +0 -66
  263. wbportfolio/viewsets/configs/display/trade_proposals.py +0 -100
  264. wbportfolio/viewsets/configs/display/transactions.py +0 -55
  265. wbportfolio/viewsets/configs/endpoints/trade_proposals.py +0 -18
  266. wbportfolio/viewsets/configs/endpoints/transactions.py +0 -14
  267. wbportfolio/viewsets/configs/menu/transactions.py +0 -9
  268. wbportfolio/viewsets/configs/titles/transactions.py +0 -9
  269. wbportfolio/viewsets/signals.py +0 -43
  270. wbportfolio/viewsets/transactions/trade_proposals.py +0 -139
  271. wbportfolio/viewsets/transactions/transactions.py +0 -122
  272. /wbportfolio/{fdm → api_clients}/__init__.py +0 -0
  273. {wbportfolio-1.52.0.dist-info → wbportfolio-1.59.4.dist-info}/licenses/LICENSE +0 -0
@@ -1,35 +1,31 @@
1
1
  import random
2
+ from datetime import timedelta
3
+ from decimal import Decimal
2
4
 
3
5
  import factory
4
6
  from faker import Faker
5
- from pandas._libs.tslibs.offsets import BDay
6
7
 
7
- from wbportfolio.models import Trade, TradeProposal
8
-
9
- from .transactions import TransactionFactory
8
+ from wbportfolio.models import Trade
10
9
 
11
10
  fake = Faker()
12
11
 
13
12
 
14
- class TradeFactory(TransactionFactory):
13
+ class TradeFactory(factory.django.DjangoModelFactory):
15
14
  class Meta:
16
15
  model = Trade
17
16
 
17
+ currency_fx_rate = Decimal(1.0)
18
+ fees = Decimal(0.0)
19
+ portfolio = factory.SubFactory("wbportfolio.factories.PortfolioFactory")
20
+ underlying_instrument = factory.SubFactory("wbfdm.factories.InstrumentFactory")
21
+ currency = factory.SubFactory("wbcore.contrib.currency.factories.CurrencyFactory")
22
+ transaction_date = factory.Faker("date_object")
23
+ value_date = factory.LazyAttribute(lambda o: o.transaction_date + timedelta(days=1))
18
24
  bank = factory.Faker("company")
19
25
  marked_for_deletion = False
20
26
  shares = factory.Faker("pydecimal", min_value=10, max_value=1000, right_digits=4)
21
27
  price = factory.LazyAttribute(lambda o: random.randint(10, 10000))
22
- # trade_proposal = factory.SubFactory("wbportfolio.factories.TradeProposalFactory")
23
-
24
-
25
- class TradeProposalFactory(factory.django.DjangoModelFactory):
26
- class Meta:
27
- model = TradeProposal
28
-
29
- trade_date = factory.LazyAttribute(lambda o: (fake.date_object() + BDay(1)).date())
30
- comment = factory.Faker("paragraph")
31
- portfolio = factory.SubFactory("wbportfolio.factories.PortfolioFactory")
32
- creator = factory.SubFactory("wbcore.contrib.directory.factories.PersonFactory")
28
+ # order_proposal = factory.SubFactory("wbportfolio.factories.OrderProposalFactory")
33
29
 
34
30
 
35
31
  class CustomerTradeFactory(TradeFactory):
@@ -127,7 +127,7 @@ class AssetPositionFilter(wb_filters.FilterSet):
127
127
  )
128
128
 
129
129
  underlying_instrument__is_cash = wb_filters.BooleanFilter(
130
- label="With Cash", exclude=True, lookup_expr="exact", field_name="underlying_instrument__is_cash"
130
+ label="Cash", lookup_expr="exact", method="filter_is_cash"
131
131
  )
132
132
  underlying_instrument__instrument_type = wb_filters.ModelChoiceFilter(
133
133
  label="Instrument Type",
@@ -230,6 +230,13 @@ class AssetPositionFilter(wb_filters.FilterSet):
230
230
  return queryset.filter(underlying_instrument__in=value.get_classified_instruments())
231
231
  return queryset
232
232
 
233
+ def filter_is_cash(self, queryset, name, value):
234
+ if value is True:
235
+ return queryset.filter(Q(underlying_quote__is_cash=True) | Q(underlying_quote__is_cash_equivalent=True))
236
+ if value is False:
237
+ return queryset.filter(Q(underlying_quote__is_cash=False) & Q(underlying_quote__is_cash_equivalent=False))
238
+ return queryset
239
+
233
240
  def filter_portfolio_instrument(self, queryset, name, value):
234
241
  if value:
235
242
  return queryset.filter(
@@ -400,8 +407,8 @@ class DistributionFilter(wb_filters.FilterSet):
400
407
 
401
408
  group_by = wb_filters.ChoiceFilter(
402
409
  label="Group By",
403
- choices=AssetPositionGroupBy.choices(),
404
- initial=AssetPositionGroupBy.INDUSTRY.name,
410
+ choices=AssetPositionGroupBy.choices,
411
+ initial=AssetPositionGroupBy.INDUSTRY.value,
405
412
  method="fake_filter",
406
413
  clearable=False,
407
414
  required=True,
@@ -427,6 +434,14 @@ class DistributionFilter(wb_filters.FilterSet):
427
434
  endpoint=ClassificationGroup.get_representation_endpoint(),
428
435
  value_key=ClassificationGroup.get_representation_value_key(),
429
436
  label_key=ClassificationGroup.get_representation_label_key(),
437
+ depends_on=[{"field": "group_by", "options": {"activates_on": [AssetPositionGroupBy.INDUSTRY.value]}}],
438
+ )
439
+
440
+ group_by_classification_height = wb_filters.NumberFilter(
441
+ method="fake_filter",
442
+ label="Classification Height",
443
+ initial=0,
444
+ depends_on=[{"field": "group_by", "options": {"activates_on": [AssetPositionGroupBy.INDUSTRY.value]}}],
430
445
  )
431
446
 
432
447
  class Meta:
@@ -441,7 +456,6 @@ def get_default_hedged_currency(field, request, view):
441
456
 
442
457
  class ContributionChartFilter(wb_filters.FilterSet):
443
458
  date = wb_filters.FinancialPerformanceDateRangeFilter(
444
- method=wb_filters.DateRangeFilter.base_date_range_filter_method,
445
459
  label="Date Range",
446
460
  required=True,
447
461
  clearable=False,
@@ -0,0 +1,2 @@
1
+ from .orders import OrderFilterSet
2
+ from .order_proposals import OrderProposalFilterSet
@@ -0,0 +1,55 @@
1
+ from django.db.models import Exists, OuterRef
2
+ from wbcore import filters as wb_filters
3
+
4
+ from wbportfolio.models import InstrumentPortfolioThroughModel, OrderProposal
5
+
6
+
7
+ class OrderProposalFilterSet(wb_filters.FilterSet):
8
+ has_custodian_adapter = wb_filters.BooleanFilter(
9
+ method="filter_has_custodian_adapter", label="Has Custodian Adapter"
10
+ )
11
+ waiting_for_input = wb_filters.BooleanFilter(method="filter_waiting_for_input", label="Waiting for Input")
12
+ is_automatic_rebalancing = wb_filters.BooleanFilter(
13
+ method="filter_is_automatic_rebalancing", label="Automatic Rebalancing"
14
+ )
15
+
16
+ def filter_has_custodian_adapter(self, queryset, name, value):
17
+ queryset = queryset.annotate(
18
+ has_custodian_adapter=Exists(
19
+ InstrumentPortfolioThroughModel.objects.filter(
20
+ portfolio=OuterRef("portfolio"), instrument__net_asset_value_computation_method_path__isnull=False
21
+ )
22
+ )
23
+ )
24
+ if value is True:
25
+ queryset = queryset.filter(has_custodian_adapter=True)
26
+ elif value is False:
27
+ queryset = queryset.filter(has_custodian_adapter=False)
28
+ return queryset
29
+
30
+ def filter_waiting_for_input(self, queryset, name, value):
31
+ input_status = [OrderProposal.Status.PENDING, OrderProposal.Status.DRAFT, OrderProposal.Status.APPROVED]
32
+ if value is True:
33
+ queryset = queryset.filter(status__in=input_status)
34
+ elif value is False:
35
+ queryset = queryset.exclude(status__in=input_status)
36
+ return queryset
37
+
38
+ def filter_is_automatic_rebalancing(self, queryset, name, value):
39
+ if value is True:
40
+ queryset = queryset.filter(rebalancing_model__isnull=False)
41
+ elif value is False:
42
+ queryset = queryset.filter(rebalancing_model__isnull=True)
43
+ return queryset
44
+
45
+ class Meta:
46
+ model = OrderProposal
47
+ fields = {
48
+ "trade_date": ["exact"],
49
+ "status": ["exact"],
50
+ "rebalancing_model": ["exact"],
51
+ "portfolio": ["exact"],
52
+ "creator": ["exact"],
53
+ "approver": ["exact"],
54
+ "execution_status": ["exact"],
55
+ }
@@ -0,0 +1,11 @@
1
+ from wbcore import filters as wb_filters
2
+
3
+ from wbportfolio.models import Order
4
+
5
+
6
+ class OrderFilterSet(wb_filters.FilterSet):
7
+ has_warnings = wb_filters.BooleanFilter()
8
+
9
+ class Meta:
10
+ model = Order
11
+ fields = {"underlying_instrument": ["exact"], "order_type": ["exact"]}
@@ -1,8 +1,11 @@
1
+ from datetime import date, timedelta
2
+
3
+ from django.db.models import Exists, OuterRef
1
4
  from wbcore import filters as wb_filters
2
5
  from wbfdm.models import Instrument
3
6
 
4
7
  from wbportfolio.filters.assets import get_latest_asset_position
5
- from wbportfolio.models import Portfolio
8
+ from wbportfolio.models import AssetPosition, Portfolio
6
9
 
7
10
 
8
11
  class PortfolioFilterSet(wb_filters.FilterSet):
@@ -16,12 +19,46 @@ class PortfolioFilterSet(wb_filters.FilterSet):
16
19
  filter_params={"is_managed": True},
17
20
  method="filter_instrument",
18
21
  )
22
+ modeled_after = wb_filters.ModelChoiceFilter(
23
+ label="Modeled After",
24
+ queryset=Portfolio.objects.all(),
25
+ endpoint=Portfolio.get_representation_endpoint(),
26
+ value_key=Portfolio.get_representation_value_key(),
27
+ label_key=Portfolio.get_representation_label_key(),
28
+ method="filter_modeled_after",
29
+ )
30
+ invests_in = wb_filters.ModelChoiceFilter(
31
+ label="Invests In",
32
+ queryset=Instrument.objects.all(),
33
+ endpoint=Instrument.get_representation_endpoint(),
34
+ value_key=Instrument.get_representation_value_key(),
35
+ label_key=Instrument.get_representation_label_key(),
36
+ filter_params={"is_investable_universe": True},
37
+ method="filter_invests_in",
38
+ )
39
+
40
+ def filter_modeled_after(self, queryset, name, value):
41
+ if value:
42
+ modeled_after_portfolio_ids = list(
43
+ map(
44
+ lambda p: p.portfolio.id, value.get_model_portfolio_relationships(date.today() - timedelta(days=7))
45
+ )
46
+ )
47
+ return queryset.filter(id__in=modeled_after_portfolio_ids)
48
+ return queryset
19
49
 
20
50
  def filter_instrument(self, queryset, name, value):
21
51
  if value:
22
52
  return queryset.filter(instruments=value)
23
53
  return queryset
24
54
 
55
+ def filter_invests_in(self, queryset, name, value):
56
+ if value:
57
+ return queryset.annotate(
58
+ invests_in=Exists(AssetPosition.objects.filter(underlying_quote=value, portfolio=OuterRef("pk")))
59
+ ).filter(invests_in=True)
60
+ return queryset
61
+
25
62
  class Meta:
26
63
  model = Portfolio
27
64
  fields = {
@@ -76,7 +76,6 @@ class AssetPositionPandasFilter(DateFilterMixin, PandasFilterSetMixin, wb_filter
76
76
  date = total_value_fx_usd = total_value_fx_usd__gte = total_value_fx_usd__lte = None
77
77
 
78
78
  date = wb_filters.FinancialPerformanceDateRangeFilter(
79
- method=wb_filters.DateRangeFilter.base_date_range_filter_method,
80
79
  label="Date Range",
81
80
  required=True,
82
81
  clearable=False,
@@ -10,7 +10,7 @@ from .claim import (
10
10
  NegativeTermimalAccountPerProductFilterSet,
11
11
  ProfitAndLossPandasFilter,
12
12
  )
13
- from .fees import FeesAggregatedFilter, FeesFilter, FeesPortfolioFilterSet
13
+ from .fees import FeesAggregatedFilter, FeesFilter, FeesProductFilterSet
14
14
  from .trades import (
15
15
  SubscriptionRedemptionFilterSet,
16
16
  SubscriptionRedemptionPortfolioFilterSet,
@@ -18,4 +18,3 @@ from .trades import (
18
18
  TradeInstrumentFilterSet,
19
19
  TradePortfolioFilter,
20
20
  )
21
- from .transactions import TransactionFilterSet, TransactionPortfolioFilterSet
@@ -4,10 +4,10 @@ from wbcore.pandas.filterset import PandasFilterSetMixin
4
4
 
5
5
  from wbportfolio.models import Fees
6
6
 
7
- from .transactions import TransactionFilterSet, get_transaction_default_date_range
7
+ from .utils import get_transaction_default_date_range
8
8
 
9
9
 
10
- class FeesFilter(TransactionFilterSet):
10
+ class FeesFilter(wb_filters.FilterSet):
11
11
  """FilterSet for Fees
12
12
 
13
13
  Currently filters:
@@ -16,28 +16,24 @@ class FeesFilter(TransactionFilterSet):
16
16
  """
17
17
 
18
18
  fee_date = wb_filters.DateRangeFilter(
19
- method=wb_filters.DateRangeFilter.base_date_range_filter_method,
20
19
  label="Date Range",
21
20
  initial=get_transaction_default_date_range,
22
21
  required=True,
23
22
  )
24
- total_value_usd__gte = total_value_usd__lte = transaction_underlying_type = transaction_date = None
25
23
 
26
24
  class Meta:
27
25
  model = Fees
28
26
  fields = {
29
27
  "transaction_subtype": ["exact"],
30
28
  "currency_fx_rate": ["gte", "exact", "lte"],
31
- "portfolio": ["exact"],
29
+ "product": ["exact"],
32
30
  "currency": ["exact"],
33
31
  "total_value": ["gte", "exact", "lte"],
34
32
  "total_value_fx_portfolio": ["gte", "exact", "lte"],
35
- "total_value_gross": ["gte", "exact", "lte"],
36
- "total_value_gross_fx_portfolio": ["gte", "exact", "lte"],
37
33
  }
38
34
 
39
35
 
40
- class FeesPortfolioFilterSet(FeesFilter):
36
+ class FeesProductFilterSet(FeesFilter):
41
37
  portfolio = None
42
38
 
43
39
  class Meta:
@@ -49,15 +45,12 @@ class FeesPortfolioFilterSet(FeesFilter):
49
45
  "currency": ["exact"],
50
46
  "total_value": ["gte", "exact", "lte"],
51
47
  "total_value_fx_portfolio": ["gte", "exact", "lte"],
52
- "total_value_gross": ["gte", "exact", "lte"],
53
- "total_value_gross_fx_portfolio": ["gte", "exact", "lte"],
54
48
  }
55
49
 
56
50
 
57
51
  class FeesAggregatedFilter(PandasFilterSetMixin, wb_filters.FilterSet):
58
- transaction_date = wb_filters.DateRangeFilter(
52
+ fee_date = wb_filters.DateRangeFilter(
59
53
  label="Date Range",
60
- method=wb_filters.DateRangeFilter.base_date_range_filter_method,
61
54
  required=True,
62
55
  clearable=False,
63
56
  initial=current_quarter_date_range,
@@ -5,14 +5,27 @@ from wbcore import filters as wb_filters
5
5
  from wbcrm.models.accounts import Account
6
6
  from wbfdm.models import Classification, Instrument
7
7
 
8
- from wbportfolio.models import Product, Trade
8
+ from wbportfolio.models import Portfolio, Product, Trade
9
9
  from wbportfolio.models.transactions.claim import Claim
10
10
 
11
11
  from .mixins import OppositeSharesFieldMethodMixin
12
- from .transactions import TransactionFilterSet
12
+ from .utils import get_transaction_default_date_range
13
13
 
14
14
 
15
- class TradeFilter(OppositeSharesFieldMethodMixin, TransactionFilterSet):
15
+ class TradeFilter(OppositeSharesFieldMethodMixin, wb_filters.FilterSet):
16
+ transaction_date = wb_filters.DateRangeFilter(
17
+ label="Date Range",
18
+ initial=get_transaction_default_date_range,
19
+ )
20
+
21
+ portfolio = wb_filters.ModelChoiceFilter(
22
+ label="Portfolio",
23
+ queryset=Portfolio.objects.all(),
24
+ endpoint=Portfolio.get_representation_endpoint(),
25
+ value_key=Portfolio.get_representation_value_key(),
26
+ label_key=Portfolio.get_representation_label_key(),
27
+ )
28
+
16
29
  # we have to redefine the mixin fields because django_filters does not allow class extension with mixin
17
30
  opposite_shares = wb_filters.NumberFilter(
18
31
  field_name="shares",
@@ -28,10 +41,6 @@ class TradeFilter(OppositeSharesFieldMethodMixin, TransactionFilterSet):
28
41
  lookup_icon="≈±",
29
42
  lookup_label="Opposite Approximate (+- 10%)",
30
43
  )
31
- transaction_date = wb_filters.DateRangeFilter(
32
- method=wb_filters.DateRangeFilter.base_date_range_filter_method,
33
- label="Date Range",
34
- )
35
44
  underlying_instrument = wb_filters.ModelChoiceFilter(
36
45
  label="Instrument",
37
46
  queryset=Instrument.objects.all(),
@@ -69,7 +78,6 @@ class TradeFilter(OppositeSharesFieldMethodMixin, TransactionFilterSet):
69
78
  # value_key=Register.get_representation_value_key(),
70
79
  # label_key = Register.get_representation_label_key(),
71
80
  # )
72
- total_value_usd__gte = total_value_usd__lte = transaction_underlying_type = None
73
81
  marked_for_deletion = wb_filters.BooleanFilter(
74
82
  label="Marked For Deletion", initial=False, field_name="marked_for_deletion", lookup_expr="exact"
75
83
  )
@@ -0,0 +1,42 @@
1
+ from datetime import date, timedelta
2
+
3
+ from psycopg.types.range import DateRange
4
+
5
+ from wbportfolio.models import Fees, Trade
6
+
7
+
8
+ def get_transaction_gte_default(field, request, view):
9
+ filter_date = date.today() - timedelta(days=90)
10
+ qs = Trade.objects.none()
11
+ if "instrument_id" in view.kwargs:
12
+ qs = Trade.objects.filter(underlying_instrument__id=view.kwargs["instrument_id"])
13
+ elif "portfolio_id" in view.kwargs:
14
+ qs = Trade.objects.filter(portfolio__id=view.kwargs["portfolio_id"])
15
+ if qs.exists():
16
+ filter_date = qs.earliest("transaction_date").transaction_date
17
+ return filter_date
18
+
19
+
20
+ def get_transaction_underlying_type_choices(*args):
21
+ models = [Fees, Trade]
22
+ choices = []
23
+ for model in models:
24
+ for choice in model.Type.choices:
25
+ choices.append(choice)
26
+ return choices
27
+
28
+
29
+ def get_transaction_lte_default(field, request, view):
30
+ filter_date = date.today() + timedelta(days=7)
31
+ qs = Trade.objects.none()
32
+ if "instrument_id" in view.kwargs:
33
+ qs = Trade.objects.filter(underlying_instrument__id=view.kwargs["instrument_id"])
34
+ elif "portfolio_id" in view.kwargs:
35
+ qs = Trade.objects.filter(portfolio__id=view.kwargs["portfolio_id"])
36
+ if qs.exists():
37
+ filter_date = qs.latest("transaction_date").transaction_date
38
+ return filter_date
39
+
40
+
41
+ def get_transaction_default_date_range(*args, **kwargs):
42
+ return DateRange(get_transaction_gte_default(*args, **kwargs), get_transaction_lte_default(*args, **kwargs))
@@ -1,3 +1,4 @@
1
1
  from .asset_position import *
2
2
  from .fees import *
3
3
  from .instrument_price import *
4
+ from .trade import *
@@ -7,7 +7,8 @@ from django.db import models
7
7
  from pandas.tseries.offsets import BDay
8
8
  from wbcore.contrib.io.backends import AbstractDataBackend, register
9
9
 
10
- from ..utils import process_request
10
+ from wbportfolio.api_clients.ubs import UBSNeoAPIClient
11
+
11
12
  from .mixin import DataBackendMixin
12
13
 
13
14
 
@@ -21,7 +22,8 @@ class DataBackend(DataBackendMixin, AbstractDataBackend):
21
22
  self.ubs_bank = ubs_bank
22
23
  if not import_credential or not import_credential.authentication_token:
23
24
  raise ValueError("UBS backend needs a valid import credential object")
24
- self.authentication_token = import_credential.authentication_token
25
+ self.authentication_token = import_credential.authentication_token.replace("Bearer ", "")
26
+ self.token_expiry_date = import_credential.validity_end
25
27
 
26
28
  def get_files(
27
29
  self,
@@ -31,13 +33,10 @@ class DataBackend(DataBackendMixin, AbstractDataBackend):
31
33
  **kwargs,
32
34
  ) -> BytesIO:
33
35
  execution_date = (execution_time - BDay(1)).date()
34
-
35
- endpoint = "https://neo.ubs.com/api/ged-amc/external/report/v1/valuation/{0}/{1}"
36
36
  if obj_external_ids:
37
+ client = UBSNeoAPIClient(self.authentication_token, self.token_expiry_date)
37
38
  for external_id in obj_external_ids:
38
- res_json = process_request(
39
- self.authentication_token, endpoint.format(external_id, execution_date.strftime("%Y-%m-%d"))
40
- )
39
+ res_json = client.validate_response(client.get_portfolio_at_date(external_id, execution_date))
41
40
  if res_json:
42
41
  content_file = BytesIO()
43
42
  content_file.write(json.dumps(res_json).encode())
@@ -7,7 +7,8 @@ from django.db import models
7
7
  from dynamic_preferences.registries import global_preferences_registry
8
8
  from wbcore.contrib.io.backends import AbstractDataBackend, register
9
9
 
10
- from ..utils import process_request
10
+ from wbportfolio.api_clients.ubs import UBSNeoAPIClient
11
+
11
12
  from .mixin import DataBackendMixin
12
13
 
13
14
 
@@ -21,7 +22,8 @@ class DataBackend(DataBackendMixin, AbstractDataBackend):
21
22
  self.ubs_bank = ubs_bank
22
23
  if not import_credential or not import_credential.authentication_token:
23
24
  raise ValueError("UBS backend needs a valid import credential object")
24
- self.authentication_token = import_credential.authentication_token
25
+ self.authentication_token = import_credential.authentication_token.replace("Bearer ", "")
26
+ self.token_expiry_date = import_credential.validity_end
25
27
 
26
28
  def get_files(
27
29
  self,
@@ -30,32 +32,20 @@ class DataBackend(DataBackendMixin, AbstractDataBackend):
30
32
  **kwargs,
31
33
  ) -> BytesIO:
32
34
  execution_date = execution_time.date()
33
-
34
- mngt_fees_endpoint = "https://neo.ubs.com/api/ged-amc/external/fee/v1/management/{0}"
35
- perf_fees_endpoint = "https://neo.ubs.com/api/ged-amc/external/fee/v1/performance/{0}"
36
35
  if obj_external_ids:
36
+ client = UBSNeoAPIClient(self.authentication_token, self.token_expiry_date)
37
+ start = kwargs.get("start", None)
38
+ if not start:
39
+ start = global_preferences_registry.manager()["wbfdm__default_start_date_historical_import"]
37
40
  for external_id in obj_external_ids:
38
- start = kwargs.get("start", None)
39
- if not start:
40
- start = global_preferences_registry.manager()["wbfdm__default_start_date_historical_import"]
41
- mngt_res = process_request(
42
- self.authentication_token,
43
- mngt_fees_endpoint.format(external_id),
44
- {"fromDate": start.strftime("%Y-%m-%d"), "toDate": execution_date.strftime("%Y-%m-%d")},
45
- )
46
- perf_res = process_request(
47
- self.authentication_token,
48
- perf_fees_endpoint.format(external_id),
49
- {"fromDate": start.strftime("%Y-%m-%d"), "toDate": execution_date.strftime("%Y-%m-%d")},
50
- )
51
-
41
+ mngt_res = client.validate_response(client.get_management_fees(external_id, start, execution_date))
42
+ perf_res = client.validate_response(client.get_performance_fees(external_id, start, execution_date))
52
43
  if mngt_res or perf_res:
53
44
  res_json = {
54
45
  "performance_fees": perf_res.get("fees", []),
55
46
  "management_fees": mngt_res.get("fees", []),
56
47
  "isin": external_id,
57
48
  }
58
-
59
49
  if res_json:
60
50
  content_file = BytesIO()
61
51
  content_file.write(json.dumps(res_json).encode())
@@ -7,7 +7,8 @@ from django.db import models
7
7
  from pandas.tseries.offsets import BDay
8
8
  from wbcore.contrib.io.backends import AbstractDataBackend, register
9
9
 
10
- from ..utils import process_request
10
+ from wbportfolio.api_clients.ubs import UBSNeoAPIClient
11
+
11
12
  from .mixin import DataBackendMixin
12
13
 
13
14
 
@@ -21,7 +22,8 @@ class DataBackend(DataBackendMixin, AbstractDataBackend):
21
22
  self.ubs_bank = ubs_bank
22
23
  if not import_credential or not import_credential.authentication_token:
23
24
  raise ValueError("UBS backend needs a valid import credential object")
24
- self.authentication_token = import_credential.authentication_token
25
+ self.authentication_token = import_credential.authentication_token.replace("Bearer ", "")
26
+ self.token_expiry_date = import_credential.validity_end
25
27
 
26
28
  def get_files(
27
29
  self,
@@ -30,12 +32,10 @@ class DataBackend(DataBackendMixin, AbstractDataBackend):
30
32
  **kwargs,
31
33
  ) -> BytesIO:
32
34
  execution_date = (execution_time - BDay(1)).date()
33
- endpoint = "https://neo.ubs.com/api/ged-amc/external/report/v1/valuation/{0}/{1}"
34
35
  if obj_external_ids:
36
+ client = UBSNeoAPIClient(self.authentication_token, self.token_expiry_date)
35
37
  for external_id in obj_external_ids:
36
- res_json = process_request(
37
- self.authentication_token, endpoint.format(external_id, execution_date.strftime("%Y-%m-%d"))
38
- )
38
+ res_json = client.validate_response(client.get_portfolio_at_date(external_id, execution_date))
39
39
  res_json.pop("constituents", None)
40
40
  if res_json:
41
41
  content_file = BytesIO()
@@ -0,0 +1,48 @@
1
+ import json
2
+ from datetime import date, datetime
3
+ from io import BytesIO
4
+ from typing import Optional
5
+
6
+ from django.db import models
7
+ from pandas.tseries.offsets import BDay
8
+ from wbcore.contrib.io.backends import AbstractDataBackend, register
9
+
10
+ from wbportfolio.api_clients.ubs import UBSNeoAPIClient
11
+
12
+ from .mixin import DataBackendMixin
13
+
14
+
15
+ @register("Trade", provider_key="ubs", save_data_in_import_source=True, passive_only=True)
16
+ class DataBackend(DataBackendMixin, AbstractDataBackend):
17
+ def __init__(
18
+ self, import_credential: Optional[models.Model] = None, ubs_bank: Optional[models.Model] = None, **kwargs
19
+ ):
20
+ if not ubs_bank:
21
+ raise ValueError("The ubs company objects needs to be passed to this backend")
22
+ self.ubs_bank = ubs_bank
23
+ if not import_credential or not import_credential.authentication_token:
24
+ raise ValueError("UBS backend needs a valid import credential object")
25
+ self.authentication_token = import_credential.authentication_token.replace("Bearer ", "")
26
+ self.token_expiry_date = import_credential.validity_end
27
+
28
+ def get_files(
29
+ self,
30
+ execution_time: datetime,
31
+ start: date = None,
32
+ obj_external_ids: list[str] = None,
33
+ **kwargs,
34
+ ) -> BytesIO:
35
+ if not start:
36
+ start = (execution_time - BDay(2)).date()
37
+ end = execution_time.date()
38
+ if obj_external_ids:
39
+ client = UBSNeoAPIClient(self.authentication_token, self.token_expiry_date)
40
+ for external_id in obj_external_ids:
41
+ res_json = client.validate_response(
42
+ client.get_rebalance_reports(external_id, from_date=start, to_date=end)
43
+ )
44
+ if res_json:
45
+ content_file = BytesIO()
46
+ content_file.write(json.dumps(res_json).encode())
47
+ file_name = f"ubs_trades_{external_id}_{start:%Y-%m-%d}-{end:%Y-%m-%d}_{datetime.timestamp(execution_time)}.json"
48
+ yield file_name, content_file
@@ -1,7 +1,3 @@
1
- from contextlib import suppress
2
-
3
- import pandas as pd
4
- import requests
5
1
  from django.db.models import Q
6
2
  from dynamic_preferences.registries import global_preferences_registry
7
3
  from wbfdm.models import Instrument
@@ -11,19 +7,6 @@ def get_timedelta_import_instrument_price():
11
7
  return global_preferences_registry.manager()["wbportfolio__timedelta_import_instrument_price"]
12
8
 
13
9
 
14
- def process_request(authentication_token: str, endpoint: str | None = None, kwargs={}) -> pd.DataFrame:
15
- headers = {"Authorization": authentication_token}
16
- r = requests.get(endpoint, params=kwargs, headers=headers)
17
- if r.status_code == requests.codes.ok:
18
- with suppress(
19
- requests.exceptions.JSONDecodeError
20
- ): # we catch any json decode error because the UBS api doesn't seem to respect HTTP status code rule (i.e. returns 200 even though the http content is malformed)
21
- r_json = r.json()
22
- if r_json.get("status", "") == "SUCCESS":
23
- return r_json
24
- raise ValueError(f"Issue while processing request: {r.content}")
25
-
26
-
27
10
  def filter_active_instruments(_date, queryset=None):
28
11
  if not queryset:
29
12
  queryset = Instrument.objects