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

Files changed (337) 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 +271 -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/contrib/__init__.py +0 -0
  29. wbfdm/contrib/dsws/__init__.py +0 -0
  30. wbfdm/contrib/dsws/client.py +285 -0
  31. wbfdm/contrib/internal/__init__.py +0 -0
  32. wbfdm/contrib/internal/dataloaders/__init__.py +0 -0
  33. wbfdm/contrib/internal/dataloaders/market_data.py +87 -0
  34. wbfdm/contrib/metric/__init__.py +0 -0
  35. wbfdm/contrib/metric/admin/__init__.py +2 -0
  36. wbfdm/contrib/metric/admin/instruments.py +12 -0
  37. wbfdm/contrib/metric/admin/metrics.py +43 -0
  38. wbfdm/contrib/metric/apps.py +10 -0
  39. wbfdm/contrib/metric/backends/__init__.py +2 -0
  40. wbfdm/contrib/metric/backends/base.py +159 -0
  41. wbfdm/contrib/metric/backends/performances.py +265 -0
  42. wbfdm/contrib/metric/backends/statistics.py +182 -0
  43. wbfdm/contrib/metric/decorators.py +14 -0
  44. wbfdm/contrib/metric/dispatch.py +23 -0
  45. wbfdm/contrib/metric/dto.py +88 -0
  46. wbfdm/contrib/metric/exceptions.py +6 -0
  47. wbfdm/contrib/metric/factories.py +33 -0
  48. wbfdm/contrib/metric/filters.py +28 -0
  49. wbfdm/contrib/metric/migrations/0001_initial.py +88 -0
  50. wbfdm/contrib/metric/migrations/0002_remove_instrumentmetric_unique_instrument_metric_and_more.py +26 -0
  51. wbfdm/contrib/metric/migrations/__init__.py +0 -0
  52. wbfdm/contrib/metric/models.py +180 -0
  53. wbfdm/contrib/metric/orchestrators.py +94 -0
  54. wbfdm/contrib/metric/registry.py +80 -0
  55. wbfdm/contrib/metric/serializers.py +44 -0
  56. wbfdm/contrib/metric/tasks.py +27 -0
  57. wbfdm/contrib/metric/tests/__init__.py +0 -0
  58. wbfdm/contrib/metric/tests/backends/__init__.py +0 -0
  59. wbfdm/contrib/metric/tests/backends/test_performances.py +152 -0
  60. wbfdm/contrib/metric/tests/backends/test_statistics.py +48 -0
  61. wbfdm/contrib/metric/tests/conftest.py +92 -0
  62. wbfdm/contrib/metric/tests/test_dto.py +73 -0
  63. wbfdm/contrib/metric/tests/test_models.py +72 -0
  64. wbfdm/contrib/metric/tests/test_tasks.py +24 -0
  65. wbfdm/contrib/metric/tests/test_viewsets.py +79 -0
  66. wbfdm/contrib/metric/urls.py +19 -0
  67. wbfdm/contrib/metric/viewsets/__init__.py +1 -0
  68. wbfdm/contrib/metric/viewsets/configs/__init__.py +1 -0
  69. wbfdm/contrib/metric/viewsets/configs/display.py +92 -0
  70. wbfdm/contrib/metric/viewsets/configs/menus.py +11 -0
  71. wbfdm/contrib/metric/viewsets/configs/utils.py +137 -0
  72. wbfdm/contrib/metric/viewsets/mixins.py +245 -0
  73. wbfdm/contrib/metric/viewsets/viewsets.py +40 -0
  74. wbfdm/contrib/msci/__init__.py +0 -0
  75. wbfdm/contrib/msci/client.py +92 -0
  76. wbfdm/contrib/msci/dataloaders/__init__.py +0 -0
  77. wbfdm/contrib/msci/dataloaders/esg.py +87 -0
  78. wbfdm/contrib/msci/dataloaders/esg_controversies.py +81 -0
  79. wbfdm/contrib/msci/sync.py +58 -0
  80. wbfdm/contrib/msci/tests/__init__.py +0 -0
  81. wbfdm/contrib/msci/tests/conftest.py +1 -0
  82. wbfdm/contrib/msci/tests/test_client.py +70 -0
  83. wbfdm/contrib/qa/__init__.py +0 -0
  84. wbfdm/contrib/qa/apps.py +22 -0
  85. wbfdm/contrib/qa/database_routers.py +25 -0
  86. wbfdm/contrib/qa/dataloaders/__init__.py +0 -0
  87. wbfdm/contrib/qa/dataloaders/adjustments.py +56 -0
  88. wbfdm/contrib/qa/dataloaders/corporate_actions.py +59 -0
  89. wbfdm/contrib/qa/dataloaders/financials.py +83 -0
  90. wbfdm/contrib/qa/dataloaders/market_data.py +117 -0
  91. wbfdm/contrib/qa/dataloaders/officers.py +59 -0
  92. wbfdm/contrib/qa/dataloaders/reporting_dates.py +67 -0
  93. wbfdm/contrib/qa/dataloaders/statements.py +267 -0
  94. wbfdm/contrib/qa/tasks.py +0 -0
  95. wbfdm/dataloaders/__init__.py +0 -0
  96. wbfdm/dataloaders/cache.py +129 -0
  97. wbfdm/dataloaders/protocols.py +112 -0
  98. wbfdm/dataloaders/proxies.py +201 -0
  99. wbfdm/dataloaders/types.py +209 -0
  100. wbfdm/dynamic_preferences_registry.py +45 -0
  101. wbfdm/enums.py +657 -0
  102. wbfdm/factories/__init__.py +13 -0
  103. wbfdm/factories/classifications.py +56 -0
  104. wbfdm/factories/controversies.py +27 -0
  105. wbfdm/factories/exchanges.py +21 -0
  106. wbfdm/factories/instrument_list.py +22 -0
  107. wbfdm/factories/instrument_prices.py +79 -0
  108. wbfdm/factories/instruments.py +63 -0
  109. wbfdm/factories/instruments_relationships.py +31 -0
  110. wbfdm/factories/options.py +66 -0
  111. wbfdm/figures/__init__.py +1 -0
  112. wbfdm/figures/financials/__init__.py +1 -0
  113. wbfdm/figures/financials/financial_analysis_charts.py +469 -0
  114. wbfdm/figures/financials/financials_charts.py +711 -0
  115. wbfdm/filters/__init__.py +31 -0
  116. wbfdm/filters/classifications.py +100 -0
  117. wbfdm/filters/exchanges.py +22 -0
  118. wbfdm/filters/financials.py +95 -0
  119. wbfdm/filters/financials_analysis.py +119 -0
  120. wbfdm/filters/instrument_prices.py +112 -0
  121. wbfdm/filters/instruments.py +198 -0
  122. wbfdm/filters/utils.py +44 -0
  123. wbfdm/import_export/__init__.py +0 -0
  124. wbfdm/import_export/backends/__init__.py +0 -0
  125. wbfdm/import_export/backends/cbinsights/__init__.py +2 -0
  126. wbfdm/import_export/backends/cbinsights/deals.py +44 -0
  127. wbfdm/import_export/backends/cbinsights/equities.py +41 -0
  128. wbfdm/import_export/backends/cbinsights/mixin.py +15 -0
  129. wbfdm/import_export/backends/cbinsights/utils/__init__.py +0 -0
  130. wbfdm/import_export/backends/cbinsights/utils/classifications.py +4150 -0
  131. wbfdm/import_export/backends/cbinsights/utils/client.py +217 -0
  132. wbfdm/import_export/backends/refinitiv/__init__.py +5 -0
  133. wbfdm/import_export/backends/refinitiv/daily_fundamental.py +36 -0
  134. wbfdm/import_export/backends/refinitiv/fiscal_period.py +63 -0
  135. wbfdm/import_export/backends/refinitiv/forecast.py +178 -0
  136. wbfdm/import_export/backends/refinitiv/fundamental.py +103 -0
  137. wbfdm/import_export/backends/refinitiv/geographic_segment.py +32 -0
  138. wbfdm/import_export/backends/refinitiv/instrument.py +55 -0
  139. wbfdm/import_export/backends/refinitiv/instrument_price.py +77 -0
  140. wbfdm/import_export/backends/refinitiv/mixin.py +29 -0
  141. wbfdm/import_export/backends/refinitiv/utils/__init__.py +1 -0
  142. wbfdm/import_export/backends/refinitiv/utils/controller.py +182 -0
  143. wbfdm/import_export/handlers/__init__.py +0 -0
  144. wbfdm/import_export/handlers/instrument.py +253 -0
  145. wbfdm/import_export/handlers/instrument_list.py +101 -0
  146. wbfdm/import_export/handlers/instrument_price.py +71 -0
  147. wbfdm/import_export/handlers/option.py +54 -0
  148. wbfdm/import_export/handlers/private_equities.py +49 -0
  149. wbfdm/import_export/parsers/__init__.py +0 -0
  150. wbfdm/import_export/parsers/cbinsights/__init__.py +0 -0
  151. wbfdm/import_export/parsers/cbinsights/deals.py +39 -0
  152. wbfdm/import_export/parsers/cbinsights/equities.py +56 -0
  153. wbfdm/import_export/parsers/cbinsights/fundamentals.py +45 -0
  154. wbfdm/import_export/parsers/refinitiv/__init__.py +0 -0
  155. wbfdm/import_export/parsers/refinitiv/daily_fundamental.py +7 -0
  156. wbfdm/import_export/parsers/refinitiv/forecast.py +7 -0
  157. wbfdm/import_export/parsers/refinitiv/fundamental.py +9 -0
  158. wbfdm/import_export/parsers/refinitiv/geographic_segment.py +7 -0
  159. wbfdm/import_export/parsers/refinitiv/instrument.py +75 -0
  160. wbfdm/import_export/parsers/refinitiv/instrument_price.py +26 -0
  161. wbfdm/import_export/parsers/refinitiv/utils.py +96 -0
  162. wbfdm/import_export/resources/__init__.py +0 -0
  163. wbfdm/import_export/resources/classification.py +23 -0
  164. wbfdm/import_export/resources/instrument_prices.py +33 -0
  165. wbfdm/import_export/resources/instruments.py +176 -0
  166. wbfdm/jinja2.py +7 -0
  167. wbfdm/management/__init__.py +30 -0
  168. wbfdm/menu.py +11 -0
  169. wbfdm/migrations/0001_initial.py +71 -0
  170. wbfdm/migrations/0002_rename_statements_instrumentlookup_financials_and_more.py +144 -0
  171. wbfdm/migrations/0003_instrument_estimate_backend_and_more.py +34 -0
  172. wbfdm/migrations/0004_rename_financials_instrumentlookup_statements_and_more.py +86 -0
  173. wbfdm/migrations/0005_instrument_corporate_action_backend.py +29 -0
  174. wbfdm/migrations/0006_instrument_officer_backend.py +29 -0
  175. wbfdm/migrations/0007_instrument_country_instrument_currency_and_more.py +117 -0
  176. wbfdm/migrations/0008_controversy.py +75 -0
  177. wbfdm/migrations/0009_alter_controversy_flag_alter_controversy_initiated_and_more.py +85 -0
  178. wbfdm/migrations/0010_classification_classificationgroup_deal_exchange_and_more.py +1299 -0
  179. wbfdm/migrations/0011_delete_instrumentlookup_instrument_corporate_actions_and_more.py +169 -0
  180. wbfdm/migrations/0012_instrumentprice_created_instrumentprice_modified.py +564 -0
  181. wbfdm/migrations/0013_instrument_is_investable_universe_and_more.py +199 -0
  182. wbfdm/migrations/0014_alter_controversy_instrument.py +22 -0
  183. wbfdm/migrations/0015_instrument_instrument_investible_index.py +16 -0
  184. wbfdm/migrations/0016_instrumenttype_name_repr.py +18 -0
  185. wbfdm/migrations/0017_instrument_instrument_security_index.py +16 -0
  186. wbfdm/migrations/0018_instrument_instrument_level_index.py +20 -0
  187. wbfdm/migrations/0019_alter_controversy_source.py +17 -0
  188. wbfdm/migrations/0020_optionaggregate_option_and_more.py +249 -0
  189. wbfdm/migrations/0021_delete_instrumentdailystatistics.py +15 -0
  190. wbfdm/migrations/0022_instrument_cusip_option_open_interest_20d_and_more.py +91 -0
  191. wbfdm/migrations/0023_instrument_unique_ric_instrument_unique_rmc_and_more.py +53 -0
  192. wbfdm/migrations/0024_option_open_interest_10d_option_volume_10d_and_more.py +36 -0
  193. wbfdm/migrations/0025_instrument_is_primary_and_more.py +29 -0
  194. wbfdm/migrations/0026_instrument_is_cash_equivalent.py +30 -0
  195. wbfdm/migrations/0027_remove_instrument_unique_ric_and_more.py +100 -0
  196. wbfdm/migrations/__init__.py +0 -0
  197. wbfdm/models/__init__.py +4 -0
  198. wbfdm/models/esg/__init__.py +1 -0
  199. wbfdm/models/esg/controversies.py +81 -0
  200. wbfdm/models/exchanges/__init__.py +1 -0
  201. wbfdm/models/exchanges/exchanges.py +223 -0
  202. wbfdm/models/fields.py +117 -0
  203. wbfdm/models/fk_fields.py +403 -0
  204. wbfdm/models/indicators.py +0 -0
  205. wbfdm/models/instruments/__init__.py +19 -0
  206. wbfdm/models/instruments/classifications.py +265 -0
  207. wbfdm/models/instruments/instrument_lists.py +120 -0
  208. wbfdm/models/instruments/instrument_prices.py +540 -0
  209. wbfdm/models/instruments/instrument_relationships.py +251 -0
  210. wbfdm/models/instruments/instrument_requests.py +196 -0
  211. wbfdm/models/instruments/instruments.py +991 -0
  212. wbfdm/models/instruments/llm/__init__.py +1 -0
  213. wbfdm/models/instruments/llm/create_instrument_news_relationships.py +78 -0
  214. wbfdm/models/instruments/mixin/__init__.py +0 -0
  215. wbfdm/models/instruments/mixin/financials_computed.py +804 -0
  216. wbfdm/models/instruments/mixin/financials_serializer_fields.py +1407 -0
  217. wbfdm/models/instruments/mixin/instruments.py +294 -0
  218. wbfdm/models/instruments/options.py +225 -0
  219. wbfdm/models/instruments/private_equities.py +59 -0
  220. wbfdm/models/instruments/querysets.py +73 -0
  221. wbfdm/models/instruments/utils.py +41 -0
  222. wbfdm/preferences.py +21 -0
  223. wbfdm/serializers/__init__.py +4 -0
  224. wbfdm/serializers/esg.py +36 -0
  225. wbfdm/serializers/exchanges.py +39 -0
  226. wbfdm/serializers/instruments/__init__.py +37 -0
  227. wbfdm/serializers/instruments/classifications.py +139 -0
  228. wbfdm/serializers/instruments/instrument_lists.py +61 -0
  229. wbfdm/serializers/instruments/instrument_prices.py +73 -0
  230. wbfdm/serializers/instruments/instrument_relationships.py +170 -0
  231. wbfdm/serializers/instruments/instrument_requests.py +61 -0
  232. wbfdm/serializers/instruments/instruments.py +274 -0
  233. wbfdm/serializers/instruments/mixins.py +104 -0
  234. wbfdm/serializers/officers.py +20 -0
  235. wbfdm/signals.py +7 -0
  236. wbfdm/sync/__init__.py +0 -0
  237. wbfdm/sync/abstract.py +31 -0
  238. wbfdm/sync/runner.py +22 -0
  239. wbfdm/tasks.py +69 -0
  240. wbfdm/tests/__init__.py +0 -0
  241. wbfdm/tests/analysis/__init__.py +0 -0
  242. wbfdm/tests/analysis/financial_analysis/__init__.py +0 -0
  243. wbfdm/tests/analysis/financial_analysis/test_statement_with_estimates.py +392 -0
  244. wbfdm/tests/analysis/financial_analysis/test_utils.py +322 -0
  245. wbfdm/tests/analysis/test_esg.py +159 -0
  246. wbfdm/tests/conftest.py +92 -0
  247. wbfdm/tests/dataloaders/__init__.py +0 -0
  248. wbfdm/tests/dataloaders/test_cache.py +73 -0
  249. wbfdm/tests/models/__init__.py +0 -0
  250. wbfdm/tests/models/test_classifications.py +99 -0
  251. wbfdm/tests/models/test_exchanges.py +7 -0
  252. wbfdm/tests/models/test_instrument_list.py +117 -0
  253. wbfdm/tests/models/test_instrument_prices.py +306 -0
  254. wbfdm/tests/models/test_instruments.py +202 -0
  255. wbfdm/tests/models/test_merge.py +99 -0
  256. wbfdm/tests/models/test_options.py +69 -0
  257. wbfdm/tests/test_tasks.py +6 -0
  258. wbfdm/tests/tests.py +10 -0
  259. wbfdm/urls.py +222 -0
  260. wbfdm/utils.py +54 -0
  261. wbfdm/viewsets/__init__.py +10 -0
  262. wbfdm/viewsets/configs/__init__.py +5 -0
  263. wbfdm/viewsets/configs/buttons/__init__.py +8 -0
  264. wbfdm/viewsets/configs/buttons/classifications.py +23 -0
  265. wbfdm/viewsets/configs/buttons/exchanges.py +9 -0
  266. wbfdm/viewsets/configs/buttons/instrument_prices.py +49 -0
  267. wbfdm/viewsets/configs/buttons/instruments.py +283 -0
  268. wbfdm/viewsets/configs/display/__init__.py +22 -0
  269. wbfdm/viewsets/configs/display/classifications.py +138 -0
  270. wbfdm/viewsets/configs/display/esg.py +75 -0
  271. wbfdm/viewsets/configs/display/exchanges.py +42 -0
  272. wbfdm/viewsets/configs/display/instrument_lists.py +137 -0
  273. wbfdm/viewsets/configs/display/instrument_prices.py +199 -0
  274. wbfdm/viewsets/configs/display/instrument_requests.py +116 -0
  275. wbfdm/viewsets/configs/display/instruments.py +618 -0
  276. wbfdm/viewsets/configs/display/instruments_relationships.py +65 -0
  277. wbfdm/viewsets/configs/display/monthly_performances.py +72 -0
  278. wbfdm/viewsets/configs/display/officers.py +16 -0
  279. wbfdm/viewsets/configs/display/prices.py +21 -0
  280. wbfdm/viewsets/configs/display/statement_with_estimates.py +101 -0
  281. wbfdm/viewsets/configs/display/statements.py +48 -0
  282. wbfdm/viewsets/configs/endpoints/__init__.py +41 -0
  283. wbfdm/viewsets/configs/endpoints/classifications.py +87 -0
  284. wbfdm/viewsets/configs/endpoints/esg.py +20 -0
  285. wbfdm/viewsets/configs/endpoints/exchanges.py +6 -0
  286. wbfdm/viewsets/configs/endpoints/financials_analysis.py +65 -0
  287. wbfdm/viewsets/configs/endpoints/instrument_lists.py +38 -0
  288. wbfdm/viewsets/configs/endpoints/instrument_prices.py +51 -0
  289. wbfdm/viewsets/configs/endpoints/instrument_requests.py +20 -0
  290. wbfdm/viewsets/configs/endpoints/instruments.py +13 -0
  291. wbfdm/viewsets/configs/endpoints/instruments_relationships.py +31 -0
  292. wbfdm/viewsets/configs/endpoints/statements.py +6 -0
  293. wbfdm/viewsets/configs/menus/__init__.py +9 -0
  294. wbfdm/viewsets/configs/menus/classifications.py +19 -0
  295. wbfdm/viewsets/configs/menus/exchanges.py +10 -0
  296. wbfdm/viewsets/configs/menus/instrument_lists.py +10 -0
  297. wbfdm/viewsets/configs/menus/instruments.py +20 -0
  298. wbfdm/viewsets/configs/menus/instruments_relationships.py +33 -0
  299. wbfdm/viewsets/configs/titles/__init__.py +42 -0
  300. wbfdm/viewsets/configs/titles/classifications.py +79 -0
  301. wbfdm/viewsets/configs/titles/esg.py +11 -0
  302. wbfdm/viewsets/configs/titles/exchanges.py +12 -0
  303. wbfdm/viewsets/configs/titles/financial_ratio_analysis.py +6 -0
  304. wbfdm/viewsets/configs/titles/financials_analysis.py +50 -0
  305. wbfdm/viewsets/configs/titles/instrument_prices.py +50 -0
  306. wbfdm/viewsets/configs/titles/instrument_requests.py +16 -0
  307. wbfdm/viewsets/configs/titles/instruments.py +31 -0
  308. wbfdm/viewsets/configs/titles/instruments_relationships.py +21 -0
  309. wbfdm/viewsets/configs/titles/market_data.py +13 -0
  310. wbfdm/viewsets/configs/titles/prices.py +15 -0
  311. wbfdm/viewsets/configs/titles/statement_with_estimates.py +10 -0
  312. wbfdm/viewsets/esg.py +72 -0
  313. wbfdm/viewsets/exchanges.py +63 -0
  314. wbfdm/viewsets/financial_analysis/__init__.py +3 -0
  315. wbfdm/viewsets/financial_analysis/financial_metric_analysis.py +85 -0
  316. wbfdm/viewsets/financial_analysis/financial_ratio_analysis.py +85 -0
  317. wbfdm/viewsets/financial_analysis/statement_with_estimates.py +145 -0
  318. wbfdm/viewsets/instruments/__init__.py +80 -0
  319. wbfdm/viewsets/instruments/classifications.py +279 -0
  320. wbfdm/viewsets/instruments/financials_analysis.py +614 -0
  321. wbfdm/viewsets/instruments/instrument_lists.py +77 -0
  322. wbfdm/viewsets/instruments/instrument_prices.py +542 -0
  323. wbfdm/viewsets/instruments/instrument_requests.py +51 -0
  324. wbfdm/viewsets/instruments/instruments.py +106 -0
  325. wbfdm/viewsets/instruments/instruments_relationships.py +235 -0
  326. wbfdm/viewsets/instruments/utils.py +27 -0
  327. wbfdm/viewsets/market_data.py +172 -0
  328. wbfdm/viewsets/mixins.py +9 -0
  329. wbfdm/viewsets/officers.py +27 -0
  330. wbfdm/viewsets/prices.py +62 -0
  331. wbfdm/viewsets/statements/__init__.py +1 -0
  332. wbfdm/viewsets/statements/statements.py +100 -0
  333. wbfdm/viewsets/technical_analysis/__init__.py +1 -0
  334. wbfdm/viewsets/technical_analysis/monthly_performances.py +93 -0
  335. wbfdm-2.2.1.dist-info/METADATA +15 -0
  336. wbfdm-2.2.1.dist-info/RECORD +337 -0
  337. wbfdm-2.2.1.dist-info/WHEEL +5 -0
@@ -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()
@@ -0,0 +1,88 @@
1
+ from dataclasses import dataclass, field
2
+ from datetime import date
3
+ from typing import Any, Optional, Type
4
+
5
+ from django.db.models import Aggregate, FloatField, Sum
6
+
7
+
8
+ @dataclass
9
+ class MetricField:
10
+ """
11
+ A DTO Class to represent the metric subfield computed by metric backend for a given Metric Key.
12
+
13
+ list_display_kwargs: Contains keyword argument to be inserted in a dp.Field
14
+ """
15
+
16
+ key: str
17
+ label: str
18
+ list_display_kwargs: dict[str, Any] = field(default_factory=dict)
19
+ aggregate: Type[Aggregate] | None = Sum
20
+ serializer_kwargs: dict[str, Any] = field(default_factory=dict)
21
+ field_type: type = FloatField
22
+ decorators: list[dict[str, str]] = field(
23
+ default_factory=list
24
+ ) # Set into a wbcore decorator accordingly (e.g. to show the metric into a specific currency)
25
+ help_text: str | None = None # define if the field needs to show a particular help text
26
+
27
+
28
+ @dataclass
29
+ class MetricKey:
30
+ """
31
+ a DTO class to represent the metric generated by a metric backend. It contains general information regarding the type of field the metric stored (e.g. Is percent? precision? etc..)
32
+ """
33
+
34
+ key: str
35
+ label: str
36
+ subfields: list[MetricField]
37
+ extra_subfields: list[MetricField] = field(default_factory=list)
38
+ additional_prefixes: list[str] = field(
39
+ default_factory=list
40
+ ) # Define the optional category/groups stored in the metric json (e.g if a variable "x" is stored as well as "past_x", a prefix would be "past")
41
+ subfields_map: dict[str, MetricField] = field(init=False)
42
+ subfields_filter_map: dict[str, str] = field(init=False)
43
+
44
+ def __post_init__(self):
45
+ subfields_map = dict()
46
+ subfields_filter_map = dict()
47
+ for subfield in self.subfields:
48
+ subfields_map[f"{self.key}_{subfield.key}"] = subfield
49
+ subfields_filter_map[f"{self.key}_{subfield.key}"] = subfield.key
50
+ for prefix in self.additional_prefixes:
51
+ subfields_map[f"{self.key}_{prefix}_{subfield.key}"] = subfield
52
+ subfields_filter_map[f"{self.key}_{prefix}_{subfield.key}"] = f"{prefix}_{subfield.key}"
53
+ object.__setattr__(self, "subfields_map", subfields_map)
54
+ object.__setattr__(self, "subfields_filter_map", subfields_filter_map)
55
+
56
+ def __hash__(self):
57
+ return hash(self.key)
58
+
59
+ def get_fields(self, with_prefixed_key: bool = False):
60
+ """
61
+ Returns a generator of key identifier and metric label containing in that metric
62
+
63
+ Args:
64
+ with_prefixed_key: Default to False. If True yield also the prefixed fields
65
+
66
+ Returns:
67
+ a generator of tuple of strings
68
+ """
69
+ for subfield in self.subfields:
70
+ yield f"{self.key}_{subfield.key}", f"{self.label} {subfield.label}"
71
+ if with_prefixed_key:
72
+ for prefix in self.additional_prefixes:
73
+ yield f"{self.key}_{prefix}_{subfield.key}", f"{self.label} {subfield.label} ({prefix.title()})"
74
+
75
+
76
+ @dataclass
77
+ class Metric:
78
+ """
79
+ A DTO class to hold the metrics values computed by a metric backend. The object will be parsed by an orchestrator into a InstrumentMetric object
80
+ """
81
+
82
+ basket_id: int
83
+ basket_content_type_id: int
84
+ key: str
85
+ metrics: dict
86
+ date: Optional[date] = None
87
+ instrument_id: int | None = None
88
+ dependency_metrics: list["Metric"] = field(default_factory=list)
@@ -0,0 +1,6 @@
1
+ class MetricInvalidParameterException(Exception):
2
+ """
3
+ If instantiated backend parameters are invalid, the orchestrator or backend itself are expected to raise that exception
4
+ """
5
+
6
+ pass
@@ -0,0 +1,33 @@
1
+ import factory
2
+ from django.contrib.contenttypes.models import ContentType
3
+ from faker import Faker
4
+ from wbfdm.contrib.metric.models import InstrumentMetric
5
+ from wbfdm.factories import InstrumentFactory
6
+ from wbfdm.models import Instrument
7
+
8
+ from .registry import backend_registry
9
+
10
+ fake = Faker()
11
+
12
+
13
+ def _get_metrics(key: str):
14
+ backend = backend_registry[key, Instrument]
15
+ metrics = {}
16
+ for sub_field in backend.keys[0].subfields:
17
+ metrics[sub_field.key] = fake.pyfloat()
18
+ return metrics
19
+
20
+
21
+ class InstrumentMetricFactory(factory.django.DjangoModelFactory):
22
+ class Meta:
23
+ model = InstrumentMetric
24
+ # django_get_or_create = ["basket_content_type", "basket_id", "instrument", "date", "key"]
25
+
26
+ basket = factory.SubFactory(InstrumentFactory)
27
+ basket_content_type = factory.LazyAttribute(lambda o: ContentType.objects.get_for_model(Instrument))
28
+ basket_id = factory.LazyAttribute(lambda o: o.basket.id)
29
+
30
+ date = factory.Faker("date_object")
31
+ key = factory.Iterator(backend_registry.keys())
32
+ metrics = factory.LazyAttribute(lambda o: _get_metrics(o.key))
33
+ parent_metric = None
@@ -0,0 +1,28 @@
1
+ from django.contrib.contenttypes.models import ContentType
2
+ from django.utils.translation import gettext as _
3
+ from wbcore import filters
4
+ from wbfdm.contrib.metric.models import InstrumentMetric
5
+
6
+ from .registry import backend_registry
7
+
8
+
9
+ class InstrumentMetricFilterSet(filters.FilterSet):
10
+ key = filters.ChoiceFilter(choices=backend_registry.get_choices(), label="Key")
11
+ basket_content_type = filters.ModelChoiceFilter(
12
+ queryset=ContentType.objects.all(),
13
+ endpoint="wbcore:contenttyperepresentation-list",
14
+ value_key="id",
15
+ label_key="{{app_label}} | {{model}}",
16
+ label=_("Basket Content Type"),
17
+ )
18
+
19
+ class Meta:
20
+ model = InstrumentMetric
21
+ fields = {
22
+ "basket_content_type": ["exact"],
23
+ "basket_id": ["exact"],
24
+ "instrument": ["exact"],
25
+ "key": ["exact"],
26
+ "date": ["lte", "gte", "exact"],
27
+ "parent_metric": ["exact", "isnull"],
28
+ }