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,50 @@
1
+ from wbcore.metadata.configs.titles import TitleViewConfig
2
+ from wbfdm.models import Instrument
3
+
4
+
5
+ class SummaryTableInstrumentChartTitle(TitleViewConfig):
6
+ def get_list_title(self) -> str:
7
+ instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
8
+ return f"{instrument.name_repr} - Financial Summary Table"
9
+
10
+
11
+ class FinancialsGraphInstrumentChartTitle(TitleViewConfig):
12
+ def get_list_title(self) -> str:
13
+ instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
14
+ return f"{instrument.name_repr} - Financials Graph Chart"
15
+
16
+
17
+ class ProfitabilityRatiosInstrumentChartTitle(TitleViewConfig):
18
+ def get_list_title(self) -> str:
19
+ instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
20
+ return f"{instrument.name_repr} - Profitability Ratios Chart"
21
+
22
+
23
+ class CashFlowAnalysisInstrumentTableChartTitle(TitleViewConfig):
24
+ def get_list_title(self) -> str:
25
+ instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
26
+ return f"{instrument.name_repr} - Cash Flow Analysis Table"
27
+
28
+
29
+ class CashFlowAnalysisInstrumentBarChartTitle(TitleViewConfig):
30
+ def get_list_title(self) -> str:
31
+ instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
32
+ return f"{instrument.name_repr} - Cash Flow Analysis Bar Chart"
33
+
34
+
35
+ class NetDebtAndEbitdaInstrumentChartTitle(TitleViewConfig):
36
+ def get_list_title(self) -> str:
37
+ instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
38
+ return f"{instrument.name_repr} - Net debt and EBITDA Chart"
39
+
40
+
41
+ class EarningsInstrumentChartTitle(TitleViewConfig):
42
+ def get_list_title(self) -> str:
43
+ instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
44
+ return f"{instrument.name_repr} - Earnings Analysis Chart"
45
+
46
+
47
+ class FinancialAnalysisGeneratorTitleConfig(TitleViewConfig):
48
+ def get_list_title(self):
49
+ instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
50
+ return f"Ratios for {str(instrument)}"
@@ -0,0 +1,50 @@
1
+ from wbcore.metadata.configs.titles import TitleViewConfig
2
+ from wbfdm.models import Instrument
3
+
4
+
5
+ class InstrumentPriceTitleConfig(TitleViewConfig):
6
+ def get_list_title(self):
7
+ return "Instrument Prices"
8
+
9
+ def get_instance_title(self):
10
+ return "Price: {{_instrument.name}} {{date}}"
11
+
12
+ def get_create_title(self):
13
+ return "New Price"
14
+
15
+
16
+ class InstrumentPriceInstrumentTitleConfig(InstrumentPriceTitleConfig):
17
+ def get_list_title(self):
18
+ return f"{str(self.view.instrument)} - Prices"
19
+
20
+
21
+ class InstrumentPriceStatisticsInstrumentTitleConfig(TitleViewConfig):
22
+ def get_list_title(self):
23
+ return f"{str(self.view.instrument)} - Statistics Chart"
24
+
25
+
26
+ class MonthlyPerformancesInstrumentTitleConfig(TitleViewConfig):
27
+ def get_list_title(self):
28
+ return f"{str(self.view.instrument)} - Monthly Returns Table"
29
+
30
+
31
+ class InstrumentTitleConfigMixin(TitleViewConfig):
32
+ def get_list_title(self):
33
+ assert self.message, "No message has been set"
34
+ instrument = Instrument.objects.get(id=self.view.kwargs["instrument_id"])
35
+ return f"{self.message} {str(instrument)}"
36
+
37
+
38
+ class FinancialStatisticsInstrumentTitleConfig(InstrumentTitleConfigMixin):
39
+ def get_list_title(self):
40
+ return f"{str(self.view.instrument)} - Financial Statistics Chart"
41
+
42
+
43
+ class InstrumentPriceInstrumentDistributionReturnsChartTitleConfig(InstrumentTitleConfigMixin):
44
+ def get_list_title(self):
45
+ return f"{str(self.view.instrument)} - Distribution Returns Chart"
46
+
47
+
48
+ class BestAndWorstReturnsInstrumentTitleConfig(InstrumentTitleConfigMixin):
49
+ def get_list_title(self):
50
+ return f"{str(self.view.instrument)} - Best and Worst returns Table "
@@ -0,0 +1,16 @@
1
+ from wbcore.metadata.configs.titles import TitleViewConfig
2
+
3
+
4
+ class InstrumentRequestTitleConfig(TitleViewConfig):
5
+ def get_list_title(self):
6
+ return "Instrument Requests"
7
+
8
+ def get_instance_title(self):
9
+ try:
10
+ obj = self.view.get_object()
11
+ return f"Instrument Request: {str(obj)}"
12
+ except AssertionError:
13
+ return "Instrument Request: {{status}}"
14
+
15
+ def get_create_title(self):
16
+ return "New Instrument Request"
@@ -0,0 +1,31 @@
1
+ from wbcore.metadata.configs.titles import TitleViewConfig
2
+
3
+
4
+ class InstrumentTitleConfig(TitleViewConfig):
5
+ def get_list_title(self):
6
+ return "Instruments"
7
+
8
+ def get_instance_title(self):
9
+ try:
10
+ obj = self.view.get_object()
11
+ instrument_type = obj.instrument_type.name if obj.instrument_type else "Instrument"
12
+ return f"{instrument_type}: {str(obj)}"
13
+ except AssertionError:
14
+ return "Instrument: {{name}}"
15
+
16
+ def get_create_title(self):
17
+ return "New Instrument"
18
+
19
+
20
+ class SubviewInstrumentTitleViewConfig(TitleViewConfig):
21
+ def get_list_title(self):
22
+ return f"{str(self.view.instrument)} - {self.view.SUBVIEW_NAME} "
23
+
24
+ def get_delete_title(self) -> str:
25
+ return None
26
+
27
+ def get_instance_title(self) -> str:
28
+ return None
29
+
30
+ def get_create_title(self) -> str:
31
+ return None
@@ -0,0 +1,21 @@
1
+ from wbcore.metadata.configs.titles import TitleViewConfig
2
+
3
+
4
+ class InstrumentFavoriteGroupTitleConfig(TitleViewConfig):
5
+ def get_list_title(self):
6
+ return "Instrument Favorite Groups"
7
+
8
+ def get_instance_title(self):
9
+ try:
10
+ obj = self.view.get_object()
11
+ return f"Favorite Group: {str(obj)}"
12
+ except AssertionError:
13
+ return "Instrument: {{name}}"
14
+
15
+ def get_create_title(self):
16
+ return "New Favorite Group"
17
+
18
+
19
+ class ClassifiedInstrumentTitleConfig(TitleViewConfig):
20
+ def get_list_title(self) -> str:
21
+ return "Classified Instruments"
@@ -0,0 +1,13 @@
1
+ from wbcore.metadata.configs.titles import TitleViewConfig
2
+ from wbfdm.enums import MarketDataChartType
3
+
4
+
5
+ class PerformanceSummaryChartTitleConfig(TitleViewConfig):
6
+ def get_list_title(self):
7
+ return f"{self.view.instrument} - Performance Summary"
8
+
9
+
10
+ class MarketDataChartTitleConfig(TitleViewConfig):
11
+ def get_list_title(self):
12
+ market_data_chart_type = MarketDataChartType(self.request.GET.get("chart_type", "close"))
13
+ return f"{self.view.instrument} - {market_data_chart_type.label} Chart"
@@ -0,0 +1,15 @@
1
+ from wbcore.metadata.configs.titles import TitleViewConfig
2
+
3
+
4
+ class InstrumentPriceTitleViewConfig(TitleViewConfig):
5
+ def get_list_title(self):
6
+ return f"{self.view.instrument} - Prices"
7
+
8
+ def get_delete_title(self) -> str:
9
+ return None
10
+
11
+ # def get_instance_title(self) -> str:
12
+ # return None
13
+
14
+ def get_create_title(self) -> str:
15
+ return None
@@ -0,0 +1,10 @@
1
+ from wbcore.metadata.configs.titles import TitleViewConfig
2
+
3
+
4
+ class StatementTitleViewConfig(TitleViewConfig):
5
+ def get_list_title(self):
6
+ try:
7
+ statement_label = self.view.financial_analysis_mapping[self.view.kwargs["statement"]][1]
8
+ return f"{self.view.instrument} - {statement_label} Table"
9
+ except KeyError:
10
+ return f"{self.view.instrument} - Statement"
wbfdm/viewsets/esg.py ADDED
@@ -0,0 +1,72 @@
1
+ import pandas as pd
2
+ from rest_framework.response import Response
3
+ from wbcore import viewsets
4
+ from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
5
+ from wbcore.pandas import fields as pf
6
+ from wbfdm.enums import ESG
7
+ from wbfdm.models.instruments import Instrument
8
+ from wbfdm.serializers import InstrumentControversySerializer
9
+ from wbfdm.viewsets.configs import (
10
+ InstrumentESGControversiesEndpointViewConfig,
11
+ InstrumentESGControversiesTitleViewConfig,
12
+ InstrumentESGControversyDisplayViewConfig,
13
+ InstrumentESGPAIDisplayViewConfig,
14
+ InstrumentESGPAIEndpointViewConfig,
15
+ InstrumentESGPAITitleViewConfig,
16
+ )
17
+
18
+ from .mixins import InstrumentMixin
19
+
20
+
21
+ class InstrumentESGControversiesViewSet(InstrumentMixin, viewsets.ViewSet):
22
+ IDENTIFIER = "wbfdm:instrument-controversy"
23
+ display_config_class = InstrumentESGControversyDisplayViewConfig
24
+ title_config_class = InstrumentESGControversiesTitleViewConfig
25
+ endpoint_config_class = InstrumentESGControversiesEndpointViewConfig
26
+ serializer_class = InstrumentControversySerializer
27
+ queryset = Instrument.objects.none()
28
+ permission_classes = []
29
+
30
+ def list(self, request, instrument_id):
31
+ queryset = self.get_queryset()
32
+ serializer = self.get_serializer_class()
33
+ serializer = serializer(queryset, many=True)
34
+ return Response({"results": serializer.data})
35
+
36
+ def get_queryset(self):
37
+ return sorted(
38
+ Instrument.objects.filter(id=self.instrument.id).dl.esg_controversies(),
39
+ key=lambda x: (x["review"] is None, x["review"]),
40
+ reverse=True,
41
+ )
42
+
43
+
44
+ class InstrumentESGPAIViewSet(InstrumentMixin, ExportPandasAPIViewSet):
45
+ queryset = Instrument.objects.none()
46
+ display_config_class = InstrumentESGPAIDisplayViewConfig
47
+ endpoint_config_class = InstrumentESGPAIEndpointViewConfig
48
+ title_config_class = InstrumentESGPAITitleViewConfig
49
+
50
+ def get_queryset(self):
51
+ return Instrument.objects.filter(id=self.instrument.id)
52
+
53
+ def get_pandas_fields(self, request):
54
+ return pf.PandasFields(
55
+ fields=[
56
+ pf.PKField(key="index", label="ID"),
57
+ pf.IntegerField(key="section", label="Section"),
58
+ pf.TextField(key="asi", label="Adverse sustainability indicator"),
59
+ pf.TextField(key="metric", label="Metric"),
60
+ pf.TextField(key="factor", label="Factor"),
61
+ pf.FloatField(key="value", label="Value"),
62
+ ]
63
+ )
64
+
65
+ def get_dataframe(self, request, queryset, **kwargs):
66
+ df = pd.DataFrame(queryset.dl.esg(values=list(ESG))).reset_index()
67
+ if not df.empty:
68
+ ESG_mapping = ESG.mapping()
69
+ df[["section", "asi", "metric", "factor"]] = pd.DataFrame(
70
+ df.factor_code.map(ESG_mapping).tolist(), index=df.index
71
+ )
72
+ return df
@@ -0,0 +1,63 @@
1
+ from wbcore import viewsets
2
+ from wbcore.permissions.permissions import InternalUserPermissionMixin
3
+ from wbfdm.filters import ExchangeFilterSet
4
+ from wbfdm.models import Exchange
5
+ from wbfdm.serializers import ExchangeModelSerializer, ExchangeRepresentationSerializer
6
+
7
+ from .configs import (
8
+ ExchangeButtonConfig,
9
+ ExchangeDisplayConfig,
10
+ ExchangeEndpointConfig,
11
+ ExchangeTitleConfig,
12
+ )
13
+
14
+
15
+ class ExchangeRepresentationViewSet(InternalUserPermissionMixin, viewsets.RepresentationViewSet):
16
+ queryset = Exchange.objects.all()
17
+ serializer_class = ExchangeRepresentationSerializer
18
+
19
+ search_fields = (
20
+ "name",
21
+ "mic_code",
22
+ "operating_mic_code",
23
+ "bbg_exchange_codes",
24
+ "bbg_composite_primary",
25
+ "bbg_composite",
26
+ "refinitiv_mnemonic",
27
+ "refinitiv_identifier_code",
28
+ "city__name",
29
+ "website",
30
+ "comments",
31
+ )
32
+ ordering_fields = search_fields
33
+ ordering = ["name"]
34
+
35
+
36
+ class ExchangeModelViewSet(InternalUserPermissionMixin, viewsets.ModelViewSet):
37
+ filterset_class = ExchangeFilterSet
38
+ serializer_class = ExchangeModelSerializer
39
+ queryset = Exchange.objects.select_related(
40
+ "country",
41
+ "city",
42
+ )
43
+
44
+ search_fields = (
45
+ "name",
46
+ "mic_code",
47
+ "operating_mic_code",
48
+ "bbg_exchange_codes",
49
+ "bbg_composite_primary",
50
+ "refinitiv_mnemonic",
51
+ "bbg_composite",
52
+ "refinitiv_identifier_code",
53
+ "city__name",
54
+ "website",
55
+ "comments",
56
+ )
57
+ ordering_fields = search_fields
58
+ ordering = ["name"]
59
+
60
+ display_config_class = ExchangeDisplayConfig
61
+ button_config_class = ExchangeButtonConfig
62
+ title_config_class = ExchangeTitleConfig
63
+ endpoint_config_class = ExchangeEndpointConfig
@@ -0,0 +1,3 @@
1
+ from .statement_with_estimates import StatementWithEstimatesPandasViewSet
2
+ from .financial_metric_analysis import FinancialMetricAnalysisPandasViewSet
3
+ from .financial_ratio_analysis import ValuationRatioChartViewSet
@@ -0,0 +1,85 @@
1
+ import pandas as pd
2
+ from django.utils.functional import cached_property
3
+ from rest_framework.exceptions import ParseError
4
+ from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
5
+ from wbcore.pandas import fields as pf
6
+ from wbcore.pandas.utils import override_number_to_percent
7
+ from wbcore.serializers.fields.types import DisplayMode
8
+ from wbfdm.analysis.financial_analysis.financial_metric_analysis import (
9
+ financial_metric_estimate_analysis,
10
+ financial_metric_growths,
11
+ )
12
+ from wbfdm.enums import Financial
13
+ from wbfdm.filters import GroupKeyFinancialsFilterSet
14
+ from wbfdm.models.instruments import Instrument
15
+ from wbfdm.viewsets.configs.display.statement_with_estimates import (
16
+ StatementWithEstimatesDisplayViewConfig,
17
+ )
18
+ from wbfdm.viewsets.configs.endpoints.statements import StatementsEndpointViewConfig
19
+ from wbfdm.viewsets.configs.titles.statement_with_estimates import (
20
+ StatementTitleViewConfig,
21
+ )
22
+
23
+ from ..mixins import InstrumentMixin
24
+
25
+
26
+ class FinancialMetricAnalysisPandasViewSet(InstrumentMixin, ExportPandasAPIViewSet):
27
+ queryset = Instrument.objects.none()
28
+ display_config_class = StatementWithEstimatesDisplayViewConfig
29
+ endpoint_config_class = StatementsEndpointViewConfig
30
+ title_config_class = StatementTitleViewConfig
31
+ filterset_class = GroupKeyFinancialsFilterSet
32
+
33
+ def get_queryset(self):
34
+ return Instrument.objects.filter(id=self.instrument.id)
35
+
36
+ def get_pandas_fields(self, request):
37
+ return pf.PandasFields(
38
+ fields=[
39
+ *[pf.FloatField(key=field, label=field, display_mode=DisplayMode.SHORTENED) for field in self.columns],
40
+ pf.PKField(key="id", label="ID"),
41
+ pf.CharField(key="financial", label="Financial"),
42
+ pf.JsonField(key="_overwrites", label="Overwrites"),
43
+ ]
44
+ )
45
+
46
+ def get_dataframe(self, request, queryset, **kwargs):
47
+ if group_keys := request.GET.get("group_keys"):
48
+ try:
49
+ financial = Financial(group_keys.lower())
50
+ except ValueError:
51
+ raise ParseError()
52
+ df, self._estimate_mapping, self._columns = financial_metric_estimate_analysis(
53
+ queryset.first().id, financial
54
+ )
55
+ empty_row = pd.Series([None], dtype="float64", name="empty_row")
56
+ df_growth = financial_metric_growths(queryset.first().id, financial)
57
+
58
+ df = pd.concat([df_growth, df, empty_row]).dropna(how="all")
59
+ if "financial" in df.columns:
60
+ override_number_to_percent(df, df["financial"].str.contains("(%)"))
61
+
62
+ df = df.rename(columns={"index": "id"})
63
+ return df
64
+
65
+ return pd.DataFrame()
66
+
67
+ @cached_property
68
+ def columns(self):
69
+ if not hasattr(self, "_columns"):
70
+ self.get_dataframe(self.request, self.get_queryset())
71
+ return self._columns
72
+
73
+ @property
74
+ def year_columns(self):
75
+ yield from filter(lambda col: "Y" in col, self.columns)
76
+
77
+ @property
78
+ def interim_columns(self):
79
+ yield from filter(lambda col: "Y" not in col, self.columns)
80
+
81
+ @cached_property
82
+ def estimate_mapping(self):
83
+ if not hasattr(self, "_estimate_mapping"):
84
+ self.get_dataframe(self.request, self.get_queryset())
85
+ return self._estimate_mapping
@@ -0,0 +1,85 @@
1
+ from contextlib import suppress
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ import plotly.express as px
6
+ import plotly.graph_objects as go
7
+ from wbcore import viewsets
8
+ from wbcore.utils.date import get_date_interval_from_request
9
+ from wbfdm.analysis.financial_analysis.financial_ratio_analysis import (
10
+ FinancialRatio,
11
+ get_financial_ratios,
12
+ )
13
+ from wbfdm.filters import FinancialRatioFilterSet
14
+ from wbfdm.models.instruments import Instrument
15
+
16
+ from ..configs import ValuationRatioChartTitleConfig
17
+ from ..mixins import InstrumentMixin
18
+
19
+
20
+ class ValuationRatioChartViewSet(InstrumentMixin, viewsets.ChartViewSet):
21
+ queryset = Instrument.objects.all()
22
+ filterset_class = FinancialRatioFilterSet
23
+ title_config_class = ValuationRatioChartTitleConfig
24
+
25
+ def get_queryset(self):
26
+ return super().get_queryset().filter(id=self.instrument.id)
27
+
28
+ def get_plotly(self, queryset):
29
+ pd.options.plotting.backend = "plotly"
30
+
31
+ ratios = [FinancialRatio.PE, FinancialRatio.PS, FinancialRatio.PB, FinancialRatio.PFCF]
32
+ ttm = False if self.request.GET.get("ttm", "true") == "false" else True
33
+ start, end = get_date_interval_from_request(self.request, date_range_fieldname="period")
34
+
35
+ df = get_financial_ratios(
36
+ self.instrument.id,
37
+ ratios,
38
+ from_date=start,
39
+ to_date=end,
40
+ ttm=ttm,
41
+ )
42
+ fig = go.Figure()
43
+ colors = iter(px.colors.qualitative.T10)
44
+
45
+ for ratio, color in zip(ratios, colors):
46
+ with suppress(AttributeError):
47
+ series = getattr(df, ratio.value)
48
+
49
+ fig.add_trace(
50
+ go.Scatter(
51
+ x=series.index,
52
+ y=series,
53
+ line=dict(color=color),
54
+ name=f"{ratio.label} {'TTM' if ttm else 'FTM'}",
55
+ legendgroup=f"{ratio.label} {'TTM' if ttm else 'FTM'}",
56
+ visible="legendonly" if ratio != FinancialRatio.PE else True,
57
+ ),
58
+ )
59
+
60
+ # fig = df.plot()
61
+ fig.update_layout(
62
+ template="plotly_white",
63
+ legend=dict(x=0.02, y=0.98, bgcolor="rgba(0,0,0,0)"),
64
+ margin=dict(l=0, r=0, t=0, b=0),
65
+ showlegend=True,
66
+ )
67
+
68
+ for i, d in enumerate(fig.data):
69
+ with suppress(AttributeError, IndexError, TypeError, OverflowError):
70
+ if (v := d.y[-1]) and not np.isnan(v):
71
+ fig.add_scatter(
72
+ x=[d.x[-1]],
73
+ y=[v],
74
+ name=d.name,
75
+ mode="markers+text",
76
+ text=round(v),
77
+ textfont=dict(color=d.line.color),
78
+ textposition="middle right",
79
+ marker=dict(color=d.line.color, size=12, symbol="circle"),
80
+ legendgroup=d.name,
81
+ showlegend=False,
82
+ visible="legendonly" if i != 0 else True,
83
+ )
84
+
85
+ return fig
@@ -0,0 +1,145 @@
1
+ from contextlib import suppress
2
+
3
+ import numpy as np
4
+ from django.conf import settings
5
+ from django.contrib.messages import warning
6
+ from django.utils.functional import cached_property
7
+ from wbcore.cache.decorators import cache_table
8
+ from wbcore.contrib.io.viewsets import ExportPandasAPIViewSet
9
+ from wbcore.pandas import fields as pf
10
+ from wbcore.serializers.fields.types import DisplayMode
11
+ from wbcore.utils.date import get_next_day_timedelta
12
+ from wbfdm.analysis.financial_analysis.statement_with_estimates import (
13
+ StatementWithEstimates,
14
+ )
15
+ from wbfdm.enums import CalendarType
16
+ from wbfdm.filters import StatementWithEstimateFilter
17
+ from wbfdm.models.instruments import Instrument
18
+ from wbfdm.viewsets.configs.display.statement_with_estimates import (
19
+ StatementWithEstimatesDisplayViewConfig,
20
+ )
21
+ from wbfdm.viewsets.configs.endpoints.statements import StatementsEndpointViewConfig
22
+ from wbfdm.viewsets.configs.titles.statement_with_estimates import (
23
+ StatementTitleViewConfig,
24
+ )
25
+
26
+ from ..mixins import InstrumentMixin
27
+
28
+
29
+ @cache_table(
30
+ timeout=lambda view: get_next_day_timedelta(),
31
+ key_prefix=lambda view: f"_{view.instrument.id}_{view.financial_analysis_key}_{view.calendar_type}",
32
+ )
33
+ class StatementWithEstimatesPandasViewSet(InstrumentMixin, ExportPandasAPIViewSet):
34
+ queryset = Instrument.objects.none()
35
+ display_config_class = StatementWithEstimatesDisplayViewConfig
36
+ endpoint_config_class = StatementsEndpointViewConfig
37
+ title_config_class = StatementTitleViewConfig
38
+ filterset_class = StatementWithEstimateFilter
39
+
40
+ financial_analysis_mapping = {
41
+ "income": ("income_statement_with_estimate", "Income Statement"),
42
+ "balancesheet": ("balance_sheet_with_estimate", "Balance Sheet"),
43
+ "cashflow": ("cash_flow_statement_with_estimate", "Cash Flow Statement"),
44
+ "ratios": ("ratios_with_estimate", "Ratios"),
45
+ "summary": ("summary_with_estimate", "Summary"),
46
+ "margins": ("margins_with_estimates", "Margins"),
47
+ "cashflow-ratios": ("cashflow_ratios_with_estimates", "Cashflow Ratios"),
48
+ "asset-turnover-ratios": ("asset_turnover_with_estimates", "Asset-Turnover Ratios"),
49
+ "credit": ("credit_with_estimates", "Credit"),
50
+ "long-term-solvency": ("long_term_solvency_with_estimates", "Long-term Solvency"),
51
+ "short-term-liquidity": ("short_term_liquidity_with_estimates", "Short-term Liquidity"),
52
+ }
53
+
54
+ def get_queryset(self):
55
+ return Instrument.objects.filter(id=self.instrument.id)
56
+
57
+ def get_pandas_fields(self, request):
58
+ return pf.PandasFields(
59
+ fields=[
60
+ *[pf.FloatField(key=field, label=field, display_mode=DisplayMode.SHORTENED) for field in self.columns],
61
+ pf.PKField(key="id", label="ID"),
62
+ pf.CharField(key="financial", label="Financial"),
63
+ pf.CharField(key="_group_key", label="Group Key"),
64
+ pf.JsonField(key="_overwrites", label="Overwrites"),
65
+ pf.SparklineField(key="progress", label="Yearly Trend"),
66
+ ]
67
+ )
68
+
69
+ def get_dataframe(self, request, queryset, **kwargs):
70
+ statement_with_estimate = StatementWithEstimates(self.instrument, calendar_type=self.calendar_type)
71
+ financial_analysis_result = getattr(
72
+ statement_with_estimate, self.financial_analysis_mapping[self.financial_analysis_key][0]
73
+ )
74
+ df = financial_analysis_result.formatted_df
75
+
76
+ year_columns = list(filter(lambda col: "Y" in col, df.columns))
77
+ if year_columns:
78
+ df["progress"] = df[year_columns].replace([np.inf, -np.inf, np.nan], None).apply(list, axis=1)
79
+
80
+ self.extra_cache_kwargs["_estimate_mapping"] = financial_analysis_result.estimated_mapping
81
+ self.extra_cache_kwargs["_columns"] = df.columns
82
+ self.extra_cache_kwargs["_errors"] = financial_analysis_result.errors
83
+
84
+ if financial_analysis_result.errors.get("duplicated_interims", []) and not settings.DEBUG:
85
+ with suppress(ModuleNotFoundError):
86
+ from sentry_sdk import capture_message
87
+
88
+ capture_message(
89
+ f"Duplicate index detected for instrument {self.instrument} (id: {self.instrument.id})",
90
+ level="error",
91
+ )
92
+ return df
93
+
94
+ def add_messages(self, request, instance=None, **kwargs):
95
+ if self.errors:
96
+ message = """
97
+ <p>While gathering the financial data, we detected the following issues from the data vendor:</p>
98
+ <ul>
99
+ """
100
+ if duplicated_interim := self.errors.get("duplicated_interim", []):
101
+ message += f"""
102
+ <li>Duplicated data for for interim period <strong>{', '.join(duplicated_interim)}</strong>
103
+ <ul>
104
+ <li>First available data was used and the rest was ignored
105
+ </li>
106
+ </ul>
107
+ </li>
108
+ """
109
+ if missing_data := self.errors.get("missing_data", []):
110
+ for error in missing_data:
111
+ message += f"<li>{error}</li>"
112
+ message += "</ul>"
113
+ warning(request, message, extra_tags="auto_close=0")
114
+
115
+ # Cached attributes as properties
116
+ @cached_property
117
+ def calendar_type(self):
118
+ return CalendarType(self.request.GET.get("calendar_type", CalendarType.FISCAL.value))
119
+
120
+ @cached_property
121
+ def financial_analysis_key(self):
122
+ return self.kwargs.get("statement", "income")
123
+
124
+ @cached_property
125
+ def estimate_mapping(self):
126
+ return getattr(self, "_estimate_mapping", {})
127
+
128
+ @cached_property
129
+ def errors(self):
130
+ return getattr(self, "_errors", {})
131
+
132
+ @cached_property
133
+ def columns(self):
134
+ return self.df.columns
135
+
136
+ def get_ordering_fields(self):
137
+ return self.columns
138
+
139
+ @cached_property
140
+ def year_columns(self):
141
+ return list(filter(lambda col: "Y" in col, self.columns))
142
+
143
+ @cached_property
144
+ def interim_columns(self):
145
+ return list(filter(lambda col: "Y" not in col, self.columns))