trendvisdata 1.0.4__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.
@@ -0,0 +1,541 @@
1
+ """
2
+ Data extraction functions
3
+
4
+ """
5
+ import datetime as dt
6
+ from io import StringIO
7
+ import norgatedata
8
+ import pandas as pd
9
+ import requests
10
+ from pandas.tseries.offsets import BDay
11
+ # from yahoofinancials import YahooFinancials
12
+ import yfinance as yf
13
+
14
+
15
+ class NorgateExtract():
16
+ """
17
+ Functions to extract data from Norgate Data
18
+
19
+ """
20
+ @staticmethod
21
+ def get_norgate_tickers(params: dict) -> dict:
22
+ """
23
+ Create list of all available Norgate Commodity tickers
24
+
25
+ Returns
26
+ -------
27
+ tickers : List
28
+ Returns a list of ticker codes.
29
+ init_ticker_dict : Dict
30
+ Dictionary of ticker-security name pairs
31
+
32
+ """
33
+
34
+ # Specify Norgate Cash Commodities database and extract data
35
+ databasename = 'Cash Commodities'
36
+ databasecontents = norgatedata.database(databasename)
37
+
38
+ # Create empty dictionary to store tickers
39
+ init_ticker_dict = {}
40
+
41
+ # For each dictionary in the data extract
42
+ for dicto in databasecontents:
43
+
44
+ # Add the symbol and security name to the ticker dict as a
45
+ # key-value pair
46
+ key = dicto['symbol']
47
+ value = dicto['securityname']
48
+ if 'Stocks' not in value:
49
+ init_ticker_dict[key] = value
50
+
51
+ # Specify Norgate Continuous Futures database and extract data
52
+ databasename = 'Continuous Futures'
53
+ databasecontents = norgatedata.database(databasename)
54
+
55
+ # For each dictionary in the data extract
56
+ for dicto in databasecontents:
57
+
58
+ # Add the symbol and security name to the ticker dict as a
59
+ # key-value pair
60
+ key = dicto['symbol']
61
+ value = dicto['securityname']
62
+
63
+ if params['tickers_adjusted']:
64
+ # Only take the back-adjusted tickers
65
+ if '_CCB' in key:
66
+ init_ticker_dict[key] = value
67
+
68
+ else:
69
+ if key.startswith('&') and '_CCB' not in key:
70
+ init_ticker_dict[key] = value
71
+
72
+ # Convert the ticker dict keys into a list
73
+ tickers = list(init_ticker_dict.keys())
74
+
75
+ params['tickers'] = tickers
76
+ params['init_ticker_dict'] = init_ticker_dict
77
+
78
+ return params
79
+
80
+
81
+ @classmethod
82
+ def import_norgate(
83
+ cls,
84
+ params: dict,
85
+ tables: dict,
86
+ mappings: dict) -> tuple[dict, dict, dict]:
87
+ """
88
+ Return dictionary of price histories from Norgate Data.
89
+
90
+ Parameters
91
+ ----------
92
+ params : Dict
93
+ Dictionary of key parameters.
94
+ tables : Dict
95
+ Dictionary of key tables.
96
+ mappings : Dict
97
+ Dictionary of sector mappings.
98
+
99
+ Returns
100
+ -------
101
+ tables : Dict
102
+ raw_ticker_dict : Dict
103
+ Dictionary of price history DataFrames, one for each ticker.
104
+ params : Dict
105
+ ticker_name_dict : Dict
106
+ Dictionary mapping ticker to long name for each ticker.
107
+ ticker_short_name_dict : Dict
108
+ Dictionary mapping ticker to short name for each ticker.
109
+ mappings : Dict
110
+ Dictionary of sector mappings
111
+
112
+ """
113
+ # Create empty dictionaries
114
+ tables['raw_ticker_dict'] = {}
115
+ params['ticker_name_dict'] = {}
116
+ params['ticker_short_name_dict'] = {}
117
+
118
+ # Loop through list of tickers
119
+ for ticker in params['tickers'][:params['ticker_limit']]:
120
+
121
+ # Append character to each ticker to represent its type and create
122
+ # lowercase value
123
+ tick = params['ticker_types'][ticker[0]]+ticker[1:]
124
+ lowtick = tick.lower()
125
+
126
+ # Set data format and extract each DataFrame, storing as
127
+ # a key-value pair in ticker_dict
128
+ timeseriesformat = 'pandas-dataframe'
129
+ try:
130
+ data = norgatedata.price_timeseries(
131
+ ticker, start_date=params['start_date'],
132
+ end_date=params['end_date'],
133
+ format=timeseriesformat,)
134
+
135
+ if type(data) == pd.DataFrame:
136
+ data = data[['Open', 'High', 'Low', 'Close']]
137
+
138
+ tables['raw_ticker_dict'][lowtick] = data
139
+
140
+ # Extract the security name and store in ticker_name_dict
141
+ ticker_name = norgatedata.security_name(ticker)
142
+ params['ticker_name_dict'][lowtick] = ticker_name
143
+
144
+ # Set the proper length of DataFrame to help filter out
145
+ # missing data
146
+ params = MktUtils.window_set(frame=data, params=params)
147
+
148
+ except IndexError:
149
+ print('Error importing : ', ticker)
150
+
151
+ #try:
152
+ # Truncate the ticker name to improve charting legibility
153
+ # and store in ticker_short_name_dict
154
+ params['ticker_short_name_dict'][
155
+ lowtick] = ticker_name.partition(" Continuous")[0]
156
+
157
+ #except:
158
+ # params['ticker_short_name_dict'][lowtick] = ticker_name
159
+
160
+ # Create sector mappings DataFrame
161
+ mappings['sector_mappings_df'] = cls._commodity_sector_mappings(
162
+ params, mappings)
163
+
164
+ return params, tables, mappings
165
+
166
+
167
+ @staticmethod
168
+ def _commodity_sector_mappings(
169
+ params: dict,
170
+ mappings: dict) -> pd.DataFrame:
171
+ """
172
+ Create sector mappings DataFrame
173
+
174
+ Parameters
175
+ ----------
176
+ params : Dict
177
+ Dictionary of key parameters.
178
+ mappings : Dict
179
+ Dictionary of sector mappings.
180
+
181
+ Returns
182
+ -------
183
+ sector_mappings_df : DataFrame
184
+ Sector mappings DataFrame.
185
+
186
+ """
187
+ # Create empty dictionary
188
+ sectors = {}
189
+
190
+ # For each key-value pair in the default sector mappings dictionary
191
+ for key, value in mappings['commodity_sector_mappings'].items():
192
+
193
+ # If the first character in the key is in the list of keys from the
194
+ # ticker types dictionary
195
+ if key[0] in list(params['ticker_types'].keys()):
196
+
197
+ # Create a new key equal to the lower case original key with
198
+ # the first character replaced by the value in the ticker types
199
+ # dictionary
200
+ new_key = key.lower().replace(
201
+ key[0], params['ticker_types'][key[0]])
202
+
203
+ # create an entry in the sectors dictionary
204
+ sectors[new_key] = value
205
+
206
+ # Create a sector mappings DataFrame from the sectors dictionary using
207
+ # the default commodity sector levels list as the column headers
208
+ sector_mappings_df = pd.DataFrame.from_dict(
209
+ sectors,
210
+ orient='index',
211
+ columns=params['commodity_sector_levels'])
212
+
213
+ return sector_mappings_df
214
+
215
+
216
+ class YahooExtract():
217
+ """
218
+ Functions to extract data from Yahoo Finance
219
+
220
+ """
221
+ @staticmethod
222
+ def ticker_extract(
223
+ params: dict,
224
+ mappings: dict) -> tuple[dict, dict]:
225
+ """
226
+ Extract list of S&P 500 Companies from Wikipedia.
227
+
228
+ Parameters
229
+ ----------
230
+ params : Dict
231
+ Dictionary of key parameters.
232
+ mappings : Dict
233
+ Dictionary of sector mappings.
234
+
235
+ Returns
236
+ -------
237
+ params : Dict
238
+ tickers : List
239
+ List of stock tickers in the form of Reuters RIC codes
240
+ as strings.
241
+ ticker_name_dict : Dict
242
+ Dictionary mapping ticker to long name for each ticker.
243
+ mappings : Dict
244
+ Dictionary of sector mappings.
245
+
246
+ """
247
+
248
+ # Extract data from the Wikipedia SPX page
249
+ url = 'https://en.wikipedia.org/wiki/List_of_S%26P_500_companies'
250
+ req = requests.get(url, timeout=10)
251
+ html_doc = req.text
252
+ spx_list = pd.read_html(StringIO(html_doc))
253
+
254
+ # the first table on the page contains the stock data
255
+ spx_table = spx_list[0]
256
+
257
+ # create a list of the tickers from the 'Symbol' column
258
+ params['tickers'] = list(spx_table['Symbol'])
259
+
260
+ # create a dictionary mapping ticker to Security Name
261
+ params['ticker_name_dict'] = dict(
262
+ zip(spx_table['Symbol'], spx_table['Security']))
263
+
264
+ # Create a DataFrame from the default equity sectors dictionary
265
+ equity_sectors_df = pd.DataFrame.from_dict(
266
+ mappings['equity_sector_mappings'],
267
+ orient='index',
268
+ columns=['Sector',
269
+ 'Industry Group',
270
+ 'Industry'])
271
+
272
+ # Reset the index and rename as Sub-Industry column
273
+ equity_sectors_df = equity_sectors_df.reset_index()
274
+ equity_sectors_df = equity_sectors_df.rename(
275
+ columns={'index':'Sub-Industry'})
276
+
277
+ # Create a sector mappings DataFrame by joining the SPX table from
278
+ # Wikipedia to the Equity Sectors DataFrame
279
+ mappings['sector_mappings_df'] = spx_table.merge(
280
+ equity_sectors_df,
281
+ how='left',
282
+ left_on='GICS Sub-Industry',
283
+ right_on='Sub-Industry')
284
+
285
+ # Set the Index to the Ticker symbol
286
+ mappings['sector_mappings_df'] = mappings[
287
+ 'sector_mappings_df'].set_index('Symbol')
288
+
289
+ # Keep only the columns related to the sector levels
290
+ mappings['sector_mappings_df'] = mappings['sector_mappings_df'][
291
+ ['Sector', 'Industry Group', 'Industry', 'Sub-Industry',
292
+ 'Security']]
293
+
294
+ return params, mappings
295
+
296
+
297
+ @classmethod
298
+ def import_yahoo(
299
+ cls,
300
+ params: dict,
301
+ tables: dict) -> tuple[dict, dict]:
302
+ """
303
+ Return dictionary of price histories from Yahoo Finance.
304
+
305
+ Parameters
306
+ ----------
307
+ params : Dict
308
+ Dictionary of key parameters.
309
+ tables : Dict
310
+ Dictionary of key tables.
311
+
312
+ Returns
313
+ -------
314
+ tables : Dict
315
+ raw_ticker_dict : Dict
316
+ Dictionary of price history DataFrames, one for each
317
+ ticker.
318
+ params : Dict
319
+ exceptions : List
320
+ List of tickers that could not be returned.
321
+
322
+ """
323
+
324
+ # Create empty dictionary and list
325
+ tables['raw_ticker_dict'] = {}
326
+ params['exceptions'] = []
327
+
328
+ # Loop through the tickers
329
+ for sym in params['tickers'][:params['ticker_limit']]:
330
+
331
+ # Attempt to return the data for given ticker
332
+ try:
333
+ tables['raw_ticker_dict'][sym], params = cls._return_data(
334
+ ticker=sym, params=params)
335
+
336
+ # If error, try replacing '.' with '-' in ticker
337
+ except KeyError:
338
+ try:
339
+ sym_alt = sym.replace('.','-')
340
+ tables['raw_ticker_dict'][sym], params = cls._return_data(
341
+ ticker=sym_alt, params=params)
342
+
343
+ # If error, add to list of exceptions and move to next
344
+ # ticker
345
+ except KeyError:
346
+ print("Error with "+sym)
347
+ params['exceptions'].append(sym)
348
+ continue
349
+
350
+ return params, tables
351
+
352
+
353
+ @staticmethod
354
+ def _return_data(
355
+ ticker: str,
356
+ params: dict) -> tuple[pd.DataFrame, dict]:
357
+ """
358
+ Create DataFrame of historic prices for specified ticker.
359
+
360
+ Parameters
361
+ ----------
362
+ ticker : Str
363
+ Stock to be returned in the form of Reuters RIC code as a
364
+ string.
365
+ params : Dict
366
+ start_date : Str
367
+ Start Date represented as a string in the
368
+ format 'YYYY-MM-DD'.
369
+ end_date : Str
370
+ End Date represented as a string in the
371
+ format 'YYYY-MM-DD'.
372
+
373
+ Returns
374
+ -------
375
+ frame : DataFrame
376
+ DataFrame of historic prices for given ticker.
377
+ params : Dict
378
+ Dictionary of key parameters.
379
+
380
+ """
381
+
382
+ # Initialize a YahooFinancials object with the supplied ticker
383
+ # yahoo_financials = YahooFinancials(ticker)
384
+
385
+ # # Set frequency to daily
386
+ # freq='daily'
387
+
388
+ # # Extract historic prices
389
+ # frame = yahoo_financials.get_historical_price_data(
390
+ # params['start_date'], params['end_date'], freq)
391
+
392
+ # # Reformat columns
393
+ # frame = pd.DataFrame(frame[ticker]['prices']).drop(['date'], axis=1) \
394
+ # .rename(columns={'formatted_date':'Date',
395
+ # 'open': 'Open',
396
+ # 'high': 'High',
397
+ # 'low': 'Low',
398
+ # 'close': 'Close',
399
+ # 'volume': 'Volume'}) \
400
+ # .loc[:, ['Date','Open','High','Low','Close','Volume']] \
401
+ # .set_index('Date')
402
+
403
+ # Initialize a yFinance object with the supplied ticker
404
+ asset = yf.Ticker(ticker)
405
+
406
+ # Extract historic prices
407
+ frame = asset.history(start=params['start_date'], end=params['end_date'])
408
+
409
+ # Reformat columns
410
+ frame = frame.drop(['Dividends', 'Stock Splits'], axis=1)
411
+
412
+ # Set Index to Datetime
413
+ frame.index = frame.index.tz_localize(None)
414
+
415
+ # Set the proper length of DataFrame to help filter out missing data
416
+ params = MktUtils.window_set(frame=frame, params=params)
417
+
418
+ return frame, params
419
+
420
+
421
+ class MktUtils():
422
+ """
423
+ Various market data cleaning utilities
424
+
425
+ """
426
+ @staticmethod
427
+ def ticker_clean(
428
+ params: dict,
429
+ tables: dict) -> dict:
430
+ """
431
+ Remove tickers with incomplete history
432
+
433
+ Parameters
434
+ ----------
435
+ params : Dict
436
+ Dictionary of key parameters.
437
+ tables : Dict
438
+ Dictionary of key tables.
439
+
440
+ Returns
441
+ -------
442
+ tables : Dict
443
+ Dictionary of key tables.
444
+
445
+ """
446
+ # Create empty list of tickers to be removed
447
+ params['drop_list'] = []
448
+
449
+ # Loop through each DataFrame in raw ticker dict
450
+ for ticker, frame in tables['raw_ticker_dict'].items():
451
+
452
+ # If the DataFrame has less than 90% full history or has too many
453
+ # repeated values
454
+ if (len(frame) < (params['window'] * 0.9)
455
+ or frame['Close'].nunique() < (params['lookback'] / 15)):
456
+
457
+ # Add ticker to the drop list
458
+ params['drop_list'].append(ticker)
459
+
460
+ # For each ticker in the drop list
461
+ for ticker in params['drop_list']:
462
+
463
+ # Delete the ticker from the dictionary
464
+ del tables['raw_ticker_dict'][ticker]
465
+
466
+ return tables
467
+
468
+
469
+ @staticmethod
470
+ def window_set(
471
+ frame: pd.DataFrame,
472
+ params: dict) -> dict:
473
+ """
474
+ Set the correct length of the selected data
475
+
476
+ Parameters
477
+ ----------
478
+ frame : DataFrame
479
+ The historical prices.
480
+ params : Dict
481
+ start_date : Str
482
+ The chosen start date.
483
+
484
+ Returns
485
+ -------
486
+ params : Dict
487
+ Dictionary of key parameters.
488
+
489
+ """
490
+ # If the history window has not yet been set
491
+ if params['window'] is None:
492
+
493
+ # If the difference in start dates between the chosen start date
494
+ # and the first value in the index is less than 5 days
495
+ if ((pd.to_datetime(params['start_date'])
496
+ - frame.index[0]).days < 5):
497
+
498
+ # Set the window length to the length of the DataFrame
499
+ params['window'] = len(frame)
500
+
501
+ return params
502
+
503
+
504
+ @staticmethod
505
+ def date_set(params: dict) -> dict:
506
+ """
507
+ Create start and end dates if not supplied
508
+
509
+ Parameters
510
+ ----------
511
+ params : Dict
512
+ start_date : Str, optional
513
+ Date to begin backtest. Format is YYYY-MM-DD. The default is
514
+ 500 business days prior (circa 2 years).
515
+ end_date : Str, optional
516
+ Date to end backtest. Format is YYYY-MM-DD. The default is the
517
+ last business day.
518
+ lookback : Int, optional
519
+ Number of business days to use for the backtest. The default
520
+ is 500 business days (circa 2 years).
521
+
522
+ Returns
523
+ -------
524
+ params : Dict
525
+ Dictionary of key parameters.
526
+
527
+ """
528
+
529
+ # If end date is not supplied, set to previous working day
530
+ if params['end_date'] is None:
531
+ end_date_as_dt = (dt.datetime.today() - BDay(1)).date()
532
+ params['end_date'] = str(end_date_as_dt)
533
+
534
+ # If start date is not supplied, set to today minus lookback period
535
+ if params['start_date'] is None:
536
+ start_date_as_dt = (
537
+ dt.datetime.today()
538
+ - pd.Timedelta(days=params['lookback']*(365/250))).date()
539
+ params['start_date'] = str(start_date_as_dt)
540
+
541
+ return params