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,71 @@
1
+ from datetime import datetime
2
+ from typing import List
3
+
4
+ from django.core.exceptions import ValidationError
5
+ from django.db import models
6
+ from wbcore.contrib.io.exceptions import DeserializationError
7
+ from wbcore.contrib.io.imports import ImportExportHandler
8
+ from wbfdm.import_export.handlers.instrument import InstrumentImportHandler
9
+
10
+ MAX_NB_DATES_BEFORE_HISTORICAL_IMPORT = 3
11
+
12
+
13
+ class InstrumentPriceImportHandler(ImportExportHandler):
14
+ MODEL_APP_LABEL: str = "wbfdm.InstrumentPrice"
15
+
16
+ def __init__(self, *args, **kwargs):
17
+ super().__init__(*args, **kwargs)
18
+ self.instrument_handler = InstrumentImportHandler(self.import_source)
19
+ self.updated_instruments = set()
20
+
21
+ def _deserialize(self, data):
22
+ if data.get("instrument", None) is None or data.get("date", None) is None:
23
+ raise DeserializationError("Instrument or Date is empty")
24
+ data["date"] = datetime.strptime(data["date"], "%Y-%m-%d").date()
25
+
26
+ instrument = self.instrument_handler.process_object(data["instrument"], only_security=False, read_only=True)[0]
27
+ if not instrument:
28
+ raise DeserializationError("Instrument couldn't be found with given data")
29
+ self.updated_instruments.add(instrument)
30
+ data["instrument"] = instrument
31
+ if data.get("net_value", None) is None:
32
+ # casted_instrument = instrument.get_casted_instrument()
33
+ # # we try to find the primary field among the data to set the net value from it
34
+ # if primary_field_value := data.pop(casted_instrument.primary_field, None):
35
+ # data["net_value"] = primary_field_value
36
+ # # in a last case, we try to find the close value and set it as the net value
37
+ if close := data.pop("close", None):
38
+ data["net_value"] = close
39
+ else:
40
+ if data.get("gross_value", None) is None:
41
+ data["gross_value"] = data["net_value"]
42
+
43
+ # Backward compatibility with refinitiv parser.
44
+ for forbbiden_field in ["close", "price_index", "yield_redemption", "net_return", "offered_rate"]:
45
+ data.pop(forbbiden_field, None)
46
+
47
+ # We do this to ensure an invalid number won't make the import fails
48
+ for k, v in data.items():
49
+ if v and (field := self.model._meta.get_field(k)) and hasattr(field, "max_digits"):
50
+ try:
51
+ field.clean(value=str(round(v, field.decimal_places)), model_instance=None)
52
+ except ValidationError:
53
+ data[k] = None
54
+
55
+ if data.get("net_value", None) is None:
56
+ raise DeserializationError("Net value not set.")
57
+
58
+ def _get_instance(self, data, history=None, **kwargs):
59
+ self.import_source.log += f"\nParameter: Instrument={data['instrument']} Date={data['date']}"
60
+ price = data["instrument"].prices.filter(date=data["date"], calculated=False)
61
+ if price.exists():
62
+ return price.first()
63
+
64
+ def _post_processing_objects(
65
+ self,
66
+ created_objs: List[models.Model],
67
+ modified_objs: List[models.Model],
68
+ unmodified_objs: List[models.Model],
69
+ ):
70
+ for instrument in self.updated_instruments:
71
+ instrument.update_last_valuation_date()
@@ -0,0 +1,54 @@
1
+ from contextlib import suppress
2
+ from datetime import datetime
3
+ from typing import Any, Dict, Optional
4
+
5
+ from django.core.exceptions import ObjectDoesNotExist
6
+ from django.db import models
7
+ from wbcore.contrib.io.exceptions import DeserializationError
8
+ from wbcore.contrib.io.imports import ImportExportHandler
9
+
10
+ from .instrument import InstrumentImportHandler
11
+
12
+
13
+ class OptionAggregateImportHandler(ImportExportHandler):
14
+ MODEL_APP_LABEL: str = "wbfdm.OptionAggregate"
15
+
16
+ def __init__(self, *args, **kwargs):
17
+ super().__init__(*args, **kwargs)
18
+ self.instrument_handler = InstrumentImportHandler(self.import_source)
19
+
20
+ def _deserialize(self, data):
21
+ data["date"] = datetime.strptime(data["date"], "%Y-%m-%d").date()
22
+ instrument = self.instrument_handler.process_object(data["instrument"], read_only=True)[0]
23
+ if not instrument:
24
+ raise DeserializationError("Instrument couldn't be found with given data")
25
+ data["instrument"] = instrument
26
+
27
+ def _get_instance(self, data: Dict[str, Any], history: Optional[models.QuerySet] = None, **kwargs) -> models.Model:
28
+ with suppress(ObjectDoesNotExist):
29
+ return self.model.objects.filter(instrument=data["instrument"], date=data["date"], type=data["type"])
30
+
31
+
32
+ class OptionImportHandler(ImportExportHandler):
33
+ MODEL_APP_LABEL: str = "wbfdm.Option"
34
+
35
+ def __init__(self, *args, **kwargs):
36
+ super().__init__(*args, **kwargs)
37
+ self.instrument_handler = InstrumentImportHandler(self.import_source)
38
+
39
+ def _deserialize(self, data):
40
+ data["date"] = datetime.strptime(data["date"], "%Y-%m-%d").date()
41
+ data["expiration_date"] = datetime.strptime(data["expiration_date"], "%Y-%m-%d").date()
42
+ instrument = self.instrument_handler.process_object(data["instrument"], read_only=True)[0]
43
+ if not instrument:
44
+ raise DeserializationError("Instrument couldn't be found with given data")
45
+ data["instrument"] = instrument
46
+
47
+ def _get_instance(self, data: Dict[str, Any], history: Optional[models.QuerySet] = None, **kwargs) -> models.Model:
48
+ with suppress(ObjectDoesNotExist):
49
+ return self.model.objects.filter(
50
+ instrument=data["instrument"],
51
+ contract_identifier=data["contract_identifier"],
52
+ date=data["date"],
53
+ type=data["type"],
54
+ )
@@ -0,0 +1,49 @@
1
+ from datetime import datetime
2
+ from typing import Any, Dict, Optional
3
+
4
+ from django.db import models
5
+ from wbcore.contrib.io.exceptions import DeserializationError
6
+ from wbcore.contrib.io.imports import ImportExportHandler
7
+
8
+ from .instrument import InstrumentImportHandler
9
+
10
+
11
+ class DealImportHandler(ImportExportHandler):
12
+ MODEL_APP_LABEL: str = "wbfdm.Deal"
13
+
14
+ def __init__(self, *args, **kwargs):
15
+ super().__init__(*args, **kwargs)
16
+ self.instrument_handler = InstrumentImportHandler(self.import_source)
17
+
18
+ def _deserialize(self, data):
19
+ data["date"] = datetime.strptime(data["date"], "%Y-%m-%d").date()
20
+ equity = self.instrument_handler.process_object(
21
+ {**data["equity"], "instrument_type": "private_equity"}, read_only=True
22
+ )[0]
23
+ if not equity:
24
+ raise DeserializationError("Private Equity couldn't be found with given data")
25
+ data["equity"] = equity
26
+
27
+ if investors := data.pop("investors", []):
28
+ data["investors"] = [
29
+ self.instrument_handler.process_object(investor_data, read_only=True)[0] for investor_data in investors
30
+ ]
31
+
32
+ def _get_instance(self, data: Dict[str, Any], history: Optional[models.QuerySet] = None, **kwargs) -> models.Model:
33
+ if external_id := data.get("external_id", None):
34
+ return self.model.objects.filter(external_id=external_id).first()
35
+ qs = self.model.objects.filter(
36
+ equity=data["equity"], date=data["date"], transaction_amount=data["transaction_amount"]
37
+ )
38
+ if qs.count() == 1:
39
+ return qs.first()
40
+
41
+ def _create_instance(self, data: Dict[str, Any], **kwargs) -> models.Model:
42
+ investors = data.pop("investors", None)
43
+ obj = self.model.objects.create(
44
+ **data,
45
+ import_source=self.import_source,
46
+ )
47
+ if investors:
48
+ obj.investors.set([i for i in investors if i])
49
+ return obj
File without changes
File without changes
@@ -0,0 +1,39 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ from wbfdm.models import Deal
4
+
5
+ COLUMNS_MAP = {
6
+ "dealDate": "date",
7
+ "fundedOrg": "equity__provider_id",
8
+ "dealSizeInMillions": "transaction_amount",
9
+ "investors": "investors",
10
+ "fundingRound": "funding_round",
11
+ "fundingRoundCategory": "funding_round_category",
12
+ "valuationInMillions": "valuation",
13
+ "valuationIsEstimate": "valuation_estimated",
14
+ "valuationSourceType": "valuation_source_type",
15
+ "valuationSourceUrls": "valuation_media_mention_source_urls",
16
+ "dealId": "external_id",
17
+ }
18
+
19
+
20
+ def parse(import_source):
21
+ def _parse_investors(investors):
22
+ return [{"provider_id": investor["orgId"]} for investor in investors]
23
+
24
+ data = []
25
+ df = pd.read_json(import_source.file, orient="records")
26
+
27
+ df = df.rename(columns=COLUMNS_MAP)
28
+ df["equity__provider_id"] = df["equity__provider_id"].apply(lambda x: x["orgId"])
29
+ df["type"] = Deal.Types.DEAL
30
+ if not df.empty:
31
+ df["investors"] = df.investors.apply(lambda x: _parse_investors(x))
32
+ df["valuation"] *= 1e6
33
+ df["transaction_amount"] *= 1e6
34
+ df = df.drop(columns=df.columns.difference(COLUMNS_MAP.values()))
35
+ df = df.replace([np.inf, -np.inf, np.nan], None)
36
+ df = df[~df["transaction_amount"].isnull()]
37
+ data.extend(df.to_dict("records"))
38
+
39
+ return {"data": data}
@@ -0,0 +1,56 @@
1
+ import json
2
+
3
+ from wbcore.contrib.geography.models import Geography
4
+ from wbfdm.models import ClassificationGroup
5
+
6
+ FIELDS_MAP = {
7
+ "orgName": "name",
8
+ "url": "primary_url",
9
+ "description": "description",
10
+ }
11
+
12
+
13
+ def parse(import_source):
14
+ cbinsight_group = ClassificationGroup.objects.get_or_create(name="CBinsights", max_depth=2)[0]
15
+ content = json.load(import_source.file)
16
+
17
+ data = []
18
+ for org_data in content:
19
+ d = {
20
+ "provider_id": org_data["orgId"],
21
+ "description": org_data["description"],
22
+ "name": org_data["orgName"],
23
+ "primary_url": org_data["url"],
24
+ "instrument_type": "private_equity",
25
+ }
26
+ if summary := org_data.get("orgSummary", None):
27
+ if additional_urls := summary.get("additionalUrls", None):
28
+ d["additional_urls"] = additional_urls
29
+ if alternative_names := summary.get("aliases", None):
30
+ d["alternative_names"] = alternative_names
31
+ d["founded_year"] = summary["foundedYear"]
32
+
33
+ if (country_name := summary.get("country")) and (
34
+ country := Geography.countries.get_by_name(country_name.strip())
35
+ ):
36
+ d["country"] = country.id
37
+
38
+ if city_name := summary.get("city", None):
39
+ # We comment this out because looking up the city at import time drastically decrease speed
40
+ # if city := Geography.cities.get_by_name(city_name.strip(), parent__parent=country):
41
+ # d["headquarter_city"] = city.id
42
+ if (postal_code := summary.get("postalCode", None)) and (street := summary.get("street", None)):
43
+ d["headquarter_address"] = f"{street}, {postal_code} {city_name}"
44
+
45
+ if sector_id := summary.get("sectorId", None):
46
+ try:
47
+ code_aggregated = f"{int(sector_id):03}"
48
+ if industry_id := summary.get("industryId", None):
49
+ code_aggregated += f"{int(industry_id):03}"
50
+ if subindustry_id := summary.get("subindustryId", None):
51
+ code_aggregated += f"{int(subindustry_id):03}"
52
+ except Exception:
53
+ pass
54
+ d["classifications"] = [{"code_aggregated": code_aggregated, "group": cbinsight_group.id}]
55
+ data.append(d)
56
+ return {"data": data}
@@ -0,0 +1,45 @@
1
+ import json
2
+
3
+ import pandas as pd
4
+ from django.contrib.contenttypes.models import ContentType
5
+ from wbfdm.models import Instrument
6
+
7
+
8
+ def parse(import_source):
9
+ content = json.load(import_source.file)
10
+ data = []
11
+ equity_content_type_id = ContentType.objects.get_for_model(Instrument).id
12
+ for org_data in content:
13
+ if (revenue_data := org_data.get("orgRevenue", None)) and not (df := pd.DataFrame(revenue_data)).empty:
14
+ df = df[["revenueFiscalYear", "revenueMin", "revenueMax"]]
15
+ df["revenue"] = (df.revenueMin + df.revenueMax) * 1e6 / 2
16
+ df = df[["revenue", "revenueFiscalYear"]].rename(
17
+ columns={"revenueFiscalYear": "period__period_year", "revenue": "revenue"}
18
+ )
19
+ df = df.groupby("period__period_year").mean().reset_index()
20
+ df.period__period_year = df.period__period_year.astype(int)
21
+ df["period__period_type"] = "FiscalPeriod.PeriodTypeChoice.ANNUAL" # TODO REFACTORING
22
+ df["period__period_interim"] = True
23
+ df["period__period_end_date"] = df.period__period_year.apply(lambda x: f"{x}-12-31")
24
+ df["instrument__provider_id"] = org_data["orgId"]
25
+ df["instrument__content_type"] = equity_content_type_id
26
+ data.extend(df.to_dict("records"))
27
+ elif (
28
+ (kpi_data := org_data.get("orgKPIs", None))
29
+ and (last_revenue_max := kpi_data.get("latestRevenueMax", None))
30
+ and (last_revenue_min := kpi_data.get("latestRevenueMin", None))
31
+ and (last_revenue_year := kpi_data.get("latestRevenueFiscalYear", None))
32
+ ):
33
+ fiscal_year = int(last_revenue_year)
34
+ data.append(
35
+ {
36
+ "revenue": (last_revenue_max + last_revenue_min) / 2,
37
+ "period__period_type": "FiscalPeriod.PeriodTypeChoice.ANNUAL", # TODO REFACTORING,
38
+ "period__period_interim": True,
39
+ "period__period_year": fiscal_year,
40
+ "period__period_end_date": f"{fiscal_year}-12-31",
41
+ "instrument__provider_id": org_data["orgId"],
42
+ "instrument__content_type": equity_content_type_id,
43
+ }
44
+ )
45
+ return {"data": data}
File without changes
@@ -0,0 +1,7 @@
1
+ from wbfdm.import_export.backends.refinitiv.daily_fundamental import DEFAULT_MAPPING
2
+
3
+ from .utils import parse_daily_fundamental_data
4
+
5
+
6
+ def parse(import_source):
7
+ return parse_daily_fundamental_data(import_source, DEFAULT_MAPPING, extra_normalization_map={"free_cash": 1000})
@@ -0,0 +1,7 @@
1
+ from wbfdm.import_export.backends.refinitiv.forecast import DEFAULT_MAPPING
2
+
3
+ from .utils import parse_daily_fundamental_data
4
+
5
+
6
+ def parse(import_source):
7
+ return parse_daily_fundamental_data(import_source, DEFAULT_MAPPING)
@@ -0,0 +1,9 @@
1
+ from wbfdm.import_export.backends.refinitiv.fundamental import DEFAULT_MAPPING
2
+
3
+ from .utils import parse_periodic_fundamental_data
4
+
5
+
6
+ def parse(import_source):
7
+ return parse_periodic_fundamental_data(
8
+ import_source, DEFAULT_MAPPING, extra_normalization_map={"company_tax_rate": 0.01}
9
+ )
@@ -0,0 +1,7 @@
1
+ from wbfdm.import_export.backends.refinitiv.geographic_segment import DEFAULT_MAPPING
2
+
3
+ from .utils import parse_periodic_fundamental_data
4
+
5
+
6
+ def parse(import_source):
7
+ return parse_periodic_fundamental_data(import_source, DEFAULT_MAPPING)
@@ -0,0 +1,75 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ from wbfdm.import_export.backends.refinitiv.instrument import DEFAULT_MAPPING
4
+ from wbfdm.models import ClassificationGroup
5
+
6
+ from .utils import get_country, get_exchange
7
+
8
+
9
+ def get_instrument_type(_type):
10
+ if _type == "BD":
11
+ return "bond"
12
+ elif _type in ["EQ", "ADR", "ET", "GDR", "INVT", "CF"]:
13
+ return "equity"
14
+ elif _type in ["CF"]:
15
+ return "close_ended_fund"
16
+ elif _type == "EQIND":
17
+ return "index"
18
+ elif _type == "OP":
19
+ return "option"
20
+ elif _type == "SWAPS":
21
+ return "swaps"
22
+ elif _type == "CMD":
23
+ return "commodity"
24
+ elif _type == "INT":
25
+ return "interest_rate_derivative"
26
+ elif _type == "FT":
27
+ return "future"
28
+
29
+
30
+ def parse(import_source):
31
+ # Read Json as dataframe
32
+ df = pd.read_json(import_source.file, orient="records")
33
+ # Sanitize df and rename columns to model knowledge's
34
+ data = list()
35
+ if not df.empty:
36
+ df = (
37
+ df.replace({"NA": None})
38
+ .rename(columns={**DEFAULT_MAPPING, "Instrument": "instrument"})
39
+ .set_index("instrument")
40
+ )
41
+ df = df.drop(columns=df.columns.difference(DEFAULT_MAPPING.values()))
42
+ df = df.replace([np.inf, -np.inf, np.nan], None)
43
+ df["ticker"] = df["ticker"].astype(str)
44
+ df = df.dropna(how="all", subset=df.columns)
45
+ df = df.groupby(level=0, axis=1).first()
46
+ df["inception_date"] = pd.to_datetime(df["inception_date"], format="%Y%m%d", errors="coerce").dt.strftime(
47
+ "%Y-%m-%d"
48
+ )
49
+ df = df.replace([np.inf, -np.inf, np.nan, pd.NaT], None).replace({"None": None}).dropna(how="all")
50
+ for row in df.to_dict("records"):
51
+ exchanges = []
52
+ if exchanges_str := row.pop("exchanges", None):
53
+ exchanges = [get_exchange(e) for e in exchanges_str.split(" ")]
54
+ if exchange := row.pop("exchange", None):
55
+ exchanges.append(get_exchange(exchange))
56
+ row["instrument_type"] = get_instrument_type(row.get("instrument_type", None))
57
+ if ticker := row.get("ticker", None):
58
+ row["ticker"] = ticker.replace("'", "").split("-")[0]
59
+
60
+ row["exchanges"] = exchanges
61
+ row["country"] = get_country(row.get("country", None))
62
+ classifications = []
63
+ if gics_classification := row.pop("gics_classification", None):
64
+ classifications.append(
65
+ {
66
+ "code_aggregated": gics_classification,
67
+ "group": ClassificationGroup.objects.get(is_primary=True).id,
68
+ }
69
+ )
70
+ if trbc_classification := row.pop("trbc_classification", None):
71
+ group, created = ClassificationGroup.objects.get_or_create(name="TRBC")
72
+ classifications.append({"code_aggregated": trbc_classification, "group": group.id})
73
+ row["classification"] = classifications
74
+ data.append({k: v for k, v in row.items() if v})
75
+ return {"data": data}
@@ -0,0 +1,26 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ from wbfdm.import_export.backends.refinitiv.instrument_price import DEFAULT_MAPPING
4
+
5
+ from .utils import _clean_and_return_dict
6
+
7
+
8
+ def parse(import_source):
9
+ # Read Json as dataframe
10
+ df = pd.read_json(import_source.file, orient="records")
11
+ df = df.replace([np.inf, -np.inf, np.nan], None)
12
+ # Sanitize df and rename columns to model knowledge's
13
+ df = (
14
+ df.replace({"NA": None})
15
+ .rename(columns=DEFAULT_MAPPING)
16
+ .rename(columns={"Instrument": "instrument", "Dates": "date"})
17
+ )
18
+ # Sanitize
19
+ df = df.replace([np.inf, -np.inf, np.nan], None)
20
+ df = df.dropna(how="all", subset=df.columns.difference(["instrument"]))
21
+
22
+ df["date"] = pd.to_datetime(df["date"], utc=True, unit="ms")
23
+ df = df.sort_values(by="date", ascending=True)
24
+ df["date"] = df["date"].dt.strftime("%Y-%m-%d")
25
+ df = df.replace([np.inf, -np.inf, np.nan], None)
26
+ return {"data": _clean_and_return_dict(df)}
@@ -0,0 +1,96 @@
1
+ from datetime import date
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ from wbcore.contrib.geography.models import Geography
6
+
7
+ REPORT_TYPE_MAP = {1: "Q", 2: "6M", 3: "4M", 4: "Y"}
8
+
9
+
10
+ def get_exchange(code):
11
+ return {"mic_code": code}
12
+
13
+
14
+ def get_country(code):
15
+ country = Geography.countries.filter(code_2=code).first()
16
+ return country.id if country else None
17
+
18
+
19
+ def _clean_and_return_dict(df, extra_normalization_map=None):
20
+ # df = df.groupby(df.columns, axis=1).sum()
21
+ if extra_normalization_map:
22
+ for col, den in extra_normalization_map.items():
23
+ if col in df.columns:
24
+ df[col] *= den
25
+ if percent_fields_columns := list(filter(lambda x: "margin" in x, df.columns)):
26
+ df[percent_fields_columns] = (
27
+ df[percent_fields_columns] / 100
28
+ ) # margin/percent type of data are given in base 100 by refinitiv
29
+ df["instrument"] = df["instrument"].str.replace("^<|>", "", regex=True)
30
+ df = df.rename(columns={"instrument": "instrument__provider_id"})
31
+ # df["instrument__refinitiv_identifier_code"] = df["instrument__provider_id"] # We add this for backward compatibility reason
32
+ df = df.replace([np.inf, -np.inf, np.nan], None)
33
+ return df.to_dict("records")
34
+
35
+
36
+ def parse_daily_fundamental_data(import_source, default_mapping, extra_normalization_map=None):
37
+ df = pd.read_json(import_source.file, orient="records")
38
+ df = df.replace([np.inf, -np.inf, np.nan], None)
39
+ mapping = {**default_mapping, "Instrument": "instrument", "Dates": "date"}
40
+ df = df.rename(columns=mapping).dropna(how="all", axis=1)
41
+ df = df.drop(columns=df.columns.difference(mapping.values()))
42
+ data = list()
43
+ if not df.empty:
44
+ df["date"] = pd.to_datetime(df["date"], utc=True, unit="ms")
45
+ df = df.sort_values(by="date", ascending=True)
46
+ df.date = df.date.dt.strftime("%Y-%m-%d")
47
+ data = _clean_and_return_dict(df, extra_normalization_map=extra_normalization_map)
48
+ return {"data": data}
49
+
50
+
51
+ def parse_periodic_fundamental_data(import_source, default_mapping, extra_normalization_map=None):
52
+ def _get_fiscal_period(_date, frequency):
53
+ fiscal_period = 0
54
+ for index, p in enumerate(
55
+ pd.period_range(start=date(_date.year, 1, 1), end=date(_date.year, 12, 31), freq=frequency)
56
+ ):
57
+ if _date >= p.start_time.date() and _date <= p.end_time.date():
58
+ fiscal_period = index + 1
59
+ return fiscal_period
60
+
61
+ df = pd.read_json(import_source.file, orient="records")
62
+ df = df.replace([np.inf, -np.inf, np.nan], None)
63
+ mapping = {**default_mapping, "Instrument": "instrument", "Dates": "date"}
64
+ df = df.rename(columns=mapping).dropna(how="all", axis=1)
65
+
66
+ df = df.drop(columns=df.columns.difference([*mapping.values(), "period__period_interim"]))
67
+ df = df.dropna(
68
+ how="all",
69
+ subset=df.columns.difference(
70
+ ["instrument", "date", "period__period_interim", "period__period_end_date", "period__period_type"]
71
+ ),
72
+ )
73
+ df = df.dropna(
74
+ how="any",
75
+ subset=df.columns.intersection(["instrument", "date", "period__period_end_date", "period__period_type"]),
76
+ )
77
+ data = list()
78
+ if not df.empty:
79
+ df["date"] = pd.to_datetime(df["date"], utc=True, unit="ms")
80
+ df["period__period_end_date"] = pd.to_datetime(df["period__period_end_date"], utc=True)
81
+ df["period__period_year"] = df.date.dt.year
82
+ df["period__period_type"] = df["period__period_type"].map(REPORT_TYPE_MAP)
83
+ df["period__period_type"] = df["period__period_type"].fillna("Q") # Assuming empty period type is quaterly
84
+ df["period__period_index"] = df[["date", "period__period_type"]].apply(
85
+ lambda x: _get_fiscal_period(x["date"].date(), x["period__period_type"]), axis=1
86
+ )
87
+ del df["date"]
88
+
89
+ df["period__period_end_date"] = df["period__period_end_date"] + pd.offsets.MonthEnd(0)
90
+ df["period__period_end_date"] = df["period__period_end_date"].dt.strftime("%Y-%m-%d")
91
+ df = df.sort_values(by="period__period_end_date")
92
+
93
+ if "employee_count" in df.columns:
94
+ df["employee_count"] = df["employee_count"].bfill()
95
+ data = _clean_and_return_dict(df, extra_normalization_map=extra_normalization_map)
96
+ return {"data": data}
File without changes
@@ -0,0 +1,23 @@
1
+ from import_export.widgets import ManyToManyWidget
2
+ from wbfdm.models import Classification, ClassificationGroup
3
+ from wbfdm.preferences import get_default_classification_group
4
+
5
+
6
+ class ClassificationManyToManyWidget(ManyToManyWidget):
7
+ model = Classification
8
+ field = "code_aggregated"
9
+
10
+ def __init__(self, separator=None, field="code_aggregated", primary_classification_group=False, **kwargs):
11
+ super().__init__(Classification, separator=separator, field=field, **kwargs)
12
+ self.primary_classification_group = primary_classification_group
13
+
14
+ def clean(self, value, row=None, **kwargs):
15
+ if self.primary_classification_group:
16
+ primary_classification_group = ClassificationGroup.objects.get(is_primary=True)
17
+ else:
18
+ primary_classification_group = get_default_classification_group()
19
+ queryset = Classification.objects.filter(
20
+ **{f"{self.field}__icontains": value, "group": primary_classification_group}
21
+ )
22
+ if queryset.count() == 1:
23
+ return queryset
@@ -0,0 +1,33 @@
1
+ from import_export import fields
2
+ from import_export.widgets import ForeignKeyWidget
3
+ from wbcore.contrib.io.resources import FilterModelResource
4
+ from wbfdm.models import Instrument, InstrumentPrice
5
+
6
+
7
+ class InstrumentPriceExportResource(FilterModelResource):
8
+ """
9
+ Instrument Price Resource class to use to export instrument price from the viewset
10
+ """
11
+
12
+ instrument = fields.Field(
13
+ column_name="instrument",
14
+ attribute="instrument",
15
+ widget=ForeignKeyWidget(Instrument, field="isin"),
16
+ )
17
+
18
+ class Meta:
19
+ fields = (
20
+ "date",
21
+ "net_value",
22
+ "gross_value",
23
+ "market_capitalization",
24
+ "sharpe_ratio",
25
+ "correlation",
26
+ "beta",
27
+ "outstanding_shares_consolidated",
28
+ "volume",
29
+ "volume_50d",
30
+ "instrument",
31
+ )
32
+ export_order = fields
33
+ model = InstrumentPrice