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,469 @@
1
+ import enum
2
+ from datetime import date
3
+ from types import DynamicClassAttribute
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ from django.db import models
8
+ from wbcore.contrib.currency.models import CurrencyFXRates
9
+ from wbfdm.models import Instrument
10
+
11
+
12
+ class PeriodChoices(models.TextChoices):
13
+ TTM = "TTM", "TTM"
14
+ NTM = "FTM", "FTM"
15
+ FY1 = "FY1", "FY+1"
16
+ FY2 = "FY2", "FY+2"
17
+
18
+
19
+ class VariableChoicesChartLabel(enum.Enum):
20
+ PE = "Price-to-Earnings PE"
21
+ PEG = "Price-to-Earnings-to-Growth PEG"
22
+ PS = "Price-to-Sales PS"
23
+ PFCF = "Price-to-FreeCashflow P/FCF"
24
+ EVEBITDA = "EV-to-EBITDA"
25
+ EV = "Enterprise Value"
26
+ EPSG = "EPS growth"
27
+ REVG = "Revenue growth"
28
+ FCFG = "FCF growth"
29
+ MKTCAP = "Market Cap"
30
+
31
+
32
+ class VariableFormatChoices(enum.Enum):
33
+ PE = ".1f}x"
34
+ PEG = ".2f}"
35
+ PS = ".1f}x"
36
+ PFCF = ".1f}x"
37
+ EVEBITDA = ".1f}x"
38
+ EV = ".0f}"
39
+ EPSG = ".1%}"
40
+ REVG = ".1%}"
41
+ FCFG = ".1%}"
42
+ MKTCAP = ",.0f}bn"
43
+
44
+
45
+ class VariableChoices(models.TextChoices):
46
+ PE = "PE", "P/E"
47
+ PEG = "PEG", "PEG"
48
+ PS = "PS", "P/S"
49
+ PFCF = "PFCF", "P/FCF"
50
+ EVEBITDA = "EVEBITDA", "EV/EBITDA"
51
+ EV = "EV", "Enterprise Value"
52
+ EPSG = "EPSG", "EPS Growth"
53
+ REVG = "REVG", "Revenue Growth"
54
+ FCFG = "FCFG", "FCF Growth"
55
+ MKTCAP = "MKTCAP", "Market Cap"
56
+
57
+ @DynamicClassAttribute
58
+ def format(self):
59
+ """The format of the Enum member."""
60
+ return VariableFormatChoices[self._value_].value
61
+
62
+ @DynamicClassAttribute
63
+ def chart_label(self):
64
+ """The chart_label of the Enum member."""
65
+ return VariableChoicesChartLabel[self._value_].value
66
+
67
+
68
+ def _interpolate(decreasing: pd.Series, increasing: pd.Series, factor) -> float:
69
+ return (decreasing * (1 - factor)) + (increasing * factor)
70
+
71
+
72
+ def _adapt_growth(first, second) -> np.ndarray:
73
+ """
74
+ In finance some variables, e.g., earnings, may pass from negative to positive and a standard percentage change
75
+ formula is inappropriate. This formula modifies the calculation for such cases.
76
+
77
+ Parameters
78
+ ----------
79
+ first = past data point/series
80
+ second = more recent data point/series
81
+
82
+ Returns
83
+ -------
84
+ A float number representing growth (percentage change)
85
+
86
+ """
87
+ return np.where(first > 0, second / first - 1, (second + abs(first)) / abs(first))
88
+
89
+
90
+ class FinancialAnalysisGenerator:
91
+ def __init__(self, instruments: list, from_date: date, to_date: date, with_related: bool = False):
92
+ """
93
+ Initialize a generator for a specific instrument
94
+
95
+ Parameters
96
+ ----------
97
+ instrument = instrument of choice
98
+ from_date
99
+ to_date
100
+ with_related = extend the instrument to a list that includes all related instruments
101
+ """
102
+ self.instruments = instruments
103
+ self.from_date = from_date
104
+ self.to_date = to_date
105
+
106
+ if with_related:
107
+ self.instruments.extend(
108
+ list(
109
+ Instrument.objects.filter(
110
+ models.Q(instrument_classification_related__classified_instrument__instrument__in=instruments)
111
+ )
112
+ )
113
+ )
114
+ self.instruments_repr_map = {i.id: i.name_repr for i in self.instruments}
115
+ self.currency_map = {i.id: i.currency.id for i in self.instruments}
116
+
117
+ def build_df(
118
+ self,
119
+ instrument_prices_field_names: list[str] = [],
120
+ fundamental_field_names: list[str] = [],
121
+ forecast_field_names: list[str] = [],
122
+ daily_fundamental_field_names: list[str] = [],
123
+ ):
124
+ """
125
+ Used to returns a df with all the variables passed in four separate lists
126
+
127
+ Parameters
128
+ ----------
129
+ instrument_prices_field_names
130
+ fundamental_field_names
131
+ forecast_field_names
132
+ daily_fundamental_field_names
133
+
134
+ Returns
135
+ -------
136
+ A df with all the variables in columns and date/instrument index
137
+ """
138
+
139
+ # df_list = []
140
+ # if instrument_prices_field_names:
141
+ # df_append = pd.DataFrame(
142
+ # InstrumentPrice.objects.filter(
143
+ # instrument__in=self.instruments, date__gte=self.from_date, date__lte=self.to_date, calculated=False
144
+ # ).values("date", "instrument", "net_value", "outstanding_shares")
145
+ # )
146
+ # if not df_append.empty:
147
+ # df_list.append(df_append.set_index(["instrument", "date"]))
148
+ #
149
+ # end_date_df = pd.DataFrame(
150
+ # FiscalPeriod.objects.filter(
151
+ # period_type=FiscalPeriod.PeriodTypeChoice.ANNUAL,
152
+ # period_interim=False,
153
+ # instrument__in=self.instruments,
154
+ # ).values("period_end_date", "instrument")
155
+ # )
156
+ # if not end_date_df.empty:
157
+ # if fundamental_field_names:
158
+ # df_append = pd.DataFrame(
159
+ # Fundamental.annual_objects.filter(
160
+ # instrument__in=self.instruments,
161
+ # period__date_range__overlap=DateRange(self.from_date, self.to_date),
162
+ # ).values(
163
+ # "instrument",
164
+ # "period__period_end_date",
165
+ # *fundamental_field_names,
166
+ # )
167
+ # ).rename(columns={"period__period_end_date": "date"})
168
+ # if not df_append.empty:
169
+ # df_list.append(df_append.set_index(["instrument", "date"]))
170
+ #
171
+ # if forecast_field_names:
172
+ # df_append = pd.DataFrame(
173
+ # Forecast.objects.filter(
174
+ # instrument__in=self.instruments, date__gte=self.from_date, date__lte=self.to_date
175
+ # ).values("instrument", "date", *forecast_field_names)
176
+ # )
177
+ # if not df_append.empty:
178
+ # df_list.append(df_append.set_index(["instrument", "date"]))
179
+ #
180
+ # if daily_fundamental_field_names:
181
+ # df_append = pd.DataFrame(
182
+ # DailyFundamental.objects.annotate(free_cash_flow_ttm=F("free_cash_flow"))
183
+ # .filter(instrument__in=self.instruments, date__gte=self.from_date, date__lte=self.to_date)
184
+ # .values("instrument", "date", *daily_fundamental_field_names)
185
+ # )
186
+ # if not df_append.empty:
187
+ # df_list.append(df_append.set_index(["instrument", "date"]))
188
+ #
189
+ # df = pd.concat(df_list, axis=1).sort_index().astype(float)
190
+ #
191
+ # if df.columns.duplicated().any():
192
+ # raise ValueError("You probably have a duplicated field in the field name list")
193
+ #
194
+ # if df.columns.symmetric_difference(
195
+ # instrument_prices_field_names
196
+ # + fundamental_field_names
197
+ # + forecast_field_names
198
+ # + daily_fundamental_field_names
199
+ # ).empty:
200
+ # return df.merge(
201
+ # end_date_df.sort_values(by="period_end_date")
202
+ # .groupby("instrument")
203
+ # .last()
204
+ # .rename(columns={"period_end_date": "end_date"}),
205
+ # left_on="instrument",
206
+ # right_index=True,
207
+ # )
208
+ return pd.DataFrame()
209
+
210
+ def convert_fx(self, df: pd.DataFrame, foreign_fx_field_names: list[str]) -> pd.DataFrame:
211
+ """
212
+ A function to FX
213
+
214
+ Parameters
215
+ ----------
216
+ df = dataframe that has one or plus columns to be converted
217
+ foreign_fx_field_names = column names to be converted
218
+
219
+ Returns
220
+ -------
221
+ A dataframe with one or more columns converted with FX
222
+ """
223
+ df = df.set_index(["instrument", "date"])
224
+ df["currency"] = df.index.get_level_values("instrument").map(self.currency_map)
225
+ currencies = df["currency"].unique()
226
+ df = df.reset_index().set_index(["date", "currency"])
227
+ df_fx_rate = pd.DataFrame(
228
+ CurrencyFXRates.objects.order_by("date", "currency__id")
229
+ .filter(date__range=[self.from_date, self.to_date], currency__in=currencies)
230
+ .values("date", "currency", "value")
231
+ ).set_index(["date", "currency"])
232
+ df = df.join(df_fx_rate).rename(columns={"value": "fx"}).reset_index()
233
+ df = df.set_index(["instrument", "date"]).groupby("instrument").ffill()
234
+ df[foreign_fx_field_names] = df[foreign_fx_field_names].div(df["fx"].astype(float), axis=0)
235
+
236
+ return df.reset_index()
237
+
238
+ @staticmethod
239
+ def clean_data(
240
+ df: pd.DataFrame,
241
+ var_list: list[str] = [],
242
+ drop_negative=True,
243
+ q_low: float = 0.05,
244
+ q_high: float = 0.95,
245
+ z_max: int = 100,
246
+ smooth_range: int = 3,
247
+ ) -> pd.DataFrame:
248
+ """
249
+ A function to clean time-series.
250
+
251
+ 1) Drops negative values (financial ratios are non-meaningful in this case)
252
+ 2) Drops outliers above and below a certain quartile
253
+ 3) Drops values above a certain threshold
254
+
255
+ Parameters
256
+ ----------
257
+ var_list = a list of variables to clean
258
+ drop_negative = boolean to consider only positive numbers
259
+ q_low = lower cut-off quartile
260
+ q_high = upper cut-off quartile
261
+ z_max = upper absolute cutoff
262
+ smooth_range = rolling period for simple moving average smoothing
263
+
264
+ Returns
265
+ -------
266
+ A clean time-series
267
+ """
268
+ df_temp = df.loc[:, (var_list, slice(None))].copy()
269
+ if drop_negative:
270
+ df_temp = df_temp.mask(df_temp < 0)
271
+ df_temp = df_temp.where(
272
+ df_temp.ge(df_temp.quantile(q_low), axis=1) & df_temp.le(df_temp.quantile(q_high), axis=1)
273
+ )
274
+ df_temp.loc[:, var_list] = df_temp[var_list].where(df_temp[var_list] < z_max)
275
+ df_temp.ffill(inplace=True)
276
+ df_temp.loc[:, var_list] = df_temp.loc[:, var_list].rolling(window=smooth_range).mean()
277
+ df.loc[:, (var_list, slice(None))] = df_temp
278
+ return df
279
+
280
+ def get_common_valuation_ratios_df(self, period: PeriodChoices, clean_data: bool = True) -> pd.DataFrame:
281
+ """
282
+ Calculates financial valuation ratios
283
+
284
+ Parameters
285
+ ----------
286
+ period = financial period, i.e., Next Twelve Months, Trailing Twelve Months, Fiscal Year +1 and +2
287
+ clean_data = a boolean to apply a clean_series method
288
+
289
+ Returns
290
+ -------
291
+ A dataframe with common valuation ratios for all instruments of interest
292
+
293
+ """
294
+ df = self.build_df(
295
+ ["net_value", "outstanding_shares"],
296
+ ["revenue", "net_debt", "ebitda", "eps", "free_cash_flow"],
297
+ [
298
+ "revenue_y1",
299
+ "entreprise_value_y1",
300
+ "net_debt_y1",
301
+ "ebitda_y1",
302
+ "eps_y1",
303
+ "free_cash_flow_y1",
304
+ "revenue_y2",
305
+ "entreprise_value_y2",
306
+ "net_debt_y2",
307
+ "ebitda_y2",
308
+ "eps_y2",
309
+ "free_cash_flow_y2",
310
+ ],
311
+ ["eps_ttm", "eps_ftw"],
312
+ )
313
+
314
+ if df.empty:
315
+ return df
316
+
317
+ if clean_data:
318
+ df = df.groupby(level=0).ffill().groupby(level=0).bfill()
319
+
320
+ # Calculate a decimal factor to know how many days an instrument has till its "annual report" publication
321
+ # so Trailing Twelve Month (TTM) and Next(or Forward) Twelve Month (NTM/FTM) interpolated variables
322
+ # may be calculated
323
+ df["factor"] = (365 - (df.end_date - df.index.get_level_values("date")).dt.days) % 365 / 365
324
+ df["free_cash_flow_ntm"] = _interpolate(df["free_cash_flow_y1"], df["free_cash_flow_y2"], df["factor"])
325
+ df["free_cash_flow_ttm"] = _interpolate(df["free_cash_flow"], df["free_cash_flow_y1"], df["factor"])
326
+ df["revenue_ntm"] = _interpolate(df["revenue_y1"], df["revenue_y2"], df["factor"])
327
+ df["revenue_ttm"] = _interpolate(df["revenue"], df["revenue_y1"], df["factor"])
328
+ df["mktcap"] = (df["net_value"] * df["outstanding_shares"] / 1000000000).replace(0, np.inf)
329
+ match period:
330
+ case PeriodChoices.NTM:
331
+ df["revenue"] = df["revenue_ntm"]
332
+ df["eps"] = df["eps_ftw"]
333
+ df["ebitda"] = _interpolate(df["ebitda_y1"], df["ebitda_y2"], df["factor"])
334
+ df["free_cash_flow"] = _interpolate(df["free_cash_flow_y1"], df["free_cash_flow_y2"], df["factor"])
335
+ df["net_debt"] = _interpolate(df["net_debt_y1"], df["net_debt_y2"], df["factor"])
336
+ df["ev"] = (df["net_value"] * df["outstanding_shares"]) + df["net_debt"]
337
+ df["epsg"] = _adapt_growth(df["eps_ttm"], df["eps_ftw"])
338
+ df["fcfg"] = _adapt_growth(df["free_cash_flow_ttm"], df["free_cash_flow_ntm"])
339
+ df["revg"] = _adapt_growth(df["revenue_ttm"], df["revenue_ntm"])
340
+ case PeriodChoices.TTM:
341
+ df["revenue"] = df["revenue_ttm"]
342
+ df["eps"] = df["eps_ttm"]
343
+ df["ebitda"] = _interpolate(df["ebitda"], df["ebitda_y1"], df["factor"])
344
+ df["free_cash_flow"] = _interpolate(df["free_cash_flow"], df["free_cash_flow_y1"], df["factor"])
345
+ df["net_debt"] = _interpolate(df["net_debt"], df["net_debt_y1"], df["factor"])
346
+ df["ev"] = df["net_value"] * df["outstanding_shares"] + df["net_debt"]
347
+ df["epsg"] = _adapt_growth(df["eps_ttm"].shift(250), df["eps_ttm"])
348
+ df["fcfg"] = _adapt_growth(df["free_cash_flow_ttm"].shift(250), df["free_cash_flow_ttm"])
349
+ df["revg"] = _adapt_growth(df["revenue"], df["revenue_ttm"])
350
+ case default: # noqa
351
+ df["ev"] = df["entreprise_value_y1"]
352
+ df["ebitda"] = df["ebitda_y1"]
353
+ df["free_cash_flow"] = df["free_cash_flow_y1"]
354
+ df["epsg"] = _adapt_growth(df["eps"], df["eps_y1"])
355
+ df["eps"] = df["eps_y1"]
356
+ df["fcfg"] = _adapt_growth(df["free_cash_flow"], df["free_cash_flow_y1"])
357
+ df["revg"] = _adapt_growth(df["revenue"], df["revenue_y1"])
358
+ if period == PeriodChoices.FY2:
359
+ df["ev"] = df["entreprise_value_y2"]
360
+ df["eps"] = df["eps_y2"]
361
+ df["ebitda"] = df["ebitda_y2"]
362
+ df["free_cash_flow"] = df["free_cash_flow_y2"]
363
+ df["epsg"] = ((1 + _adapt_growth(df["eps_y1"], df["eps_y2"])) * (1 + df["epsg"])) - 1
364
+ df["fcfg"] = (
365
+ (_adapt_growth(df["free_cash_flow_y1"], df["free_cash_flow_y2"])) * (1 + df["free_cash_flow"])
366
+ ) - 1
367
+ df["revg"] = ((_adapt_growth(df["revenue_y1"], df["revenue_y2"])) * (1 + df["revg"])) - 1
368
+
369
+ df = df.interpolate(method="linear")
370
+
371
+ # After getting the dataframe, calculate the ratios, then optionally clean the final series
372
+ df["pe"] = df["net_value"] / df["eps"]
373
+ df["peg"] = df["net_value"] / df["eps"] / (100 * df["epsg"])
374
+ df["ps"] = df["net_value"] / (df["revenue"] / df["outstanding_shares"])
375
+ df["pfcf"] = df["net_value"] / df["free_cash_flow"]
376
+ df["evebitda"] = df["ev"] / df["ebitda"]
377
+
378
+ if clean_data:
379
+ df = df.unstack("instrument")
380
+ df = self.clean_data(df=df, var_list=["pe", "pfcf", "evebitda"])
381
+ df = self.clean_data(df=df, var_list=["ps"], z_max=40)
382
+ df = self.clean_data(df=df, var_list=["peg"], z_max=15, smooth_range=10).replace(0, np.nan)
383
+ df = self.clean_data(df=df, var_list=["ev", "epsg", "revg", "fcfg"], drop_negative=False)
384
+ df = df.stack("instrument")
385
+
386
+ df = df.reset_index()
387
+ df = pd.concat([df[df.instrument == self.instruments[0].id], df[df.instrument != self.instruments[0].id]])
388
+ df["instrument_title"] = df["instrument"].map(self.instruments_repr_map)
389
+
390
+ df = self.convert_fx(df, ["ev", "mktcap"]).replace([np.inf, -np.inf, np.nan], None)
391
+
392
+ return df[
393
+ [
394
+ "instrument",
395
+ "instrument_title",
396
+ "date",
397
+ "pe",
398
+ "peg",
399
+ "ps",
400
+ "pfcf",
401
+ "evebitda",
402
+ "ev",
403
+ "mktcap",
404
+ "epsg",
405
+ "revg",
406
+ "fcfg",
407
+ ]
408
+ ]
409
+
410
+ def get_earnings_df(self, period: PeriodChoices, clean_data: bool = True) -> pd.DataFrame:
411
+ """
412
+ Calculates earnings anylysis
413
+
414
+ Parameters
415
+ ----------
416
+ period = financial period, i.e., Next Twelve Months, Trailing Twelve Months, Fiscal Year +1 and +2
417
+ clean_data = a boolean to apply a clean_series method
418
+
419
+ Returns
420
+ -------
421
+ A dataframe with earnings analysis for all instruments of interest
422
+
423
+ """
424
+ df = self.build_df(
425
+ [],
426
+ [],
427
+ ["eps_y1", "eps_y2"],
428
+ ["eps_ttm", "eps_ftw"],
429
+ )
430
+
431
+ if df.empty:
432
+ return df
433
+
434
+ if clean_data:
435
+ df = df.groupby(level=0).ffill()
436
+
437
+ match period:
438
+ case PeriodChoices.FY1:
439
+ df["eps"] = df["eps_y1"]
440
+ case PeriodChoices.FY2:
441
+ df["eps"] = df["eps_y2"]
442
+ case PeriodChoices.TTM:
443
+ df["eps"] = df["eps_ttm"]
444
+ case default: # noqa
445
+ df["eps"] = df["eps_ftw"]
446
+
447
+ df = df.interpolate(method="linear")
448
+
449
+ if clean_data:
450
+ df = df.unstack("instrument")
451
+ df = self.clean_data(
452
+ df=df, var_list=["eps"], drop_negative=False, q_low=0.01, q_high=0.99, z_max=200, smooth_range=4
453
+ )
454
+ df = df.stack("instrument")
455
+
456
+ df = df.reset_index()
457
+ df = pd.concat([df[df.instrument == self.instruments[0].id], df[df.instrument != self.instruments[0].id]])
458
+ df["instrument_title"] = df["instrument"].map(self.instruments_repr_map)
459
+
460
+ df = self.convert_fx(df, ["eps"]).replace([np.inf, -np.inf, np.nan], None)
461
+
462
+ return df[
463
+ [
464
+ "instrument",
465
+ "instrument_title",
466
+ "date",
467
+ "eps",
468
+ ]
469
+ ]