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,81 @@
1
+ from datetime import datetime
2
+ from typing import Iterator
3
+
4
+ from django.conf import settings
5
+ from wbcore.contrib.dataloader.dataloaders import Dataloader
6
+ from wbfdm.dataloaders.protocols import ESGControversyProtocol
7
+ from wbfdm.dataloaders.types import ESGControversyDataDict
8
+
9
+ from ..client import MSCIClient
10
+
11
+ CONTROVERY_ASSESSMENT_MAP = {
12
+ "Minor": "MINOR",
13
+ "Moderate": "MODERATE",
14
+ "Severe": "SEVERE",
15
+ "Very Severe": "VERY_SEVERE",
16
+ }
17
+ CONTROVERSY_STATUS_MAP = {
18
+ "Ongoing": "ONGOING",
19
+ "Partially Concluded": "PARTIALLY_CONCLUDED",
20
+ "Concluded": "CONCLUDED",
21
+ }
22
+ CONTROVERSY_TYPE_MAP = {
23
+ "Structural": "STRUCTURAL",
24
+ "Non-Structural": "NON_STRUCTURAL",
25
+ }
26
+
27
+ CONTROVERSY_FLAG_MAP = {
28
+ "Green": "GREEN",
29
+ "Yellow": "YELLOW",
30
+ "Orange": "ORANGE",
31
+ "Red": "RED",
32
+ "Unknown": "UNKNOWN",
33
+ }
34
+
35
+
36
+ class MSCIESGControversyDataloader(ESGControversyProtocol, Dataloader):
37
+ def esg_controversies(self) -> Iterator[ESGControversyDataDict]:
38
+ msci_client_id = getattr(settings, "MSCI_CLIENT_ID", None)
39
+ msci_client_secret = getattr(settings, "MSCI_CLIENT_SECRET", None)
40
+ if msci_client_id and msci_client_secret:
41
+ client = MSCIClient(msci_client_id, msci_client_secret)
42
+ lookup = {k: v for k, v in self.entities.values_list("dl_parameters__esg_controversies__parameters", "id")}
43
+ factors = [
44
+ "CONTROVERSY_CASE_ID",
45
+ "CONTROVERSY_CASE_HEADLINE",
46
+ "CONTROVERSY_LAST_REVIEWED",
47
+ "CONTROVERSY_CASE_NARRATIVE",
48
+ "CONTROVERSY_CASE_SOURCE",
49
+ "CONTROVERSY_CASE_STATUS",
50
+ "CONTROVERSY_CASE_TYPE",
51
+ "CONTROVERSY_CASE_ASSESSMENT",
52
+ "CONTROVERSY_CASE_COMPANY_RESPONSE",
53
+ "CONTROVERSY_CASE_DATE_INITIATED",
54
+ "CONTROVERSY_CASE_FLAG",
55
+ ]
56
+ for row in client.controversies(list(lookup.keys()), factors):
57
+ isin = row.get("CLIENT_IDENTIFIER", "")
58
+ instrument_id = lookup.get(isin, "")
59
+
60
+ headline = row.get("CONTROVERSY_CASE_HEADLINE", "")
61
+ narrative = row.get("CONTROVERSY_CASE_NARRATIVE", "")
62
+ reviewed = row.get("CONTROVERSY_LAST_REVIEWED", None)
63
+ initiated = row.get("CONTROVERSY_CASE_DATE_INITIATED", None)
64
+ controversy_id = row.get("CONTROVERSY_CASE_ID", f"{isin}-{hash(headline)}")
65
+ if controversy_id and narrative and headline and isin and instrument_id:
66
+ yield ESGControversyDataDict(
67
+ id=controversy_id,
68
+ instrument_id=instrument_id,
69
+ headline=headline,
70
+ narrative=narrative,
71
+ source=row.get("CONTROVERSY_CASE_SOURCE", ""),
72
+ review=datetime.strptime(reviewed, "%Y%m%d").date() if reviewed else None,
73
+ initiated=datetime.strptime(initiated, "%Y%m%d").date() if initiated else None,
74
+ response=row.get("CONTROVERSY_CASE_COMPANY_RESPONSE", ""),
75
+ assessment=CONTROVERY_ASSESSMENT_MAP.get(
76
+ row.get("CONTROVERSY_CASE_ASSESSMENT", "Minor"), None
77
+ ),
78
+ status=CONTROVERSY_STATUS_MAP.get(row.get("CONTROVERSY_CASE_STATUS", "Ongoing"), None),
79
+ type=CONTROVERSY_TYPE_MAP.get(row.get("CONTROVERSY_CASE_TYPE", "Structural"), None),
80
+ flag=CONTROVERSY_FLAG_MAP.get(row.get("CONTROVERSY_CASE_FLAG", "Green"), None),
81
+ )
@@ -0,0 +1,58 @@
1
+ from django.db.models import Q, QuerySet
2
+ from wbfdm.models import Instrument
3
+ from wbfdm.sync.abstract import Sync
4
+
5
+
6
+ class MsciInstrumentSync(Sync[Instrument]):
7
+ ESG_PATH = "wbfdm.contrib.msci.dataloaders.esg.MSCIESGDataloader"
8
+ ESG_CONTROVERSY_PATH = "wbfdm.contrib.msci.dataloaders.esg_controversies.MSCIESGControversyDataloader"
9
+
10
+ def get_updatable_instruments(self) -> QuerySet[Instrument]:
11
+ return Instrument.objects.filter(
12
+ Q(isin__isnull=False)
13
+ & (Q(dl_parameters__esg__isnull=True) | Q(dl_parameters__esg_controversies__isnull=True))
14
+ )
15
+
16
+ def update(self):
17
+ instruments = []
18
+
19
+ for instrument in self.get_updatable_instruments():
20
+ self._update_dl_parameters(instrument)
21
+ instruments.append(instrument)
22
+
23
+ if len(instruments) % 10000 == 0:
24
+ Instrument.objects.bulk_update(instruments, ["dl_parameters"])
25
+ instruments = []
26
+ Instrument.objects.bulk_update(instruments, ["dl_parameters"])
27
+
28
+ def update_or_create_item(self, external_id: int) -> Instrument:
29
+ raise NotImplementedError(f"The {__class__} is not allowed to create instruments")
30
+
31
+ def get_item(self, external_id: int) -> dict:
32
+ raise NotImplementedError(f"The {__class__} is not allowed to instantiate from remote resources")
33
+
34
+ def _update_dl_parameters(self, instrument) -> Instrument:
35
+ if isin := instrument.isin:
36
+ instrument.dl_parameters["esg"] = {
37
+ "path": self.ESG_PATH,
38
+ "parameters": isin,
39
+ }
40
+ instrument.dl_parameters["esg_controversies"] = {
41
+ "path": self.ESG_CONTROVERSY_PATH,
42
+ "parameters": isin,
43
+ }
44
+ return instrument
45
+
46
+ def update_item(self, instrument: Instrument) -> Instrument:
47
+ self._update_dl_parameters(instrument)
48
+ instrument.save()
49
+ return instrument
50
+
51
+ def trigger_partial_update(self):
52
+ instruments_to_update = self.get_updatable_instruments()
53
+ instruments_to_update_count = instruments_to_update.count()
54
+ if instruments_to_update_count > 30:
55
+ self.update()
56
+ else:
57
+ for instrument in instruments_to_update:
58
+ self.update_item(instrument)
File without changes
@@ -0,0 +1 @@
1
+ from wbcore.tests.conftest import * # type: ignore # isort:skip
@@ -0,0 +1,70 @@
1
+ import time
2
+ from datetime import datetime
3
+
4
+ import pytest
5
+ from django.core.cache import cache as cache_layer
6
+ from django.utils import timezone
7
+ from faker import Faker
8
+ from jwt import encode
9
+ from requests.exceptions import ConnectionError
10
+
11
+ from ..client import MSCIClient, is_expired
12
+
13
+ fake = Faker()
14
+
15
+
16
+ class TestMSCIClient:
17
+ @pytest.fixture()
18
+ def jwt(self, expiry_date: datetime):
19
+ payload = {"exp": int(expiry_date.timestamp()), "iat": int(timezone.now().timestamp())}
20
+ return encode(payload, "secret", algorithm="HS256")
21
+
22
+ @pytest.fixture()
23
+ def msci_client(self):
24
+ return MSCIClient("username", "password")
25
+
26
+ @pytest.mark.parametrize("expiry_date", [fake.past_datetime()])
27
+ def test_is_expired_true(self, expiry_date, jwt):
28
+ assert is_expired(jwt)
29
+
30
+ @pytest.mark.parametrize("expiry_date", [fake.future_datetime()])
31
+ def test_is_expired_false(self, expiry_date, jwt):
32
+ assert not is_expired(jwt)
33
+
34
+ def test_oauth_token_not_in_cache(self, requests_mock, msci_client):
35
+ access_token = fake.word()
36
+ expiry = fake.pyint(min_value=60)
37
+ requests_mock.post(MSCIClient.AUTH_SERVER_ULR, json={"access_token": access_token, "expires_in": expiry})
38
+ t0 = time.time()
39
+ res = msci_client.oauth_token
40
+ assert res == access_token
41
+ assert cache_layer.get("msci_oauth_token") == access_token
42
+ assert int(cache_layer._expire_info.get(":1:msci_oauth_token")) == int(t0 + expiry)
43
+
44
+ @pytest.mark.parametrize("expiry_date", [fake.future_datetime()])
45
+ def test_oauth_token_in_cache_and_valid(self, requests_mock, msci_client, expiry_date, jwt):
46
+ requests_mock.post(MSCIClient.AUTH_SERVER_ULR, json={"access_token": fake.word()})
47
+ cache_layer.set("msci_oauth_token", jwt)
48
+ assert msci_client.oauth_token == jwt
49
+
50
+ @pytest.mark.parametrize("expiry_date", [fake.past_datetime()])
51
+ def test_oauth_token_in_cache_but_expired(self, requests_mock, msci_client, expiry_date, jwt):
52
+ requests_mock.post(MSCIClient.AUTH_SERVER_ULR, json={"access_token": fake.word()})
53
+ cache_layer.set("msci_oauth_token", jwt)
54
+
55
+ access_token = fake.word()
56
+ requests_mock.post(
57
+ MSCIClient.AUTH_SERVER_ULR, json={"access_token": access_token, "expires_in": fake.pyint(min_value=60)}
58
+ )
59
+ assert msci_client.oauth_token == access_token
60
+
61
+ def test_oauth_token_connection_error(self, requests_mock, msci_client):
62
+ cache_layer.clear()
63
+ # keyerror
64
+ requests_mock.post(MSCIClient.AUTH_SERVER_ULR, json={"access_token_typo": fake.word()})
65
+ with pytest.raises(ConnectionError):
66
+ t = msci_client.oauth_token # noqa
67
+
68
+ requests_mock.post(MSCIClient.AUTH_SERVER_ULR, status_code=500)
69
+ with pytest.raises(ConnectionError):
70
+ t = msci_client.oauth_token # noqa
File without changes
@@ -0,0 +1,22 @@
1
+ from django.apps import AppConfig
2
+ from django.conf import settings
3
+ from django.core.checks import Warning, register
4
+
5
+
6
+ @register()
7
+ def check_qa_db_available(app_configs, **kwargs):
8
+ warnings = []
9
+
10
+ if "qa" not in settings.DATABASES:
11
+ warnings.append(
12
+ Warning(
13
+ "A database with the name qa needs to be installed in order to run QA",
14
+ )
15
+ )
16
+
17
+ return warnings
18
+
19
+
20
+ class QAAppConfig(AppConfig):
21
+ name = "wbfdm.contrib.qa"
22
+ verbose_name = "Quantitative Analytics (QA)"
@@ -0,0 +1,25 @@
1
+ class QARouter:
2
+ """
3
+ QA from Refinitiv is usually served through an external mssql, oracle or snowflake database.
4
+ We are going to use it in a mostly read-only way, therefore we need a DBRouter that routes
5
+ all database connections for models from QA to the correct database. The database needs to be
6
+ registered in the settings of the project like this:
7
+
8
+ ```
9
+ DATABASES = {
10
+ "default": {}, # This is the default django database
11
+ "qa": {}, # This is the important database. The name qa is really important here.
12
+ }
13
+ ```
14
+ Afterwards the settings needs to declare this router explicately as one of the `DATABASE_ROUTERS`
15
+ """
16
+
17
+ def db_for_read(self, model, **hints):
18
+ if model._meta.app_label == "qa":
19
+ return "qa"
20
+ return None
21
+
22
+ def allow_migrate(self, db, app_label, model_name=None, **hints):
23
+ if app_label == "qa":
24
+ return False
25
+ return None
File without changes
@@ -0,0 +1,56 @@
1
+ from datetime import date
2
+ from itertools import batched # type: ignore
3
+ from typing import Iterator
4
+
5
+ from django.db import connections
6
+ from jinjasql import JinjaSql # type: ignore
7
+ from wbcore.contrib.dataloader.dataloaders import Dataloader
8
+ from wbcore.contrib.dataloader.utils import dictfetchall
9
+ from wbfdm.dataloaders.protocols import AdjustmentsProtocol
10
+ from wbfdm.dataloaders.types import AdjustmentDataDict
11
+
12
+
13
+ class DatastreamAdjustmentsDataloader(AdjustmentsProtocol, Dataloader):
14
+ def adjustments(self, from_date: date, to_date: date) -> Iterator[AdjustmentDataDict]:
15
+ lookup = {k: v for k, v in self.entities.values_list("dl_parameters__adjustments__parameters", "id")}
16
+
17
+ sql = """
18
+ SELECT
19
+ InfoCode as external_identifier,
20
+ CONCAT(InfoCode, '_', CONVERT(DATE, AdjDate)) as id,
21
+ CONVERT(DATE, AdjDate) as adjustment_date,
22
+ CONVERT(DATE, EndAdjDate) as adjustment_end_date,
23
+ 'qa-ds2' as source,
24
+ AdjFactor as adjustment_factor,
25
+ CumAdjFactor as cumulative_adjustment_factor
26
+
27
+ FROM Ds2Adj
28
+ WHERE
29
+ AdjType = 2
30
+ AND (
31
+ {% for instrument in instruments %}
32
+ InfoCode = {{instrument}} {% if not loop.last %} OR {% endif %}
33
+ {% endfor %}
34
+ )
35
+ {% if from_date %} AND AdjDate >= {{ from_date }} {% endif %}
36
+ {% if to_date %} AND AdjDate <= {{ to_date }} {% endif %}
37
+
38
+ ORDER BY AdjDate DESC
39
+ """
40
+ for batch in batched(lookup.keys(), 1000):
41
+ query, bind_params = JinjaSql(param_style="format").prepare_query(
42
+ sql,
43
+ {
44
+ "instruments": batch,
45
+ "from_date": from_date,
46
+ "to_date": to_date,
47
+ },
48
+ )
49
+ with connections["qa"].cursor() as cursor:
50
+ cursor.execute(
51
+ query,
52
+ bind_params,
53
+ )
54
+ for row in dictfetchall(cursor):
55
+ row["instrument_id"] = lookup[row["external_identifier"]]
56
+ yield row
@@ -0,0 +1,59 @@
1
+ from datetime import date
2
+ from typing import Iterator
3
+
4
+ from django.db import connections
5
+ from jinjasql import JinjaSql # type: ignore
6
+ from wbcore.contrib.dataloader.dataloaders import Dataloader
7
+ from wbcore.contrib.dataloader.utils import dictfetchall
8
+ from wbfdm.dataloaders.protocols import CorporateActionsProtocol
9
+ from wbfdm.dataloaders.types import CorporateActionDataDict
10
+
11
+
12
+ class DatastreamCorporateActionsDataloader(CorporateActionsProtocol, Dataloader):
13
+ def corporate_actions(
14
+ self,
15
+ from_date: date | None = None,
16
+ to_date: date | None = None,
17
+ ) -> Iterator[CorporateActionDataDict]:
18
+ lookup = {k: v for k, v in self.entities.values_list("dl_parameters__corporate_actions__parameters", "id")}
19
+
20
+ sql = """
21
+ SELECT
22
+ InfoCode as external_identifier,
23
+ CONCAT(InfoCode, '_', CONVERT(DATE, EffectiveDate)) as id,
24
+ CONVERT(DATE, EffectiveDate) as valuation_date,
25
+ 'qa-ds2' as source,
26
+ ActionTypeCode as action_code,
27
+ EventStatusCode as event_code,
28
+ NumOldShares as old_shares,
29
+ NumNewShares as new_shares,
30
+ ISOCurrCode as currency
31
+
32
+ FROM Ds2CapEvent
33
+ WHERE (
34
+ {% for instrument in instruments %}
35
+ InfoCode = {{instrument}} {% if not loop.last %} OR {% endif %}
36
+ {% endfor %}
37
+ )
38
+ {% if from_date %} AND EffectiveDate >= {{ from_date }} {% endif %}
39
+ {% if to_date %} AND EffectiveDate <= {{ to_date }} {% endif %}
40
+
41
+ ORDER BY EffectiveDate DESC
42
+ """
43
+
44
+ query, bind_params = JinjaSql(param_style="format").prepare_query(
45
+ sql,
46
+ {
47
+ "instruments": self.entities.values_list("dl_parameters__corporate_actions__parameters", flat=True),
48
+ "from_date": from_date,
49
+ "to_date": to_date,
50
+ },
51
+ )
52
+ with connections["qa"].cursor() as cursor:
53
+ cursor.execute(
54
+ query,
55
+ bind_params,
56
+ )
57
+ for row in dictfetchall(cursor):
58
+ row["instrument_id"] = lookup[row["external_identifier"]]
59
+ yield row
@@ -0,0 +1,83 @@
1
+ from datetime import date
2
+ from itertools import batched # type: ignore
3
+ from typing import Iterator
4
+
5
+ from django.db import connections
6
+ from django.template.loader import get_template
7
+ from jinjasql import JinjaSql # type: ignore
8
+ from wbcore.contrib.dataloader.dataloaders import Dataloader
9
+ from wbcore.contrib.dataloader.utils import dictfetchall
10
+ from wbfdm.dataloaders.protocols import FinancialsProtocol
11
+ from wbfdm.dataloaders.types import FinancialDataDict
12
+ from wbfdm.enums import (
13
+ CalendarType,
14
+ DataType,
15
+ EstimateType,
16
+ Financial,
17
+ PeriodType,
18
+ SeriesType,
19
+ )
20
+
21
+
22
+ class IBESFinancialsDataloader(FinancialsProtocol, Dataloader):
23
+ def financials(
24
+ self,
25
+ values: list[Financial],
26
+ from_date: date | None = None,
27
+ to_date: date | None = None,
28
+ from_year: int | None = None,
29
+ to_year: int | None = None,
30
+ from_index: int | None = None,
31
+ to_index: int | None = None,
32
+ from_valid: date | None = None,
33
+ to_valid: date | None = None,
34
+ period_type: PeriodType = PeriodType.ANNUAL,
35
+ calendar_type: CalendarType = CalendarType.FISCAL,
36
+ series_type: SeriesType = SeriesType.COMPLETE,
37
+ data_type: DataType = DataType.STANDARDIZED,
38
+ estimate_type: EstimateType = EstimateType.VALID,
39
+ target_currency: str | None = None,
40
+ ) -> Iterator[FinancialDataDict]:
41
+ lookup = {k: v for k, v in self.entities.values_list("dl_parameters__financials__parameters", "id")}
42
+ for batch in batched(lookup.keys(), 1000):
43
+ sql = ""
44
+ if series_type == SeriesType.COMPLETE:
45
+ sql = "qa/sql/ibes/complete.sql"
46
+ if calendar_type == CalendarType.CALENDAR:
47
+ sql = "qa/sql/ibes/calendarized.sql"
48
+
49
+ elif series_type == SeriesType.FULL_ESTIMATE:
50
+ sql = "qa/sql/ibes/estimates.sql"
51
+
52
+ query, bind_params = JinjaSql(param_style="format").prepare_query(
53
+ get_template(sql, using="jinja").template,
54
+ {
55
+ "instruments": batch,
56
+ "financials": [financial.value for financial in values],
57
+ "estimate_type": estimate_type.value,
58
+ "from_date": from_date,
59
+ "to_date": to_date,
60
+ "from_year": from_year,
61
+ "to_year": to_year,
62
+ "from_index": from_index,
63
+ "to_index": to_index,
64
+ "from_valid": from_valid,
65
+ "to_valid": to_valid,
66
+ "period_type": period_type.value,
67
+ },
68
+ )
69
+ with connections["qa"].cursor() as cursor:
70
+ cursor.execute(
71
+ query,
72
+ bind_params,
73
+ )
74
+ for row in dictfetchall(cursor):
75
+ row["instrument_id"] = lookup[row["external_identifier"]]
76
+ row["estimate"] = bool(row["estimate"])
77
+ row["value"] = row.get("value", None)
78
+ row["difference_pct"] = row.get("difference_pct", 0)
79
+ row["value_high"] = row.get("value_high", row["value"])
80
+ row["value_low"] = row.get("value_low", row["value"])
81
+ row["value_amount"] = row.get("value_amount", row["value"])
82
+ row["value_stdev"] = row.get("value_stdev", row["value"])
83
+ yield row
@@ -0,0 +1,117 @@
1
+ from datetime import date
2
+ from enum import Enum
3
+ from typing import Iterator
4
+
5
+ from django.db import connections
6
+ from jinjasql import JinjaSql # type: ignore
7
+ from wbcore.contrib.dataloader.dataloaders import Dataloader
8
+ from wbcore.contrib.dataloader.utils import dictfetchall
9
+ from wbfdm.dataloaders.protocols import MarketDataProtocol
10
+ from wbfdm.dataloaders.types import MarketDataDict
11
+ from wbfdm.enums import Frequency, MarketData
12
+
13
+
14
+ class DS2MarketData(Enum):
15
+ OPEN = ("Open_", None)
16
+ CLOSE = ("Close_", None)
17
+ HIGH = ("High", None)
18
+ LOW = ("Low", None)
19
+ BID = ("Bid", None)
20
+ ASK = ("Ask", None)
21
+ VWAP = ("VWAP", None)
22
+ VOLUME = ("Volume", None)
23
+ MARKET_CAPITALIZATION = ("ConsolMktVal", 1_000_000)
24
+ SHARES_OUTSTANDING = ("ConsolNumShrs", 1_000)
25
+
26
+
27
+ class DatastreamMarketDataDataloader(MarketDataProtocol, Dataloader):
28
+ def market_data(
29
+ self,
30
+ values: list[MarketData] | None = [MarketData.CLOSE],
31
+ from_date: date | None = None,
32
+ to_date: date | None = None,
33
+ exact_date: date | None = None,
34
+ frequency: Frequency = Frequency.DAILY,
35
+ target_currency: str | None = None,
36
+ **kwargs,
37
+ ) -> Iterator[MarketDataDict]:
38
+ """Get market data for instruments.
39
+
40
+ Args:
41
+ queryset (QuerySet["Instrument"]): The queryset of instruments.
42
+ values (list[MarketData]): List of values to include in the results.
43
+ from_date (date | None): The starting date for filtering prices. Defaults to None.
44
+ to_date (date | None): The ending date for filtering prices. Defaults to None.
45
+ frequency (Frequency): The frequency of the requested data
46
+
47
+ Returns:
48
+ Iterator[MarketDataDict]: An iterator of dictionaries conforming to the DailyValuationDict.
49
+ """
50
+
51
+ lookup = {
52
+ f"{k[0]},{k[1]}": v for k, v in self.entities.values_list("dl_parameters__market_data__parameters", "id")
53
+ }
54
+ value_mapping = [(DS2MarketData[x.name].value, x.value) for x in values or []]
55
+
56
+ sql = """
57
+ SELECT
58
+ CONCAT(pricing.InfoCode, ',', ExchIntCode) as external_identifier,
59
+ CONCAT(pricing.InfoCode, ',', ExchIntCode, '_', CONVERT(DATE, MarketDate)) as id,
60
+ CONVERT(DATE, MarketDate) as valuation_date,
61
+ 'qa-ds2' as source,
62
+ {% if target_currency %}
63
+ '{{ target_currency|sqlsafe }}' as 'currency',
64
+ {% else %}
65
+ Currency as 'currency',
66
+ {% endif %}
67
+ {% for value in values %}
68
+ {% if target_currency %}
69
+ COALESCE(fx_rate.midrate, 1) *
70
+ {% endif %}
71
+ {{ value[0][0] | sqlsafe }}{% if value[0][1] %} * {{ value[0][1] | sqlsafe }}{% endif %} as '{{ value[1] | sqlsafe }}'{% if not loop.last %}, {% endif %}
72
+ {% endfor %}
73
+ FROM vw_Ds2Pricing as pricing
74
+ LEFT JOIN DS2MktVal as market_val
75
+ ON pricing.InfoCode = market_val.InfoCode
76
+ AND pricing.MarketDate = market_val.ValDate
77
+ {% if target_currency %}
78
+ LEFT JOIN Ds2FxCode as fx_code
79
+ ON fx_code.FromCurrCode = '{{target_currency|sqlsafe}}'
80
+ AND fx_code.ToCurrCode = pricing.Currency
81
+ AND fx_code.RateTypeCode = 'SPOT'
82
+ LEFT JOIN Ds2FxRate as fx_rate
83
+ ON fx_rate.ExRateIntCode = fx_code.ExRateIntCode
84
+ AND fx_rate.ExRateDate = pricing.MarketDate
85
+ {% endif %}
86
+
87
+ WHERE (
88
+ {% for instrument in instruments %}
89
+ (pricing.InfoCode = {{instrument[0]}} AND ExchIntCode = {{instrument[1]}}) {% if not loop.last %} OR {% endif %}
90
+ {% endfor %}
91
+ )
92
+ AND AdjType = 2
93
+ {% if from_date %} AND MarketDate >= {{ from_date }} {% endif %}
94
+ {% if to_date %} AND MarketDate <= {{ to_date }} {% endif %}
95
+ {% if exact_date %} AND MarketDate = {{ exact_date }} {% endif %}
96
+
97
+ ORDER BY MarketDate DESC
98
+ """
99
+ query, bind_params = JinjaSql(param_style="format").prepare_query(
100
+ sql,
101
+ {
102
+ "instruments": self.entities.values_list("dl_parameters__market_data__parameters", flat=True),
103
+ "values": value_mapping,
104
+ "from_date": from_date,
105
+ "to_date": to_date,
106
+ "exact_date": exact_date,
107
+ "target_currency": target_currency,
108
+ },
109
+ )
110
+ with connections["qa"].cursor() as cursor:
111
+ cursor.execute(
112
+ query,
113
+ bind_params,
114
+ )
115
+ for row in dictfetchall(cursor):
116
+ row["instrument_id"] = lookup[row["external_identifier"]]
117
+ yield row
@@ -0,0 +1,59 @@
1
+ from typing import Iterator
2
+
3
+ from django.db import connections
4
+ from jinjasql import JinjaSql # type: ignore
5
+ from wbcore.contrib.dataloader.dataloaders import Dataloader
6
+ from wbcore.contrib.dataloader.utils import dictfetchall
7
+ from wbfdm.dataloaders.protocols import OfficersProtocol
8
+ from wbfdm.dataloaders.types import OfficerDataDict
9
+
10
+
11
+ class RKDOfficersDataloader(OfficersProtocol, Dataloader):
12
+ def officers(
13
+ self,
14
+ ) -> Iterator[OfficerDataDict]:
15
+ lookup = {k: v for k, v in self.entities.values_list("dl_parameters__officers__parameters", "id")}
16
+
17
+ sql = """
18
+ SELECT
19
+ CONCAT(designation.Code, '-', ROW_NUMBER() OVER (ORDER BY officer.OfficerRank)) as id,
20
+ designation.Code as external_identifier,
21
+ designation.Title as position,
22
+ CONCAT(
23
+ officer.Prefix,
24
+ ' ',
25
+ officer.FirstName,
26
+ ' ',
27
+ officer.LastName,
28
+ CASE
29
+ WHEN officer.Suffix IS NOT NULL THEN CONCAT(', ', officer.Suffix)
30
+ ELSE ''
31
+ END
32
+ ) as name,
33
+ officer.Age as age,
34
+ officer.Sex as sex,
35
+ CONVERT(DATE, designation.DesgStartDt) as start
36
+ FROM RKDFndCmpOffTitleChg AS designation
37
+ JOIN RKDFndCmpOfficer AS officer
38
+ ON designation.Code = officer.Code
39
+ AND designation.OfficerID = officer.Officerid
40
+
41
+ WHERE
42
+ designation.Code in (
43
+ {% for instrument in instruments %}
44
+ {{instrument}} {% if not loop.last %}, {% endif %}
45
+ {% endfor %})
46
+ AND DesgEndDt IS NULL
47
+
48
+ ORDER BY
49
+ officer.OfficerRank
50
+ """
51
+ query, bind_params = JinjaSql(param_style="format").prepare_query(sql, {"instruments": lookup.keys()})
52
+ with connections["qa"].cursor() as cursor:
53
+ cursor.execute(
54
+ query,
55
+ bind_params,
56
+ )
57
+ for row in dictfetchall(cursor):
58
+ row["instrument_id"] = lookup[row["external_identifier"]]
59
+ yield row