wbfdm 1.43.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 wbfdm might be problematic. Click here for more details.

Files changed (351) hide show
  1. wbfdm/__init__.py +2 -0
  2. wbfdm/admin/__init__.py +42 -0
  3. wbfdm/admin/classifications.py +39 -0
  4. wbfdm/admin/esg.py +23 -0
  5. wbfdm/admin/exchanges.py +53 -0
  6. wbfdm/admin/instrument_lists.py +23 -0
  7. wbfdm/admin/instrument_prices.py +62 -0
  8. wbfdm/admin/instrument_requests.py +33 -0
  9. wbfdm/admin/instruments.py +117 -0
  10. wbfdm/admin/instruments_relationships.py +25 -0
  11. wbfdm/admin/options.py +101 -0
  12. wbfdm/analysis/__init__.py +2 -0
  13. wbfdm/analysis/esg/__init__.py +0 -0
  14. wbfdm/analysis/esg/enums.py +82 -0
  15. wbfdm/analysis/esg/esg_analysis.py +217 -0
  16. wbfdm/analysis/esg/utils.py +13 -0
  17. wbfdm/analysis/financial_analysis/__init__.py +1 -0
  18. wbfdm/analysis/financial_analysis/financial_metric_analysis.py +88 -0
  19. wbfdm/analysis/financial_analysis/financial_ratio_analysis.py +125 -0
  20. wbfdm/analysis/financial_analysis/financial_statistics_analysis.py +277 -0
  21. wbfdm/analysis/financial_analysis/statement_with_estimates.py +558 -0
  22. wbfdm/analysis/financial_analysis/utils.py +316 -0
  23. wbfdm/analysis/technical_analysis/__init__.py +1 -0
  24. wbfdm/analysis/technical_analysis/technical_analysis.py +138 -0
  25. wbfdm/analysis/technical_analysis/traces.py +165 -0
  26. wbfdm/analysis/utils.py +32 -0
  27. wbfdm/apps.py +14 -0
  28. wbfdm/backends/dto.py +36 -0
  29. wbfdm/contrib/__init__.py +0 -0
  30. wbfdm/contrib/dsws/__init__.py +0 -0
  31. wbfdm/contrib/dsws/client.py +285 -0
  32. wbfdm/contrib/dsws/dataloaders/market_data.py +130 -0
  33. wbfdm/contrib/internal/__init__.py +0 -0
  34. wbfdm/contrib/internal/dataloaders/__init__.py +0 -0
  35. wbfdm/contrib/internal/dataloaders/market_data.py +87 -0
  36. wbfdm/contrib/metric/__init__.py +0 -0
  37. wbfdm/contrib/metric/admin/__init__.py +2 -0
  38. wbfdm/contrib/metric/admin/instruments.py +12 -0
  39. wbfdm/contrib/metric/admin/metrics.py +43 -0
  40. wbfdm/contrib/metric/apps.py +10 -0
  41. wbfdm/contrib/metric/backends/__init__.py +2 -0
  42. wbfdm/contrib/metric/backends/base.py +159 -0
  43. wbfdm/contrib/metric/backends/performances.py +265 -0
  44. wbfdm/contrib/metric/backends/statistics.py +182 -0
  45. wbfdm/contrib/metric/decorators.py +14 -0
  46. wbfdm/contrib/metric/dispatch.py +23 -0
  47. wbfdm/contrib/metric/dto.py +88 -0
  48. wbfdm/contrib/metric/exceptions.py +6 -0
  49. wbfdm/contrib/metric/factories.py +33 -0
  50. wbfdm/contrib/metric/filters.py +28 -0
  51. wbfdm/contrib/metric/migrations/0001_initial.py +88 -0
  52. wbfdm/contrib/metric/migrations/0002_remove_instrumentmetric_unique_instrument_metric_and_more.py +26 -0
  53. wbfdm/contrib/metric/migrations/__init__.py +0 -0
  54. wbfdm/contrib/metric/models.py +180 -0
  55. wbfdm/contrib/metric/orchestrators.py +94 -0
  56. wbfdm/contrib/metric/registry.py +80 -0
  57. wbfdm/contrib/metric/serializers.py +44 -0
  58. wbfdm/contrib/metric/tasks.py +27 -0
  59. wbfdm/contrib/metric/tests/__init__.py +0 -0
  60. wbfdm/contrib/metric/tests/backends/__init__.py +0 -0
  61. wbfdm/contrib/metric/tests/backends/test_performances.py +152 -0
  62. wbfdm/contrib/metric/tests/backends/test_statistics.py +48 -0
  63. wbfdm/contrib/metric/tests/conftest.py +92 -0
  64. wbfdm/contrib/metric/tests/test_dto.py +73 -0
  65. wbfdm/contrib/metric/tests/test_models.py +72 -0
  66. wbfdm/contrib/metric/tests/test_tasks.py +24 -0
  67. wbfdm/contrib/metric/tests/test_viewsets.py +79 -0
  68. wbfdm/contrib/metric/urls.py +19 -0
  69. wbfdm/contrib/metric/viewsets/__init__.py +1 -0
  70. wbfdm/contrib/metric/viewsets/configs/__init__.py +1 -0
  71. wbfdm/contrib/metric/viewsets/configs/display.py +92 -0
  72. wbfdm/contrib/metric/viewsets/configs/menus.py +11 -0
  73. wbfdm/contrib/metric/viewsets/configs/utils.py +137 -0
  74. wbfdm/contrib/metric/viewsets/mixins.py +248 -0
  75. wbfdm/contrib/metric/viewsets/viewsets.py +40 -0
  76. wbfdm/contrib/msci/__init__.py +0 -0
  77. wbfdm/contrib/msci/client.py +92 -0
  78. wbfdm/contrib/msci/dataloaders/__init__.py +0 -0
  79. wbfdm/contrib/msci/dataloaders/esg.py +87 -0
  80. wbfdm/contrib/msci/dataloaders/esg_controversies.py +81 -0
  81. wbfdm/contrib/msci/sync.py +58 -0
  82. wbfdm/contrib/msci/tests/__init__.py +0 -0
  83. wbfdm/contrib/msci/tests/conftest.py +1 -0
  84. wbfdm/contrib/msci/tests/test_client.py +70 -0
  85. wbfdm/contrib/qa/__init__.py +0 -0
  86. wbfdm/contrib/qa/apps.py +22 -0
  87. wbfdm/contrib/qa/database_routers.py +25 -0
  88. wbfdm/contrib/qa/dataloaders/__init__.py +0 -0
  89. wbfdm/contrib/qa/dataloaders/adjustments.py +56 -0
  90. wbfdm/contrib/qa/dataloaders/corporate_actions.py +59 -0
  91. wbfdm/contrib/qa/dataloaders/financials.py +83 -0
  92. wbfdm/contrib/qa/dataloaders/market_data.py +117 -0
  93. wbfdm/contrib/qa/dataloaders/officers.py +59 -0
  94. wbfdm/contrib/qa/dataloaders/reporting_dates.py +67 -0
  95. wbfdm/contrib/qa/dataloaders/statements.py +267 -0
  96. wbfdm/contrib/qa/jinja2/qa/sql/companies.sql +100 -0
  97. wbfdm/contrib/qa/jinja2/qa/sql/ibes/base_estimates.sql +33 -0
  98. wbfdm/contrib/qa/jinja2/qa/sql/ibes/calendarized.sql +37 -0
  99. wbfdm/contrib/qa/jinja2/qa/sql/ibes/complete.sql +9 -0
  100. wbfdm/contrib/qa/jinja2/qa/sql/ibes/estimates.sql +3 -0
  101. wbfdm/contrib/qa/jinja2/qa/sql/ibes/financials.sql +79 -0
  102. wbfdm/contrib/qa/jinja2/qa/sql/instruments.sql +100 -0
  103. wbfdm/contrib/qa/jinja2/qa/sql/quotes.sql +98 -0
  104. wbfdm/contrib/qa/sync/exchanges.py +70 -0
  105. wbfdm/contrib/qa/sync/instruments.py +94 -0
  106. wbfdm/contrib/qa/sync/utils.py +241 -0
  107. wbfdm/contrib/qa/tasks.py +0 -0
  108. wbfdm/dataloaders/__init__.py +0 -0
  109. wbfdm/dataloaders/cache.py +129 -0
  110. wbfdm/dataloaders/protocols.py +112 -0
  111. wbfdm/dataloaders/proxies.py +201 -0
  112. wbfdm/dataloaders/types.py +209 -0
  113. wbfdm/dynamic_preferences_registry.py +45 -0
  114. wbfdm/enums.py +657 -0
  115. wbfdm/factories/__init__.py +13 -0
  116. wbfdm/factories/classifications.py +56 -0
  117. wbfdm/factories/controversies.py +27 -0
  118. wbfdm/factories/exchanges.py +21 -0
  119. wbfdm/factories/instrument_list.py +22 -0
  120. wbfdm/factories/instrument_prices.py +79 -0
  121. wbfdm/factories/instruments.py +63 -0
  122. wbfdm/factories/instruments_relationships.py +31 -0
  123. wbfdm/factories/options.py +66 -0
  124. wbfdm/figures/__init__.py +1 -0
  125. wbfdm/figures/financials/__init__.py +1 -0
  126. wbfdm/figures/financials/financial_analysis_charts.py +469 -0
  127. wbfdm/figures/financials/financials_charts.py +711 -0
  128. wbfdm/filters/__init__.py +31 -0
  129. wbfdm/filters/classifications.py +100 -0
  130. wbfdm/filters/exchanges.py +22 -0
  131. wbfdm/filters/financials.py +95 -0
  132. wbfdm/filters/financials_analysis.py +119 -0
  133. wbfdm/filters/instrument_prices.py +112 -0
  134. wbfdm/filters/instruments.py +198 -0
  135. wbfdm/filters/utils.py +44 -0
  136. wbfdm/import_export/__init__.py +0 -0
  137. wbfdm/import_export/backends/__init__.py +0 -0
  138. wbfdm/import_export/backends/cbinsights/__init__.py +2 -0
  139. wbfdm/import_export/backends/cbinsights/deals.py +44 -0
  140. wbfdm/import_export/backends/cbinsights/equities.py +41 -0
  141. wbfdm/import_export/backends/cbinsights/mixin.py +15 -0
  142. wbfdm/import_export/backends/cbinsights/utils/__init__.py +0 -0
  143. wbfdm/import_export/backends/cbinsights/utils/classifications.py +4150 -0
  144. wbfdm/import_export/backends/cbinsights/utils/client.py +217 -0
  145. wbfdm/import_export/backends/refinitiv/__init__.py +5 -0
  146. wbfdm/import_export/backends/refinitiv/daily_fundamental.py +36 -0
  147. wbfdm/import_export/backends/refinitiv/fiscal_period.py +63 -0
  148. wbfdm/import_export/backends/refinitiv/forecast.py +178 -0
  149. wbfdm/import_export/backends/refinitiv/fundamental.py +103 -0
  150. wbfdm/import_export/backends/refinitiv/geographic_segment.py +32 -0
  151. wbfdm/import_export/backends/refinitiv/instrument.py +55 -0
  152. wbfdm/import_export/backends/refinitiv/instrument_price.py +77 -0
  153. wbfdm/import_export/backends/refinitiv/mixin.py +29 -0
  154. wbfdm/import_export/backends/refinitiv/utils/__init__.py +1 -0
  155. wbfdm/import_export/backends/refinitiv/utils/controller.py +182 -0
  156. wbfdm/import_export/handlers/__init__.py +0 -0
  157. wbfdm/import_export/handlers/instrument.py +253 -0
  158. wbfdm/import_export/handlers/instrument_list.py +101 -0
  159. wbfdm/import_export/handlers/instrument_price.py +71 -0
  160. wbfdm/import_export/handlers/option.py +54 -0
  161. wbfdm/import_export/handlers/private_equities.py +49 -0
  162. wbfdm/import_export/parsers/__init__.py +0 -0
  163. wbfdm/import_export/parsers/cbinsights/__init__.py +0 -0
  164. wbfdm/import_export/parsers/cbinsights/deals.py +39 -0
  165. wbfdm/import_export/parsers/cbinsights/equities.py +56 -0
  166. wbfdm/import_export/parsers/cbinsights/fundamentals.py +45 -0
  167. wbfdm/import_export/parsers/refinitiv/__init__.py +0 -0
  168. wbfdm/import_export/parsers/refinitiv/daily_fundamental.py +7 -0
  169. wbfdm/import_export/parsers/refinitiv/forecast.py +7 -0
  170. wbfdm/import_export/parsers/refinitiv/fundamental.py +9 -0
  171. wbfdm/import_export/parsers/refinitiv/geographic_segment.py +7 -0
  172. wbfdm/import_export/parsers/refinitiv/instrument.py +75 -0
  173. wbfdm/import_export/parsers/refinitiv/instrument_price.py +26 -0
  174. wbfdm/import_export/parsers/refinitiv/utils.py +96 -0
  175. wbfdm/import_export/resources/__init__.py +0 -0
  176. wbfdm/import_export/resources/classification.py +23 -0
  177. wbfdm/import_export/resources/instrument_prices.py +33 -0
  178. wbfdm/import_export/resources/instruments.py +176 -0
  179. wbfdm/jinja2.py +7 -0
  180. wbfdm/management/__init__.py +30 -0
  181. wbfdm/menu.py +11 -0
  182. wbfdm/migrations/0001_initial.py +71 -0
  183. wbfdm/migrations/0002_rename_statements_instrumentlookup_financials_and_more.py +144 -0
  184. wbfdm/migrations/0003_instrument_estimate_backend_and_more.py +34 -0
  185. wbfdm/migrations/0004_rename_financials_instrumentlookup_statements_and_more.py +86 -0
  186. wbfdm/migrations/0005_instrument_corporate_action_backend.py +29 -0
  187. wbfdm/migrations/0006_instrument_officer_backend.py +29 -0
  188. wbfdm/migrations/0007_instrument_country_instrument_currency_and_more.py +117 -0
  189. wbfdm/migrations/0008_controversy.py +75 -0
  190. wbfdm/migrations/0009_alter_controversy_flag_alter_controversy_initiated_and_more.py +85 -0
  191. wbfdm/migrations/0010_classification_classificationgroup_deal_exchange_and_more.py +1299 -0
  192. wbfdm/migrations/0011_delete_instrumentlookup_instrument_corporate_actions_and_more.py +169 -0
  193. wbfdm/migrations/0012_instrumentprice_created_instrumentprice_modified.py +564 -0
  194. wbfdm/migrations/0013_instrument_is_investable_universe_and_more.py +199 -0
  195. wbfdm/migrations/0014_alter_controversy_instrument.py +22 -0
  196. wbfdm/migrations/0015_instrument_instrument_investible_index.py +16 -0
  197. wbfdm/migrations/0016_instrumenttype_name_repr.py +18 -0
  198. wbfdm/migrations/0017_instrument_instrument_security_index.py +16 -0
  199. wbfdm/migrations/0018_instrument_instrument_level_index.py +20 -0
  200. wbfdm/migrations/0019_alter_controversy_source.py +17 -0
  201. wbfdm/migrations/0020_optionaggregate_option_and_more.py +249 -0
  202. wbfdm/migrations/0021_delete_instrumentdailystatistics.py +15 -0
  203. wbfdm/migrations/0022_instrument_cusip_option_open_interest_20d_and_more.py +91 -0
  204. wbfdm/migrations/0023_instrument_unique_ric_instrument_unique_rmc_and_more.py +53 -0
  205. wbfdm/migrations/0024_option_open_interest_10d_option_volume_10d_and_more.py +36 -0
  206. wbfdm/migrations/0025_instrument_is_primary_and_more.py +29 -0
  207. wbfdm/migrations/0026_instrument_is_cash_equivalent.py +30 -0
  208. wbfdm/migrations/0027_remove_instrument_unique_ric_and_more.py +100 -0
  209. wbfdm/migrations/0028_instrumentprice_annualized_daily_volatility.py +17 -0
  210. wbfdm/migrations/__init__.py +0 -0
  211. wbfdm/models/__init__.py +4 -0
  212. wbfdm/models/esg/__init__.py +1 -0
  213. wbfdm/models/esg/controversies.py +81 -0
  214. wbfdm/models/exchanges/__init__.py +1 -0
  215. wbfdm/models/exchanges/exchanges.py +223 -0
  216. wbfdm/models/fields.py +117 -0
  217. wbfdm/models/fk_fields.py +403 -0
  218. wbfdm/models/indicators.py +0 -0
  219. wbfdm/models/instruments/__init__.py +19 -0
  220. wbfdm/models/instruments/classifications.py +265 -0
  221. wbfdm/models/instruments/instrument_lists.py +120 -0
  222. wbfdm/models/instruments/instrument_prices.py +544 -0
  223. wbfdm/models/instruments/instrument_relationships.py +251 -0
  224. wbfdm/models/instruments/instrument_requests.py +196 -0
  225. wbfdm/models/instruments/instruments.py +991 -0
  226. wbfdm/models/instruments/llm/__init__.py +1 -0
  227. wbfdm/models/instruments/llm/create_instrument_news_relationships.py +78 -0
  228. wbfdm/models/instruments/mixin/__init__.py +0 -0
  229. wbfdm/models/instruments/mixin/financials_computed.py +804 -0
  230. wbfdm/models/instruments/mixin/financials_serializer_fields.py +1407 -0
  231. wbfdm/models/instruments/mixin/instruments.py +297 -0
  232. wbfdm/models/instruments/options.py +225 -0
  233. wbfdm/models/instruments/private_equities.py +59 -0
  234. wbfdm/models/instruments/querysets.py +73 -0
  235. wbfdm/models/instruments/utils.py +41 -0
  236. wbfdm/preferences.py +21 -0
  237. wbfdm/serializers/__init__.py +4 -0
  238. wbfdm/serializers/esg.py +36 -0
  239. wbfdm/serializers/exchanges.py +39 -0
  240. wbfdm/serializers/instruments/__init__.py +37 -0
  241. wbfdm/serializers/instruments/classifications.py +139 -0
  242. wbfdm/serializers/instruments/instrument_lists.py +61 -0
  243. wbfdm/serializers/instruments/instrument_prices.py +73 -0
  244. wbfdm/serializers/instruments/instrument_relationships.py +170 -0
  245. wbfdm/serializers/instruments/instrument_requests.py +61 -0
  246. wbfdm/serializers/instruments/instruments.py +274 -0
  247. wbfdm/serializers/instruments/mixins.py +104 -0
  248. wbfdm/serializers/officers.py +20 -0
  249. wbfdm/signals.py +7 -0
  250. wbfdm/sync/__init__.py +0 -0
  251. wbfdm/sync/abstract.py +31 -0
  252. wbfdm/sync/runner.py +22 -0
  253. wbfdm/tasks.py +69 -0
  254. wbfdm/tests/__init__.py +0 -0
  255. wbfdm/tests/analysis/__init__.py +0 -0
  256. wbfdm/tests/analysis/financial_analysis/__init__.py +0 -0
  257. wbfdm/tests/analysis/financial_analysis/test_statement_with_estimates.py +392 -0
  258. wbfdm/tests/analysis/financial_analysis/test_utils.py +322 -0
  259. wbfdm/tests/analysis/test_esg.py +159 -0
  260. wbfdm/tests/conftest.py +92 -0
  261. wbfdm/tests/dataloaders/__init__.py +0 -0
  262. wbfdm/tests/dataloaders/test_cache.py +73 -0
  263. wbfdm/tests/models/__init__.py +0 -0
  264. wbfdm/tests/models/test_classifications.py +99 -0
  265. wbfdm/tests/models/test_exchanges.py +7 -0
  266. wbfdm/tests/models/test_instrument_list.py +117 -0
  267. wbfdm/tests/models/test_instrument_prices.py +306 -0
  268. wbfdm/tests/models/test_instruments.py +202 -0
  269. wbfdm/tests/models/test_merge.py +99 -0
  270. wbfdm/tests/models/test_options.py +69 -0
  271. wbfdm/tests/test_tasks.py +6 -0
  272. wbfdm/tests/tests.py +10 -0
  273. wbfdm/urls.py +222 -0
  274. wbfdm/utils.py +54 -0
  275. wbfdm/viewsets/__init__.py +10 -0
  276. wbfdm/viewsets/configs/__init__.py +5 -0
  277. wbfdm/viewsets/configs/buttons/__init__.py +8 -0
  278. wbfdm/viewsets/configs/buttons/classifications.py +23 -0
  279. wbfdm/viewsets/configs/buttons/exchanges.py +9 -0
  280. wbfdm/viewsets/configs/buttons/instrument_prices.py +49 -0
  281. wbfdm/viewsets/configs/buttons/instruments.py +283 -0
  282. wbfdm/viewsets/configs/display/__init__.py +22 -0
  283. wbfdm/viewsets/configs/display/classifications.py +138 -0
  284. wbfdm/viewsets/configs/display/esg.py +75 -0
  285. wbfdm/viewsets/configs/display/exchanges.py +42 -0
  286. wbfdm/viewsets/configs/display/instrument_lists.py +137 -0
  287. wbfdm/viewsets/configs/display/instrument_prices.py +199 -0
  288. wbfdm/viewsets/configs/display/instrument_requests.py +116 -0
  289. wbfdm/viewsets/configs/display/instruments.py +340 -0
  290. wbfdm/viewsets/configs/display/instruments_relationships.py +65 -0
  291. wbfdm/viewsets/configs/display/monthly_performances.py +72 -0
  292. wbfdm/viewsets/configs/display/officers.py +16 -0
  293. wbfdm/viewsets/configs/display/prices.py +21 -0
  294. wbfdm/viewsets/configs/display/statement_with_estimates.py +101 -0
  295. wbfdm/viewsets/configs/display/statements.py +48 -0
  296. wbfdm/viewsets/configs/endpoints/__init__.py +41 -0
  297. wbfdm/viewsets/configs/endpoints/classifications.py +87 -0
  298. wbfdm/viewsets/configs/endpoints/esg.py +20 -0
  299. wbfdm/viewsets/configs/endpoints/exchanges.py +6 -0
  300. wbfdm/viewsets/configs/endpoints/financials_analysis.py +65 -0
  301. wbfdm/viewsets/configs/endpoints/instrument_lists.py +38 -0
  302. wbfdm/viewsets/configs/endpoints/instrument_prices.py +51 -0
  303. wbfdm/viewsets/configs/endpoints/instrument_requests.py +20 -0
  304. wbfdm/viewsets/configs/endpoints/instruments.py +13 -0
  305. wbfdm/viewsets/configs/endpoints/instruments_relationships.py +31 -0
  306. wbfdm/viewsets/configs/endpoints/statements.py +6 -0
  307. wbfdm/viewsets/configs/menus/__init__.py +9 -0
  308. wbfdm/viewsets/configs/menus/classifications.py +19 -0
  309. wbfdm/viewsets/configs/menus/exchanges.py +10 -0
  310. wbfdm/viewsets/configs/menus/instrument_lists.py +10 -0
  311. wbfdm/viewsets/configs/menus/instruments.py +20 -0
  312. wbfdm/viewsets/configs/menus/instruments_relationships.py +33 -0
  313. wbfdm/viewsets/configs/titles/__init__.py +42 -0
  314. wbfdm/viewsets/configs/titles/classifications.py +79 -0
  315. wbfdm/viewsets/configs/titles/esg.py +11 -0
  316. wbfdm/viewsets/configs/titles/exchanges.py +12 -0
  317. wbfdm/viewsets/configs/titles/financial_ratio_analysis.py +6 -0
  318. wbfdm/viewsets/configs/titles/financials_analysis.py +50 -0
  319. wbfdm/viewsets/configs/titles/instrument_prices.py +50 -0
  320. wbfdm/viewsets/configs/titles/instrument_requests.py +16 -0
  321. wbfdm/viewsets/configs/titles/instruments.py +31 -0
  322. wbfdm/viewsets/configs/titles/instruments_relationships.py +21 -0
  323. wbfdm/viewsets/configs/titles/market_data.py +13 -0
  324. wbfdm/viewsets/configs/titles/prices.py +15 -0
  325. wbfdm/viewsets/configs/titles/statement_with_estimates.py +10 -0
  326. wbfdm/viewsets/esg.py +72 -0
  327. wbfdm/viewsets/exchanges.py +63 -0
  328. wbfdm/viewsets/financial_analysis/__init__.py +3 -0
  329. wbfdm/viewsets/financial_analysis/financial_metric_analysis.py +85 -0
  330. wbfdm/viewsets/financial_analysis/financial_ratio_analysis.py +85 -0
  331. wbfdm/viewsets/financial_analysis/statement_with_estimates.py +145 -0
  332. wbfdm/viewsets/instruments/__init__.py +80 -0
  333. wbfdm/viewsets/instruments/classifications.py +279 -0
  334. wbfdm/viewsets/instruments/financials_analysis.py +614 -0
  335. wbfdm/viewsets/instruments/instrument_lists.py +77 -0
  336. wbfdm/viewsets/instruments/instrument_prices.py +542 -0
  337. wbfdm/viewsets/instruments/instrument_requests.py +51 -0
  338. wbfdm/viewsets/instruments/instruments.py +106 -0
  339. wbfdm/viewsets/instruments/instruments_relationships.py +235 -0
  340. wbfdm/viewsets/instruments/utils.py +27 -0
  341. wbfdm/viewsets/market_data.py +172 -0
  342. wbfdm/viewsets/mixins.py +9 -0
  343. wbfdm/viewsets/officers.py +27 -0
  344. wbfdm/viewsets/prices.py +62 -0
  345. wbfdm/viewsets/statements/__init__.py +1 -0
  346. wbfdm/viewsets/statements/statements.py +100 -0
  347. wbfdm/viewsets/technical_analysis/__init__.py +1 -0
  348. wbfdm/viewsets/technical_analysis/monthly_performances.py +93 -0
  349. wbfdm-1.43.1.dist-info/METADATA +15 -0
  350. wbfdm-1.43.1.dist-info/RECORD +351 -0
  351. wbfdm-1.43.1.dist-info/WHEEL +5 -0
@@ -0,0 +1,159 @@
1
+ from contextlib import suppress
2
+ from datetime import date
3
+ from decimal import Decimal
4
+ from typing import Any, Generator, Generic, Type, TypeVar
5
+
6
+ import pandas as pd
7
+ from django.contrib.contenttypes.models import ContentType
8
+ from django.db.models import F, Model, QuerySet, Value
9
+ from rest_framework.serializers import Field
10
+ from wbcore import serializers as wb_serializers
11
+ from wbcore.contrib.currency.models import CurrencyFXRates
12
+ from wbfdm.models import Instrument, InstrumentPrice
13
+
14
+ from ..dto import Metric, MetricField, MetricKey
15
+ from ..exceptions import MetricInvalidParameterException
16
+
17
+ T = TypeVar("T", bound=Model)
18
+
19
+
20
+ class AbstractBackend(Generic[T]):
21
+ BASKET_MODEL_CLASS: Type[Model]
22
+ keys: list[MetricKey]
23
+
24
+ def __init__(self, val_date: date | None):
25
+ self.val_date = val_date
26
+ if not self.BASKET_MODEL_CLASS:
27
+ raise ValueError("A class implementing AbstractBackend needs to define a BASKET_MODEL_CLASS")
28
+ self.content_type = ContentType.objects.get_for_model(self.BASKET_MODEL_CLASS)
29
+
30
+ def compute_metrics(self, basket: Any) -> Generator[Metric, None, None]:
31
+ raise NotImplementedError()
32
+
33
+ def get_queryset(self) -> QuerySet:
34
+ return self.BASKET_MODEL_CLASS.objects.all()
35
+
36
+ def get_serializer_field_attr(self, metric_field: MetricField) -> dict[str, Any]:
37
+ """
38
+ Returns all the serializer attributes for that metric
39
+
40
+ We expect the implementing backends to override this method to define custom logics
41
+ """
42
+ return {
43
+ "decorators": metric_field.decorators,
44
+ "help_text": metric_field.help_text,
45
+ **metric_field.serializer_kwargs,
46
+ }
47
+
48
+ def get_serializer_fields(
49
+ self, with_prefixed_key: bool = False, metric_key: MetricKey | None = None
50
+ ) -> dict[str, Field]:
51
+ if metric_key is None:
52
+ metric_keys = self.keys
53
+ else:
54
+ metric_keys = [metric_key]
55
+ fields = {}
56
+ for metric_key in metric_keys:
57
+ fields.update(
58
+ {
59
+ field_key: wb_serializers.FloatField(
60
+ label=field_title,
61
+ read_only=True,
62
+ **self.get_serializer_field_attr(metric_key.subfields_map[field_key]),
63
+ )
64
+ for field_key, field_title in metric_key.get_fields(with_prefixed_key=with_prefixed_key)
65
+ }
66
+ )
67
+ for extra_subfield in metric_key.extra_subfields:
68
+ fields[
69
+ f"{metric_key.key}_{extra_subfield.key}"
70
+ ] = wb_serializers.ModelSerializer.serializer_field_mapping[extra_subfield.field_type](
71
+ read_only=True, label=extra_subfield.label, **self.get_serializer_field_attr(extra_subfield)
72
+ )
73
+ return fields
74
+
75
+
76
+ class InstrumentMetricBaseBackend(AbstractBackend[Instrument]):
77
+ BASKET_MODEL_CLASS = Instrument
78
+ TARGET_CURRENCY_KEY: str | None = None
79
+
80
+ def get_queryset(self) -> QuerySet[Instrument]:
81
+ return super().get_queryset().filter(is_investable_universe=True)
82
+
83
+ def _get_valid_date(self, instrument: Instrument) -> date:
84
+ if self.val_date is None and instrument.last_price_date:
85
+ return instrument.last_price_date
86
+ elif self.val_date:
87
+ with suppress(InstrumentPrice.DoesNotExist):
88
+ return instrument.prices.filter(date__lte=self.val_date).latest("date").date
89
+ else:
90
+ raise MetricInvalidParameterException()
91
+
92
+
93
+ class BaseDataloader:
94
+ METRIC_KEY: str
95
+
96
+ def __init__(
97
+ self,
98
+ basket,
99
+ val_date: date | None = None,
100
+ min_date: date | None = None,
101
+ target_currency_key: str | None = None,
102
+ use_cached_metrics_key: str | None = None,
103
+ basket_objects: QuerySet | None = None,
104
+ ):
105
+ self.val_date = val_date
106
+ self.min_date = min_date
107
+ self.basket = basket
108
+ self.use_cached_metrics_key = use_cached_metrics_key
109
+ self.target_currency_key = target_currency_key
110
+ self.basket_objects = basket_objects
111
+ if self.basket_objects is None:
112
+ self.basket_objects = self._get_basket_basket_objects()
113
+ self.basket_object_ids = list(map(lambda x: x.id, self.basket_objects))
114
+ self.aggregate_callback = "mean"
115
+ if self.target_currency_key == "USD":
116
+ self.fx_rate_expression = F("currency_fx_rate_to_usd__value")
117
+ elif self.target_currency_key:
118
+ self.fx_rate_expression = CurrencyFXRates.get_fx_rates_subquery_for_two_currencies(
119
+ "date", start_currency="instrument__currency", target_currency=self.target_currency_key
120
+ )
121
+ else:
122
+ self.fx_rate_expression = Value(Decimal(1.0))
123
+
124
+ def _get_basket_basket_objects(self) -> QuerySet:
125
+ """
126
+ Return an iterable of instrument contains within the basket. Expected to be override for custom logic
127
+
128
+ Returns:
129
+ An iterable of instrument. Default to the basket itself
130
+ """
131
+ return Instrument.objects.filter(id=self.basket.id)
132
+
133
+ def _compute(self):
134
+ raise NotImplementedError()
135
+
136
+ def compute(self):
137
+ if self.basket_object_ids:
138
+ if self.use_cached_metrics_key:
139
+ from wbfdm.contrib.metric.models import InstrumentMetric
140
+
141
+ df = pd.json_normalize(
142
+ InstrumentMetric.objects.filter(
143
+ basket_content_type=ContentType.objects.get_for_model(self.basket_objects.model),
144
+ basket_id__in=self.basket_object_ids,
145
+ key=self.use_cached_metrics_key,
146
+ date__isnull=True,
147
+ ).values_list("metrics", flat=True)
148
+ )
149
+ if not df.empty:
150
+ if (
151
+ "date" in df.columns
152
+ ): # it can happen that the cached metric don't contain a date key value, so we ffill it in case
153
+ df["date"] = df["date"].ffill()
154
+ if isinstance(self.aggregate_callback, dict):
155
+ df = df.agg({k: v for k, v in self.aggregate_callback.items() if k in df.columns})
156
+ else:
157
+ df = df.agg(self.aggregate_callback)
158
+ return df.dropna().to_dict()
159
+ return self._compute()
@@ -0,0 +1,265 @@
1
+ from contextlib import suppress
2
+ from datetime import date, timedelta
3
+ from typing import Any, Generator, Iterable
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ from dateutil.relativedelta import relativedelta
8
+ from django.db.models import Avg, BooleanField, DateField, F, QuerySet
9
+ from wbfdm.models import Instrument, InstrumentPrice, RelatedInstrumentThroughModel
10
+
11
+ from ..decorators import register
12
+ from ..dto import Metric, MetricField, MetricKey
13
+ from .base import BaseDataloader, InstrumentMetricBaseBackend
14
+
15
+ PERFORMANCE_METRIC = MetricKey(
16
+ key="performance",
17
+ label="Performance",
18
+ subfields=[
19
+ MetricField(key="daily", label="Daily", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg),
20
+ MetricField(key="weekly", label="Weekly", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg),
21
+ MetricField(
22
+ key="monthly", label="Monthly", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg
23
+ ),
24
+ MetricField(
25
+ key="quarterly", label="Quarterly", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg
26
+ ),
27
+ MetricField(key="yearly", label="Yearly", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg),
28
+ MetricField(
29
+ key="week_to_date", label="WTD", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg
30
+ ),
31
+ MetricField(
32
+ key="month_to_date", label="MTD", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg
33
+ ),
34
+ MetricField(
35
+ key="quarter_to_date", label="QTD", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg
36
+ ),
37
+ MetricField(
38
+ key="year_to_date", label="YTD", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg
39
+ ),
40
+ MetricField(
41
+ key="previous_week_to_date",
42
+ label="Previous Week",
43
+ serializer_kwargs={"percent": True, "precision": 4},
44
+ aggregate=Avg,
45
+ ),
46
+ MetricField(
47
+ key="previous_month_to_date",
48
+ label="Previous Month",
49
+ serializer_kwargs={"percent": True, "precision": 4},
50
+ aggregate=Avg,
51
+ ),
52
+ MetricField(
53
+ key="previous_quarter_to_date",
54
+ label="Previous Quarter",
55
+ serializer_kwargs={"percent": True, "precision": 4},
56
+ aggregate=Avg,
57
+ ),
58
+ MetricField(
59
+ key="previous_year_to_date",
60
+ label="Previous Year",
61
+ serializer_kwargs={"percent": True, "precision": 4},
62
+ aggregate=Avg,
63
+ ),
64
+ MetricField(
65
+ key="inception", label="Inception", serializer_kwargs={"percent": True, "precision": 4}, aggregate=Avg
66
+ ),
67
+ ],
68
+ extra_subfields=[
69
+ MetricField(
70
+ key="is_estimated",
71
+ label="Estimated",
72
+ help_text="True if the performance used a estimated price",
73
+ field_type=BooleanField,
74
+ aggregate=None,
75
+ ),
76
+ MetricField(
77
+ key="date",
78
+ label="Performance Date",
79
+ help_text="The date at which the performances were computed",
80
+ field_type=DateField,
81
+ aggregate=None,
82
+ ),
83
+ ],
84
+ additional_prefixes=["benchmark", "peer"],
85
+ )
86
+
87
+ PERFORMANCE_METRIC_USD = MetricKey(
88
+ key="performance_usd",
89
+ label=PERFORMANCE_METRIC.label,
90
+ subfields=PERFORMANCE_METRIC.subfields,
91
+ additional_prefixes=PERFORMANCE_METRIC.additional_prefixes,
92
+ )
93
+
94
+
95
+ class Dataloader(BaseDataloader):
96
+ METRIC_KEY = "performance"
97
+
98
+ PERFORMANCE_MAP = {
99
+ "weekly": 7,
100
+ "monthly": 30,
101
+ "quarterly": 120,
102
+ "yearly": 365,
103
+ "daily": "B",
104
+ "week_to_date": "W-FRI",
105
+ "month_to_date": "BME",
106
+ "quarter_to_date": "BQE",
107
+ "year_to_date": "BYE",
108
+ }
109
+
110
+ def __init__(self, *args, **kwargs):
111
+ super().__init__(*args, **kwargs)
112
+ self.aggregate_callback = (
113
+ lambda df: df.min() if df.name == "date" else df.mean()
114
+ ) # we need to not sum the "date" column otherwise pandas crashes
115
+
116
+ @classmethod
117
+ def get_performance_date_map(cls, pivot_date) -> dict[str, date]:
118
+ pivot_date = pivot_date + timedelta(days=1) - pd.tseries.offsets.BDay(1)
119
+ return {
120
+ "weekly": (pivot_date - relativedelta(weeks=1) + timedelta(days=1) - pd.tseries.offsets.BDay(1)).date(),
121
+ "monthly": (pivot_date - relativedelta(months=1) + timedelta(days=1) - pd.tseries.offsets.BDay(1)).date(),
122
+ "quarterly": (
123
+ pivot_date - relativedelta(months=3) + timedelta(days=1) - pd.tseries.offsets.BDay(1)
124
+ ).date(),
125
+ "yearly": (pivot_date - relativedelta(years=1) + timedelta(days=1) - pd.tseries.offsets.BDay(1)).date(),
126
+ "daily": (pivot_date - pd.tseries.offsets.BDay(1)).date(),
127
+ "week_to_date": (pivot_date - pd.tseries.offsets.Week(1, weekday=4)).date(),
128
+ "month_to_date": (pivot_date - pd.tseries.offsets.BMonthEnd(1)).date(),
129
+ "quarter_to_date": (pivot_date - pd.tseries.offsets.BQuarterEnd(1)).date(),
130
+ "year_to_date": (pivot_date - pd.tseries.offsets.BYearEnd(1)).date(),
131
+ }
132
+
133
+ def get_data(self) -> Iterable[tuple[Instrument, pd.Series, pd.Series]]:
134
+ """
135
+ Helper method to return the instrument prices as a pandas Series
136
+
137
+ Args:
138
+ instrument: The instrument to get the prices from
139
+
140
+ Returns:
141
+ a tuple of the prices as Series and the calculated mask (as series as well)
142
+ """
143
+
144
+ qs = (
145
+ InstrumentPrice.objects.filter(instrument__in=self.basket_objects, date__lte=self.val_date)
146
+ .annotate_base_data()
147
+ .annotate(fx_rate=self.fx_rate_expression, price=F("net_value") / F("fx_rate"))
148
+ )
149
+ fields = ["instrument", "date", "price", "fx_rate", "calculated"]
150
+ instruments_map = {i.id: i for i in self.basket_objects}
151
+
152
+ if self.min_date:
153
+ qs = qs.filter(date__gte=self.min_date)
154
+ else:
155
+ qs = qs.filter(date__gte=F("instrument__inception_date"))
156
+ recs = qs.values_list(*fields)
157
+ df = (
158
+ pd.DataFrame.from_records(recs, columns=fields)
159
+ .sort_values(by="calculated")
160
+ .groupby(["instrument", "date"])
161
+ .agg("first")
162
+ .sort_index()
163
+ )
164
+ for instrument_id, dff in df.groupby(level=0):
165
+ dff = dff.droplevel(0)
166
+ dff = dff.reindex(pd.date_range(dff.index.min(), dff.index.max()), method="ffill")
167
+ dff.index = pd.to_datetime(dff.index).date
168
+ yield instruments_map[instrument_id], dff["price"].astype(float), dff["calculated"]
169
+
170
+ def _compute(self) -> dict[str, float]:
171
+ """
172
+ Compute the performance metrics for all the PERFORMANCE_MAP keys. If the basket is constituted of multiple instrument, take the average of each performance
173
+
174
+ Returns:
175
+ The metrics as dictionary
176
+ """
177
+ res = {}
178
+ if self.val_date:
179
+ agg_metrics = []
180
+ is_estimated = False
181
+ for _, prices_df, calculated_df in self.get_data():
182
+ if not prices_df.empty and not calculated_df.empty:
183
+ metrics = {}
184
+ is_estimated = is_estimated or bool(calculated_df.iloc[-1])
185
+ for performance, start_date in self.get_performance_date_map(self.val_date).items():
186
+ with suppress(KeyError):
187
+ if start_price := prices_df.loc[start_date]:
188
+ metrics[performance] = round(prices_df.loc[self.val_date] / start_price - 1, 6)
189
+ previous_start_date = self.get_performance_date_map(start_date)[performance]
190
+ if previous_start_price := prices_df.loc[previous_start_date]:
191
+ metrics[f"previous_{performance}"] = round(
192
+ prices_df.loc[start_date] / previous_start_price - 1, 6
193
+ )
194
+
195
+ if not prices_df.empty and prices_df.iloc[0]:
196
+ metrics["inception"] = round(float(prices_df.iloc[-1] / prices_df.iloc[0] - 1), 6)
197
+ agg_metrics.append(metrics)
198
+ res = (
199
+ pd.DataFrame(agg_metrics).astype(float).mean(axis=0).replace([np.inf, -np.inf, np.nan], None).to_dict()
200
+ )
201
+ res["is_estimated"] = is_estimated
202
+ return res
203
+
204
+
205
+ @register(move_first=True)
206
+ class InstrumentPerformanceMetricBackend(InstrumentMetricBaseBackend):
207
+ performance = PERFORMANCE_METRIC
208
+ keys = [PERFORMANCE_METRIC]
209
+
210
+ def get_related_instrument_relationships(self, basket) -> QuerySet[RelatedInstrumentThroughModel]:
211
+ if issubclass(basket.__class__, Instrument):
212
+ return RelatedInstrumentThroughModel.objects.filter(instrument=basket)
213
+ return RelatedInstrumentThroughModel.objects.none()
214
+
215
+ def compute_metrics(self, basket: Instrument) -> Generator[Metric, None, None]:
216
+ val_date = self._get_valid_date(basket)
217
+ metrics = Dataloader(basket, val_date, target_currency_key=self.TARGET_CURRENCY_KEY).compute()
218
+ instrument_relationships = self.get_related_instrument_relationships(basket)
219
+ if instrument_relationships.exists():
220
+ for related_type in [
221
+ RelatedInstrumentThroughModel.RelatedTypeChoices.BENCHMARK,
222
+ RelatedInstrumentThroughModel.RelatedTypeChoices.PEER,
223
+ ]:
224
+ type_metrics = []
225
+ for rel in instrument_relationships.filter(related_type=related_type):
226
+ type_metrics.append(
227
+ Dataloader(
228
+ rel.related_instrument, val_date, target_currency_key=self.TARGET_CURRENCY_KEY
229
+ ).compute()
230
+ )
231
+
232
+ type_metrics = pd.DataFrame(type_metrics).mean(axis=0).round(6)
233
+ for subfield in self.performance.subfields:
234
+ if (base_value := metrics.get(subfield.key)) and (type_value := type_metrics.get(subfield.key)):
235
+ metrics[f"{related_type.value.lower()}_{subfield.key}"] = round(base_value - type_value, 6)
236
+ metrics["date"] = val_date
237
+ yield Metric(
238
+ metrics=metrics,
239
+ basket_id=basket.id,
240
+ basket_content_type_id=self.content_type.id,
241
+ key=self.performance.key,
242
+ date=None,
243
+ )
244
+
245
+ def get_serializer_field_attr(self, metric_field: MetricField) -> dict[str, Any]:
246
+ attrs = super().get_serializer_field_attr(metric_field)
247
+ pivot_date = self.val_date
248
+ if not pivot_date:
249
+ pivot_date = (date.today() - pd.tseries.offsets.BDay(1)).date()
250
+ if "previous" in metric_field.key:
251
+ pivot_date = Dataloader.get_performance_date_map(pivot_date)[metric_field.key.replace("previous_", "")]
252
+
253
+ with suppress(KeyError):
254
+ start_date = Dataloader.get_performance_date_map(pivot_date)[metric_field.key.replace("previous_", "")]
255
+ attrs[
256
+ "help_text"
257
+ ] = f"The {metric_field.label} performance is computed from {start_date:%Y-%m-%d} to {pivot_date:%Y-%m-%d}"
258
+ return attrs
259
+
260
+
261
+ @register(move_first=True)
262
+ class InstrumentPerformanceUSDMetricBackend(InstrumentPerformanceMetricBackend):
263
+ performance = PERFORMANCE_METRIC_USD
264
+ keys = [PERFORMANCE_METRIC_USD]
265
+ TARGET_CURRENCY_KEY = "USD"
@@ -0,0 +1,182 @@
1
+ from contextlib import suppress
2
+ from datetime import date
3
+ from typing import Generator
4
+
5
+ import pandas as pd
6
+ from django.db.models import Avg, Sum
7
+ from wbcore.serializers.fields.number import DisplayMode
8
+ from wbfdm.enums import Financial, PeriodType, SeriesType
9
+ from wbfdm.models import Instrument, InstrumentPrice
10
+
11
+ from ..decorators import register
12
+ from ..dto import Metric, MetricField, MetricKey
13
+ from ..exceptions import MetricInvalidParameterException
14
+ from .base import BaseDataloader, InstrumentMetricBaseBackend
15
+
16
+ STATISTICS_METRIC = MetricKey(
17
+ key="statistic",
18
+ label="Statistic",
19
+ subfields=[
20
+ MetricField(
21
+ key="revenue_y_1",
22
+ label="Revenue Y-1",
23
+ aggregate=Sum,
24
+ list_display_kwargs={"show": "open"},
25
+ decorators=[{"position": "left", "value": "{{currency_symbol}}"}],
26
+ serializer_kwargs={"display_mode": DisplayMode.SHORTENED},
27
+ ),
28
+ MetricField(
29
+ key="revenue_y0",
30
+ label="Revenue Y0",
31
+ aggregate=Sum,
32
+ decorators=[{"position": "left", "value": "{{currency_symbol}}"}],
33
+ serializer_kwargs={"display_mode": DisplayMode.SHORTENED},
34
+ ),
35
+ MetricField(
36
+ key="revenue_y1",
37
+ label="Revenue Y1",
38
+ aggregate=Sum,
39
+ list_display_kwargs={"show": "open"},
40
+ decorators=[{"position": "left", "value": "{{currency_symbol}}"}],
41
+ serializer_kwargs={"display_mode": DisplayMode.SHORTENED},
42
+ ),
43
+ MetricField(
44
+ key="market_capitalization",
45
+ label="Market Capitalization",
46
+ aggregate=Sum,
47
+ decorators=[{"position": "left", "value": "{{currency_symbol}}"}],
48
+ serializer_kwargs={"display_mode": DisplayMode.SHORTENED},
49
+ ),
50
+ MetricField(
51
+ key="price",
52
+ label="Price",
53
+ aggregate=Avg,
54
+ decorators=[{"position": "left", "value": "{{currency_symbol}}"}],
55
+ ),
56
+ MetricField(
57
+ key="volume_50d",
58
+ label="Volume 50D",
59
+ aggregate=Avg,
60
+ serializer_kwargs={"display_mode": DisplayMode.SHORTENED},
61
+ ),
62
+ ],
63
+ )
64
+
65
+ STATISTICS_METRIC_USD = MetricKey(
66
+ key="statistic_usd",
67
+ label=STATISTICS_METRIC.label,
68
+ subfields=STATISTICS_METRIC.subfields,
69
+ additional_prefixes=STATISTICS_METRIC.additional_prefixes,
70
+ )
71
+
72
+
73
+ class Dataloader(BaseDataloader):
74
+ METRIC_KEY = "statistic"
75
+
76
+ def __init__(self, *args, **kwargs):
77
+ super().__init__(*args, **kwargs)
78
+ self.aggregate_callback = {
79
+ "revenue_y_1": "sum",
80
+ "revenue_y0": "sum",
81
+ "revenue_y1": "sum",
82
+ "market_capitalization": "sum",
83
+ "price": "mean",
84
+ "volume_50d": "mean",
85
+ }
86
+
87
+ def _compute(self) -> dict[str, float]:
88
+ """
89
+ Compute/fetch the statistics metrics. If the basket is constituted of multiple instrument, take the average of each performance
90
+
91
+ Returns:
92
+ The metrics as dictionary
93
+ """
94
+ if self.val_date:
95
+ pivot_year = self.val_date.year
96
+ instruments = self.basket_objects
97
+ df_revenue = pd.DataFrame(
98
+ instruments.dl.financials(
99
+ values=[Financial.REVENUE],
100
+ series_type=SeriesType.COMPLETE,
101
+ period_type=PeriodType.ANNUAL,
102
+ from_year=pivot_year - 1,
103
+ to_year=pivot_year + 1,
104
+ )
105
+ )
106
+ if not df_revenue.empty:
107
+ df_revenue = (
108
+ df_revenue.pivot_table(index="instrument_id", columns="year", values="value")
109
+ .rename(
110
+ columns={pivot_year - 1: "revenue_y_1", pivot_year: "revenue_y0", pivot_year + 1: "revenue_y1"}
111
+ )
112
+ .astype(float)
113
+ )
114
+ df_price = (
115
+ pd.DataFrame(
116
+ InstrumentPrice.objects.filter(instrument__in=instruments, calculated=False, date=self.val_date)
117
+ .annotate(fx_rate=self.fx_rate_expression)
118
+ .annotate_market_data()
119
+ .values_list(
120
+ "instrument",
121
+ "internal_market_capitalization",
122
+ "net_value",
123
+ "volume_50d",
124
+ "fx_rate",
125
+ ),
126
+ columns=[
127
+ "instrument",
128
+ "market_capitalization",
129
+ "price",
130
+ "volume_50d",
131
+ "fx_rate",
132
+ ],
133
+ )
134
+ .set_index("instrument")
135
+ .astype(float)
136
+ )
137
+ fx_rate = df_price["fx_rate"]
138
+ df = pd.concat([df_revenue, df_price.drop("fx_rate", axis=1)], axis=1)
139
+ for key in ["revenue_y_1", "revenue_y0", "revenue_y1", "market_capitalization", "price"]:
140
+ if key in df.columns:
141
+ df[key] = df[key] / fx_rate
142
+
143
+ if not df.empty:
144
+ return (
145
+ df.reset_index()
146
+ .agg({k: v for k, v in self.aggregate_callback.items() if k in df.columns})
147
+ .dropna()
148
+ .to_dict()
149
+ )
150
+ return dict()
151
+
152
+
153
+ @register(move_first=True)
154
+ class InstrumentFinancialStatisticsMetricBackend(InstrumentMetricBaseBackend):
155
+ statistic = STATISTICS_METRIC
156
+ keys = [STATISTICS_METRIC]
157
+
158
+ def compute_metrics(self, basket: Instrument) -> Generator[Metric, None, None]:
159
+ val_date = self._get_valid_date(basket)
160
+ metrics = Dataloader(basket, val_date, target_currency_key=self.TARGET_CURRENCY_KEY).compute()
161
+ yield Metric(
162
+ metrics=metrics,
163
+ basket_id=basket.id,
164
+ basket_content_type_id=self.content_type.id,
165
+ key=self.statistic.key,
166
+ date=None,
167
+ )
168
+
169
+ def _get_valid_date(self, instrument: Instrument) -> date:
170
+ if self.val_date is None and instrument.last_valuation_date:
171
+ return instrument.last_valuation_date
172
+ elif self.val_date:
173
+ with suppress(InstrumentPrice.DoesNotExist):
174
+ return instrument.valuations.filter(date__lte=self.val_date).latest("date").date
175
+ raise MetricInvalidParameterException()
176
+
177
+
178
+ @register(move_first=True)
179
+ class InstrumentFinancialStatisticsUSDMetricBackend(InstrumentFinancialStatisticsMetricBackend):
180
+ statistic = STATISTICS_METRIC_USD
181
+ keys = [STATISTICS_METRIC_USD]
182
+ TARGET_CURRENCY_KEY = "USD"
@@ -0,0 +1,14 @@
1
+ def register(move_first: bool = False, override_backend: bool = False):
2
+ """
3
+ Decorator to register the metric backend
4
+ """
5
+ from wbfdm.contrib.metric.registry import backend_registry
6
+
7
+ def _model_wrapper(backend):
8
+ for key in backend.keys:
9
+ backend_registry.set(
10
+ key, backend.BASKET_MODEL_CLASS, backend, move_first=move_first, override_backend=override_backend
11
+ )
12
+ return backend
13
+
14
+ return _model_wrapper
@@ -0,0 +1,23 @@
1
+ from contextlib import suppress
2
+ from datetime import date
3
+ from typing import Any
4
+
5
+
6
+ def compute_metrics(val_date: date, key: str | None = None, basket: Any | None = None, **kwargs):
7
+ """
8
+ Compute and process metrics for a given date using the MetricOrchestrator.
9
+
10
+ Args:
11
+ val_date (date): The validation date for the metrics computation.
12
+ key (Optional[str]): The optional metric backend key to narrow down the set of backends to use. Defaults to None.
13
+ basket (Optional[Any]): An optional basket to narrow down the backend queryset. Defaults to None.
14
+ **kwargs: Additional keyword arguments to pass to the MetricOrchestrator.
15
+
16
+ Returns:
17
+ None
18
+ """
19
+ from wbfdm.contrib.metric.orchestrators import MetricOrchestrator
20
+
21
+ with suppress(KeyError):
22
+ orchestrator = MetricOrchestrator(val_date, key=key, basket=basket, **kwargs)
23
+ orchestrator.process()