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,100 @@
1
+ SELECT
2
+ 'qa-ds2-security' AS 'source',
3
+ security.DsSecCode AS 'source_id',
4
+ security.IsMajorSec AS 'is_primary',
5
+ company.DsCmpyCode AS 'parent_id',
6
+ coalesce(cmp_ref.PrimaryName, company.DsCmpyName) AS 'name',
7
+ company.CmpyCtryCode AS 'country_id',
8
+ exchange_qt.ISOCurrCode AS 'currency_id',
9
+ country_qt.TypeCode AS 'instrument_type_id',
10
+ rkd_instrument.ISIN AS 'isin',
11
+ rkd_instrument.Ticker AS 'ticker',
12
+ rkd_instrument.RIC AS 'refinitiv_identifier_code',
13
+ country_qt.DsMnem AS 'refinitiv_mnemonic_code',
14
+ rkd_instrument.Cusip AS 'cusip',
15
+ rkd_instrument.Sedol AS 'sedol',
16
+ filing.TxtInfo AS 'description',
17
+ rkd_cmp_det.Employees AS 'employees',
18
+ (select top 1 concat('00', phone.CtryPh, phone.City, phone.PhoneNo) from RKDFndCmpPhone AS phone where phone.Code = rkd_instrument.Code AND PhTypeCode = 1) AS 'phone',
19
+ (select top 1 web.URL from RKDFndCmpWebLink AS web where web.Code = rkd_instrument.Code AND web.URLTypeCode = 1) AS 'primary_url',
20
+ (select string_agg(web.URL, ',') from RKDFndCmpWebLink AS web where web.Code = rkd_instrument.Code AND web.URLTypeCode <> 1) AS 'additional_urls',
21
+ concat(rkd_cmp_det.StAdd1, ', ', rkd_cmp_det.Post, ', ', rkd_cmp_det.City) AS 'headquarter_address',
22
+ convert(Date, COALESCE(rkd_cmp_det.PublicSince, (SELECT MIN(MarketDate) FROM DS2PrimQtPrc WHERE InfoCode = exchange_qt.InfoCode))) AS 'inception_date',
23
+ convert(Date, security.DelistDate) AS 'delisted_date',
24
+ convert(Date, cmp_ref.LatestFinAnnDt) AS 'last_annual_report',
25
+ convert(Date, cmp_ref.LatestFinIntmDt) AS 'last_interim_report',
26
+ -- MarketData DL
27
+ exchange_qt.InfoCode AS 'quote_code',
28
+ exchange_qt.ExchIntCode AS 'exchange_code',
29
+ -- Fundamental DL
30
+ rkd_instrument.Code AS 'rkd_code',
31
+ -- Forecast DL
32
+ ibes_mapping.EstPermID AS 'ibes_code'
33
+
34
+ FROM DS2Security AS security
35
+
36
+ LEFT JOIN DS2Company AS company
37
+ ON security.DsCmpyCode = company.DsCmpyCode
38
+
39
+ LEFT JOIN DS2ExchQtInfo AS exchange_qt
40
+ ON exchange_qt.InfoCode = security.PrimQtInfoCode
41
+ AND exchange_qt.IsPrimExchQt = 'Y'
42
+
43
+ LEFT JOIN DS2CtryQtInfo AS country_qt
44
+ ON exchange_qt.InfoCode = country_qt.InfoCode
45
+
46
+ LEFT JOIN vw_SecurityMappingX AS mappingX
47
+ ON mappingX.vencode = security.PrimQtInfoCode
48
+ AND mappingX.ventype = 33
49
+ AND mappingX.rank = 1
50
+ AND mappingX.StartDate = (
51
+ SELECT MAX(I.StartDate)
52
+ FROM vw_SecurityMappingX AS I
53
+ WHERE I.typ = mappingX.typ AND I.vencode = mappingX.vencode AND I.ventype = mappingX.ventype
54
+ )
55
+
56
+ LEFT JOIN vw_SecurityMappingX AS mappingRKD
57
+ ON mappingX.seccode = mappingRKD.seccode
58
+ AND mappingX.typ = mappingRKD.typ
59
+ AND mappingRKD.ventype = 26
60
+ AND mappingRKD.rank = 1
61
+ AND mappingRKD.StartDate = (
62
+ SELECT MAX(I.StartDate)
63
+ FROM vw_SecurityMappingX AS I
64
+ WHERE I.typ = mappingRKD.typ AND I.vencode = mappingRKD.vencode AND I.ventype = mappingRKD.ventype
65
+ )
66
+
67
+ LEFT JOIN RKDFndCmpRefIssue AS rkd_instrument
68
+ ON rkd_instrument.IssueCode = mappingRKD.vencode
69
+
70
+ LEFT JOIN RKDFndCmpDet AS rkd_cmp_det
71
+ ON rkd_cmp_det.Code = rkd_instrument.Code
72
+
73
+ LEFT JOIN RKDFndCmpRef AS cmp_ref
74
+ ON cmp_ref.Code = rkd_instrument.Code
75
+
76
+ LEFT JOIN RKDFndCmpFiling AS filing
77
+ ON filing.Code = rkd_instrument.Code
78
+ AND filing.TxtInfoTypeCode = 2
79
+
80
+ LEFT JOIN vw_IBES2Mapping AS ibes_mapping
81
+ ON ibes_mapping.SecCode = mappingX.seccode
82
+ AND ibes_mapping.typ = mappingX.typ
83
+ AND ibes_mapping.Exchange = (
84
+ CASE
85
+ WHEN ibes_mapping.typ = 6 THEN 0
86
+ WHEN ibes_mapping.typ = 1 THEN 1
87
+ END
88
+ )
89
+
90
+ {% if source_id %}
91
+ where security.DsSecCode = {{ source_id }}
92
+ {% endif %}
93
+ -- where company.CmpyCtryCode = 'IS'
94
+
95
+ ORDER BY company.DsCmpyCode
96
+
97
+ {% if (offset == 0 or offset) and batch %}
98
+ offset {{ offset|sqlsafe }} rows
99
+ fetch next {{ batch|sqlsafe }} rows only
100
+ {% endif %}
@@ -0,0 +1,98 @@
1
+ SELECT
2
+ 'qa-ds2-quote' AS 'source',
3
+ CONCAT(exchange_qt.InfoCode, '-', exchange_qt.ExchIntCode) AS 'source_id',
4
+ security.DsSecCode AS 'parent_id',
5
+ case
6
+ when exchange_qt.IsPrimExchQt = 'Y' and country_qt.IsPrimQt = 1 and security.DelistDate is null then 'Y'
7
+ else 'N'
8
+ end as is_primary,
9
+ coalesce(cmp_ref.PrimaryName, exchange_qt.DsQtName) AS 'name',
10
+ country_qt.Region AS 'country_id',
11
+ exchange_qt.ISOCurrCode AS 'currency_id',
12
+ 'quote' AS 'instrument_type_id',
13
+ rkd_instrument.ISIN AS 'isin',
14
+ rkd_instrument.Ticker AS 'ticker',
15
+ rkd_instrument.RIC AS 'refinitiv_identifier_code',
16
+ country_qt.DsMnem AS 'refinitiv_mnemonic_code',
17
+ rkd_instrument.Cusip AS 'cusip',
18
+ rkd_instrument.Sedol AS 'sedol',
19
+ filing.TxtInfo AS 'description',
20
+ rkd_cmp_det.Employees AS 'employees',
21
+ (select top 1 concat('00', phone.CtryPh, phone.City, phone.PhoneNo) from RKDFndCmpPhone AS phone where phone.Code = rkd_instrument.Code AND PhTypeCode = 1) AS 'phone',
22
+ (select top 1 web.URL from RKDFndCmpWebLink AS web where web.Code = rkd_instrument.Code AND web.URLTypeCode = 1) AS 'primary_url',
23
+ (select string_agg(web.URL, ',') from RKDFndCmpWebLink AS web where web.Code = rkd_instrument.Code AND web.URLTypeCode <> 1) AS 'additional_urls',
24
+ concat(rkd_cmp_det.StAdd1, ', ', rkd_cmp_det.Post, ', ', rkd_cmp_det.City) AS 'headquarter_address',
25
+ convert(Date, COALESCE(rkd_cmp_det.PublicSince, (SELECT MIN(MarketDate) FROM DS2PrimQtPrc WHERE InfoCode = exchange_qt.InfoCode))) AS 'inception_date',
26
+ convert(Date, security.DelistDate) AS 'delisted_date',
27
+ convert(Date, cmp_ref.LatestFinAnnDt) AS 'last_annual_report',
28
+ convert(Date, cmp_ref.LatestFinIntmDt) AS 'last_interim_report',
29
+ -- MarketData DL
30
+ exchange_qt.InfoCode AS 'quote_code',
31
+ exchange_qt.ExchIntCode AS 'exchange_code',
32
+ -- Fundamental DL
33
+ rkd_instrument.Code AS 'rkd_code',
34
+ -- Forecast DL
35
+ ibes_mapping.EstPermID AS 'ibes_code'
36
+
37
+ FROM DS2ExchQtInfo AS exchange_qt
38
+
39
+ LEFT JOIN DS2CtryQtInfo AS country_qt
40
+ ON exchange_qt.InfoCode = country_qt.InfoCode
41
+
42
+ LEFT JOIN DS2Security AS security
43
+ ON country_qt.DsSecCode = security.DsSecCode
44
+
45
+ LEFT JOIN vw_SecurityMappingX AS mappingX
46
+ ON mappingX.vencode = security.PrimQtInfoCode
47
+ AND mappingX.ventype = 33
48
+ AND mappingX.rank = 1
49
+ AND mappingX.StartDate = (
50
+ SELECT MAX(I.StartDate)
51
+ FROM vw_SecurityMappingX AS I
52
+ WHERE I.typ = mappingX.typ AND I.vencode = mappingX.vencode AND I.ventype = mappingX.ventype
53
+ )
54
+
55
+ LEFT JOIN vw_SecurityMappingX AS mappingRKD
56
+ ON mappingX.seccode = mappingRKD.seccode
57
+ AND mappingX.typ = mappingRKD.typ
58
+ AND mappingRKD.ventype = 26
59
+ AND mappingRKD.rank = 1
60
+ AND mappingRKD.StartDate = (
61
+ SELECT MAX(I.StartDate)
62
+ FROM vw_SecurityMappingX AS I
63
+ WHERE I.typ = mappingRKD.typ AND I.vencode = mappingRKD.vencode AND I.ventype = mappingRKD.ventype
64
+ )
65
+
66
+ LEFT JOIN RKDFndCmpRefIssue AS rkd_instrument
67
+ ON rkd_instrument.IssueCode = mappingRKD.vencode
68
+
69
+ LEFT JOIN RKDFndCmpDet AS rkd_cmp_det
70
+ ON rkd_cmp_det.Code = rkd_instrument.Code
71
+
72
+ LEFT JOIN RKDFndCmpRef AS cmp_ref
73
+ ON cmp_ref.Code = rkd_instrument.Code
74
+
75
+ LEFT JOIN RKDFndCmpFiling AS filing
76
+ ON filing.Code = rkd_instrument.Code
77
+ AND filing.TxtInfoTypeCode = 2
78
+
79
+ LEFT JOIN vw_IBES2Mapping AS ibes_mapping
80
+ ON ibes_mapping.SecCode = mappingX.seccode
81
+ AND ibes_mapping.typ = mappingX.typ
82
+ AND ibes_mapping.Exchange = (
83
+ CASE
84
+ WHEN ibes_mapping.typ = 6 THEN 0
85
+ WHEN ibes_mapping.typ = 1 THEN 1
86
+ END
87
+ )
88
+
89
+ {% if source_id %}
90
+ where CONCAT(exchange_qt.InfoCode, '-', exchange_qt.ExchIntCode) = {{ source_id }}
91
+ {% endif %}
92
+
93
+ ORDER BY exchange_qt.InfoCode, exchange_qt.ExchIntCode
94
+
95
+ {% if (offset == 0 or offset) and batch %}
96
+ offset {{ offset|sqlsafe }} rows
97
+ fetch next {{ batch|sqlsafe }} rows only
98
+ {% endif %}
@@ -0,0 +1,70 @@
1
+ import pytz
2
+ from django.conf import settings
3
+ from django.db import connections
4
+ from django.db.models import Max
5
+ from wbcore.contrib.dataloader.utils import dictfetchall, dictfetchone
6
+ from wbcore.contrib.geography.models import Geography
7
+ from wbcore.utils.cache import mapping
8
+ from wbfdm.models import Exchange
9
+ from wbfdm.sync.abstract import Sync
10
+
11
+
12
+ class QAExchangeSync(Sync[Exchange]):
13
+ SOURCE = "qa-ds2-exchange"
14
+
15
+ def update(self):
16
+ sql = "SELECT ExchIntCode AS source_id, ExchName AS name, ExchCtryCode AS country_id, ExchMnem AS refinitiv_mnemonic FROM DS2Exchange"
17
+ Exchange.objects.bulk_create(
18
+ map(
19
+ lambda data: Exchange(
20
+ source=self.SOURCE,
21
+ source_id=data["source_id"],
22
+ name=data["name"],
23
+ country_id=mapping(Geography.countries, "code_2").get(data["country_id"]),
24
+ refinitiv_mnemonic=data["refinitiv_mnemonic"],
25
+ ),
26
+ dictfetchall(connections["qa"].cursor().execute(sql)),
27
+ ),
28
+ update_conflicts=True,
29
+ update_fields=["name", "country", "refinitiv_mnemonic"],
30
+ unique_fields=["source", "source_id"],
31
+ )
32
+
33
+ def update_or_create_item(self, external_id: int) -> Exchange:
34
+ defaults = self.get_item(external_id)
35
+ defaults["country_id"] = mapping(Geography.countries, "code_2").get(defaults["country_id"])
36
+ exchange, _ = Exchange.objects.update_or_create(
37
+ source=self.SOURCE,
38
+ source_id=external_id,
39
+ defaults=defaults,
40
+ )
41
+ return exchange
42
+
43
+ def update_item(self, item: Exchange) -> Exchange:
44
+ return self.update_or_create_item(item.source_id)
45
+
46
+ def get_item(self, external_id: int) -> dict:
47
+ sql = "SELECT ExchName AS name, ExchCtryCode AS country_id, ExchMnem AS refinitiv_mnemonic FROM DS2Exchange WHERE ExchIntCode = %s"
48
+ return dictfetchone(connections["qa"].cursor().execute(sql, (external_id,)))
49
+
50
+ def trigger_partial_update(self):
51
+ max_last_updated = (
52
+ Exchange.objects.filter(source=self.SOURCE)
53
+ .aggregate(max_last_updated=Max("last_updated"))
54
+ .get("max_last_updated")
55
+ )
56
+ if max_last_updated is None:
57
+ self.update()
58
+ else:
59
+ with connections["qa"].cursor() as cursor:
60
+ cursor.execute(
61
+ "SELECT MAX(last_user_update) FROM sys.dm_db_index_usage_stats WHERE OBJECT_NAME(object_id) = 'DS2Exchange_changes'"
62
+ )
63
+ max_last_updated_qa = (
64
+ pytz.timezone(settings.TIME_ZONE).localize(result[0]) if (result := cursor.fetchone()) else None
65
+ )
66
+ if max_last_updated_qa and max_last_updated_qa > max_last_updated:
67
+ for _, exchange_id in cursor.execute(
68
+ "SELECT UpdateFlag_, ExchIntCode FROM DS2Exchange_changes"
69
+ ).fetchall():
70
+ self.update_or_create_item(exchange_id)
@@ -0,0 +1,94 @@
1
+ from django.db import connections
2
+ from wbfdm.contrib.qa.sync.utils import (
3
+ get_item,
4
+ trigger_partial_update,
5
+ update_instruments,
6
+ update_or_create_item,
7
+ )
8
+ from wbfdm.models import Instrument
9
+ from wbfdm.sync.abstract import Sync
10
+
11
+
12
+ class QACompanySync(Sync[Instrument]):
13
+ SOURCE = "qa-ds2-company"
14
+
15
+ def update(self, **kwargs):
16
+ update_instruments("qa/sql/companies.sql", **kwargs)
17
+
18
+ def update_or_create_item(self, external_id: int) -> Instrument:
19
+ return update_or_create_item(external_id, self.get_item, self.SOURCE)
20
+
21
+ def update_item(self, item: Instrument) -> Instrument:
22
+ return self.update_or_create_item(item.source_id)
23
+
24
+ def get_item(self, external_id: int) -> dict:
25
+ return get_item(external_id, "qa/sql/companies.sql")
26
+
27
+ def trigger_partial_update(self):
28
+ trigger_partial_update(
29
+ Instrument.objects.filter(source=self.SOURCE),
30
+ "last_update",
31
+ "DsCmpyCode",
32
+ "DS2Company_changes",
33
+ self.update,
34
+ self.update_or_create_item,
35
+ )
36
+
37
+
38
+ class QAInstrumentSync(Sync[Instrument]):
39
+ SOURCE = "qa-ds2-security"
40
+
41
+ def update(self, **kwargs):
42
+ update_instruments("qa/sql/instruments.sql", parent_source="qa-ds2-company", **kwargs)
43
+
44
+ def update_or_create_item(self, external_id: int) -> Instrument:
45
+ return update_or_create_item(external_id, self.get_item, self.SOURCE, "qa-ds2-company")
46
+
47
+ def update_item(self, item: Instrument) -> Instrument:
48
+ return self.update_or_create_item(item.source_id)
49
+
50
+ def get_item(self, external_id: int) -> dict:
51
+ return get_item(external_id, "qa/sql/instruments.sql")
52
+
53
+ def trigger_partial_update(self):
54
+ trigger_partial_update(
55
+ Instrument.objects.filter(source=self.SOURCE),
56
+ "last_update",
57
+ "DsSecCode",
58
+ "DS2Security_changes",
59
+ self.update,
60
+ self.update_or_create_item,
61
+ )
62
+
63
+
64
+ class QAQuoteSync(Sync[Instrument]):
65
+ SOURCE = "qa-ds2-quote"
66
+
67
+ def update(self, **kwargs):
68
+ count = connections["qa"].cursor().execute("SELECT COUNT(*) FROM DS2ExchQtInfo").fetchone()[0]
69
+ for offset in range(0, count, 100_000):
70
+ update_instruments(
71
+ "qa/sql/quotes.sql",
72
+ parent_source="qa-ds2-security",
73
+ context={"offset": offset, "batch": 100_000},
74
+ **kwargs,
75
+ )
76
+
77
+ def update_or_create_item(self, external_id: int) -> Instrument:
78
+ return update_or_create_item(external_id, self.get_item, self.SOURCE, "qa-ds2-security")
79
+
80
+ def update_item(self, item: Instrument) -> Instrument:
81
+ return self.update_or_create_item(item.source_id)
82
+
83
+ def get_item(self, external_id: int) -> dict:
84
+ return get_item(external_id, "qa/sql/quotes.sql")
85
+
86
+ def trigger_partial_update(self):
87
+ trigger_partial_update(
88
+ Instrument.objects.filter(source=self.SOURCE),
89
+ "last_update",
90
+ "CONCAT(InfoCode, '-', ExchIntCode)",
91
+ "DS2ExchQtInfo_changes",
92
+ self.update,
93
+ self.update_or_create_item,
94
+ )
@@ -0,0 +1,241 @@
1
+ from contextlib import suppress
2
+ from typing import Callable
3
+
4
+ import pytz
5
+ from django.conf import settings
6
+ from django.db import connections
7
+ from django.db.models import Max, QuerySet
8
+ from django.template.loader import get_template
9
+ from jinjasql import JinjaSql # type: ignore
10
+ from tqdm import tqdm
11
+ from wbcore.contrib.currency.models import Currency
12
+ from wbcore.contrib.dataloader.utils import dictfetchall, dictfetchone
13
+ from wbcore.contrib.geography.models import Geography
14
+ from wbcore.utils.cache import mapping
15
+ from wbfdm.models.exchanges.exchanges import Exchange
16
+ from wbfdm.models.instruments.instruments import Instrument, InstrumentType
17
+
18
+ BATCH_SIZE: int = 10000
19
+ instrument_type_map = {
20
+ "ADR": "american_depository_receipt",
21
+ "CF": "close_ended_fund",
22
+ "EQ": "equity",
23
+ "ET": "exchange_traded_fund",
24
+ "ETC": "exchange_traded_commodity",
25
+ "ETN": "exchange_traded_note",
26
+ "EWT": "warrant",
27
+ "GDR": "global_depository_receipt",
28
+ "GNSH": "genussschein",
29
+ "INVT": "investment_trust",
30
+ "NVDR": "non_voting_depository_receipt",
31
+ "PREF": "preference_share",
32
+ "UT": "abc",
33
+ }
34
+
35
+
36
+ def get_dataloader_parameters(
37
+ quote_code: str | None = None,
38
+ exchange_code: str | None = None,
39
+ rkd_code: str | None = None,
40
+ ibes_code: str | None = None,
41
+ ) -> dict:
42
+ dataloader_parameters = dict()
43
+ if quote_code:
44
+ dataloader_parameters["adjustments"] = {
45
+ "path": "wbfdm.contrib.qa.dataloaders.adjustments.DatastreamAdjustmentsDataloader",
46
+ "parameters": quote_code,
47
+ }
48
+ dataloader_parameters["corporate_actions"] = {
49
+ "path": "wbfdm.contrib.qa.dataloaders.corporate_actions.DatastreamCorporateActionsDataloader",
50
+ "parameters": quote_code,
51
+ }
52
+ if exchange_code:
53
+ dataloader_parameters["market_data"] = {
54
+ "path": "wbfdm.contrib.qa.dataloaders.market_data.DatastreamMarketDataDataloader",
55
+ "parameters": [
56
+ quote_code,
57
+ exchange_code,
58
+ ],
59
+ }
60
+
61
+ if rkd_code:
62
+ dataloader_parameters["officers"] = {
63
+ "path": "wbfdm.contrib.qa.dataloaders.officers.RKDOfficersDataloader",
64
+ "parameters": rkd_code,
65
+ }
66
+ dataloader_parameters["statements"] = {
67
+ "path": "wbfdm.contrib.qa.dataloaders.statements.RKDStatementsDataloader",
68
+ "parameters": rkd_code,
69
+ }
70
+
71
+ if ibes_code:
72
+ dataloader_parameters["financials"] = {
73
+ "path": "wbfdm.contrib.qa.dataloaders.financials.IBESFinancialsDataloader",
74
+ "parameters": ibes_code,
75
+ }
76
+ dataloader_parameters["reporting_dates"] = {
77
+ "path": "wbfdm.contrib.qa.dataloaders.reporting_dates.IbesReportingDateDataloader",
78
+ "parameters": ibes_code,
79
+ }
80
+
81
+ return dataloader_parameters
82
+
83
+
84
+ def convert_data(data, parent_source: str | None = None) -> dict:
85
+ if len(data.keys()) == 0:
86
+ return data
87
+ data["country_id"] = mapping(Geography.countries, "code_2").get(data["country_id"])
88
+ data["currency_id"] = mapping(Currency.objects).get(data["currency_id"])
89
+ if instrument_type_id := data.pop("instrument_type_id"):
90
+ try:
91
+ data["instrument_type_id"] = mapping(InstrumentType.objects)[
92
+ instrument_type_map.get(instrument_type_id, instrument_type_id)
93
+ ]
94
+ except KeyError:
95
+ data["instrument_type_id"] = InstrumentType.objects.get_or_create(
96
+ key=instrument_type_id.lower(),
97
+ defaults={"name": instrument_type_id.title(), "short_name": instrument_type_id.title()},
98
+ )[0].pk
99
+ data["exchange_id"] = mapping(Exchange.objects, "source_id", source="qa-ds2-exchange").get(
100
+ str(data["exchange_code"])
101
+ )
102
+ data["dl_parameters"] = get_dataloader_parameters(
103
+ data.pop("quote_code"), data.pop("exchange_code"), data.pop("rkd_code"), data.pop("ibes_code")
104
+ )
105
+ data["additional_urls"] = urls.split(",") if (urls := data.get("additional_urls")) else []
106
+ if parent_source:
107
+ data["parent_id"] = mapping(Instrument.objects, "source_id", source=parent_source).get(str(data["parent_id"]))
108
+ if (is_primary := data.get("is_primary")) and isinstance(is_primary, str):
109
+ data["is_primary"] = is_primary.lower().strip() == "y"
110
+ data.pop("phone")
111
+ return data
112
+
113
+
114
+ def get_instrument_from_data(data, parent_source: str | None = None) -> Instrument | None:
115
+ if len(data.keys()) == 0:
116
+ return None
117
+ instrument = Instrument(**convert_data(data, parent_source))
118
+ instrument.pre_save()
119
+ instrument.computed_str = instrument.compute_str()
120
+ if (
121
+ instrument.name and instrument.currency and instrument.instrument_type
122
+ ): # we return an instrument only if it contains the basic name, currency and type
123
+ return instrument
124
+
125
+
126
+ def _bulk_create_instruments_chunk(instruments: list[Instrument], update_unique_identifiers: bool = False):
127
+ update_fields = [
128
+ "name",
129
+ "dl_parameters",
130
+ "description",
131
+ "country",
132
+ "currency",
133
+ "parent",
134
+ "primary_url",
135
+ "additional_urls",
136
+ "headquarter_address",
137
+ "inception_date",
138
+ "delisted_date",
139
+ "exchange",
140
+ "instrument_type",
141
+ "computed_str",
142
+ "search_vector",
143
+ "trigram_search_vector",
144
+ "is_primary",
145
+ ]
146
+ bulk_update_kwargs = {"update_conflicts": True}
147
+ if update_unique_identifiers:
148
+ update_fields.extend(
149
+ [
150
+ "refinitiv_identifier_code",
151
+ "refinitiv_mnemonic_code",
152
+ "isin",
153
+ "sedol",
154
+ "valoren",
155
+ "cusip",
156
+ ]
157
+ )
158
+ bulk_update_kwargs = {"ignore_conflicts": True}
159
+ Instrument.objects.bulk_create(
160
+ instruments, update_fields=update_fields, unique_fields=["source", "source_id"], **bulk_update_kwargs
161
+ )
162
+
163
+
164
+ def update_instruments(sql_name: str, parent_source: str | None = None, context=None, debug: bool = False, **kwargs):
165
+ template = get_template(sql_name, using="jinja").template # type: ignore
166
+ if context is None:
167
+ context = {}
168
+ query, params = JinjaSql(param_style="format").prepare_query(template, context)
169
+ instruments = []
170
+
171
+ gen = dictfetchall(connections["qa"].cursor().execute(query, params))
172
+ if debug:
173
+ gen = tqdm(gen)
174
+ # we update in batch to be sure to not exhaust resources
175
+ for row in gen:
176
+ with suppress(TypeError): # we don't fail if the given data doesn't satisfy the schema
177
+ if instrument := get_instrument_from_data(row, parent_source=parent_source):
178
+ instruments.append(instrument)
179
+
180
+ if len(instruments) >= BATCH_SIZE:
181
+ _bulk_create_instruments_chunk(instruments, **kwargs)
182
+ instruments = []
183
+
184
+ _bulk_create_instruments_chunk(instruments, **kwargs)
185
+
186
+
187
+ def update_or_create_item(
188
+ external_id: int, get_item: Callable, source: str, parent_source: str | None = None
189
+ ) -> Instrument:
190
+ defaults = convert_data(get_item(external_id), parent_source=parent_source)
191
+
192
+ dl_parameters = defaults.pop("dl_parameters", {})
193
+ defaults.pop("source", None)
194
+ defaults.pop("source_id", None)
195
+
196
+ instrument, _ = Instrument.objects.update_or_create(
197
+ source=source,
198
+ source_id=external_id,
199
+ defaults=defaults,
200
+ )
201
+
202
+ for key, value in dl_parameters.items():
203
+ instrument.dl_parameters[key] = value
204
+
205
+ instrument.save()
206
+
207
+ return instrument
208
+
209
+
210
+ def get_item(external_id: int, template_name: str) -> dict:
211
+ template = get_template(template_name, using="jinja").template # type: ignore
212
+ query, params = JinjaSql(param_style="format").prepare_query(template, {"source_id": external_id})
213
+ return dictfetchone(connections["qa"].cursor().execute(query, params))
214
+
215
+
216
+ def trigger_partial_update(
217
+ queryset: QuerySet,
218
+ last_update_field: str,
219
+ id_field: str,
220
+ table_change_name: str,
221
+ update: Callable,
222
+ update_or_create_item: Callable,
223
+ ):
224
+ max_last_updated = queryset.aggregate(max_last_updated=Max(last_update_field)).get("max_last_updated")
225
+
226
+ if max_last_updated is None:
227
+ update()
228
+
229
+ else:
230
+ with connections["qa"].cursor() as cursor:
231
+ cursor.execute(
232
+ f"SELECT MAX(last_user_update) FROM sys.dm_db_index_usage_stats WHERE OBJECT_NAME(object_id) = '{table_change_name}'"
233
+ )
234
+ max_last_updated_qa = (
235
+ pytz.timezone(settings.TIME_ZONE).localize(result[0]) if (result := cursor.fetchone()) else None
236
+ )
237
+ if max_last_updated_qa and max_last_updated_qa > max_last_updated:
238
+ for _, security_id in cursor.execute(
239
+ f"SELECT UpdateFlag_, {id_field} FROM {table_change_name}"
240
+ ).fetchall():
241
+ update_or_create_item(security_id)
File without changes
File without changes