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,637 @@
1
+ """
2
+ Prepare data for charts and graphs
3
+
4
+ """
5
+
6
+ import copy
7
+ import math
8
+ import pandas as pd
9
+
10
+ class Formatting():
11
+ """
12
+ Various data formatting methods for graphing
13
+
14
+ """
15
+ @staticmethod
16
+ def get_chart_title(params: dict) -> str:
17
+ """
18
+ Create title label for market chart
19
+
20
+ Parameters
21
+ ----------
22
+ norm : Bool
23
+ Whether the prices have been normalised.
24
+ trend : Str
25
+ The type / direction of the trend.
26
+ days : Int
27
+ The number of days price history.
28
+
29
+ Returns
30
+ -------
31
+ charttitle : Str
32
+ The chart title label.
33
+
34
+ """
35
+ norm = params['norm']
36
+ trend = params['trend']
37
+ days = params['days']
38
+
39
+ # Update chart title based on whether the data is normalized
40
+ # and the chosen trend type to display
41
+ if norm:
42
+ if trend == 'up':
43
+ chart_title = ("Up Trending Markets"
44
+ +" - Relative Return Over Last "
45
+ +str(days)+' Trading Days')
46
+
47
+ if trend == 'down':
48
+ chart_title = ("Down Trending Markets"
49
+ +" - Relative Return Over Last "
50
+ +str(days)+' Trading Days')
51
+
52
+ if trend == 'strong':
53
+ chart_title = ("Most Strongly Trending Markets"
54
+ +" - Relative Return Over Last "
55
+ +str(days)+' Trading Days')
56
+
57
+ if trend == 'neutral':
58
+ chart_title = ("Neutral Trending Markets"
59
+ +" - Relative Return Over Last "
60
+ +str(days)+' Trading Days')
61
+
62
+ if trend == 'all':
63
+ chart_title = ("Most Strongly and Neutral Trending"
64
+ +" Markets - Price Over Last "
65
+ +str(days)+' Trading Days')
66
+
67
+ else:
68
+ if trend == 'up':
69
+ chart_title = ("Up Trending Markets"
70
+ +" - Price Over Last "
71
+ +str(days)+' Trading Days')
72
+
73
+ if trend == 'down':
74
+ chart_title = ("Down Trending Markets"
75
+ +" - Price Over Last "
76
+ +str(days)+' Trading Days')
77
+
78
+ if trend == 'strong':
79
+ chart_title = ("Most Strongly Trending Markets"
80
+ +" - Price Over Last "
81
+ +str(days)+' Trading Days')
82
+
83
+ if trend == 'neutral':
84
+ chart_title = ("Neutral Trending Markets"
85
+ +" - Price Over Last "
86
+ +str(days)+' Trading Days')
87
+
88
+ if trend == 'all':
89
+ chart_title = ("Most Strongly and Neutral Trending"
90
+ +" Markets - Price Over Last "
91
+ +str(days)+' Trading Days')
92
+
93
+ chart_title = str(chart_title)
94
+
95
+ chart_title = chart_title+' - '+params['end_date']
96
+
97
+ return chart_title
98
+
99
+
100
+ @staticmethod
101
+ def create_mkt_dims(params: dict) -> dict:
102
+ """
103
+ Create a tuple giving the height and width of the market chart
104
+
105
+ Parameters
106
+ ----------
107
+ mkts : Int
108
+ Number of markets to chart.
109
+
110
+ Returns
111
+ -------
112
+ chart_dimensions : Tuple
113
+ Tuple of height, width for market chart.
114
+
115
+ """
116
+ mkts = params['chart_mkts']
117
+
118
+ if mkts % 5 == 0:
119
+ width = 5
120
+
121
+ elif mkts % 4 == 0:
122
+ width = 4
123
+
124
+ elif mkts < 20 and mkts % 3 == 0:
125
+ width = 3
126
+
127
+ elif (mkts+1) % 5 == 0:
128
+ width = 5
129
+
130
+ elif (mkts+1) % 4 == 0:
131
+ width = 4
132
+
133
+ elif mkts < 20 and (mkts+1) % 3 == 0:
134
+ width = 3
135
+
136
+ else:
137
+ width = 5
138
+
139
+ height = math.ceil(mkts/width)
140
+ params['chart_dimensions'] = (height, width)
141
+
142
+ return params
143
+
144
+
145
+ @classmethod
146
+ def create_normalized_data(
147
+ cls,
148
+ params: dict,
149
+ tables: dict,
150
+ flag: str) -> pd.DataFrame:
151
+ """
152
+ Create a subset of chart_prep dataset normalized to start from
153
+ 100 for the specified history window
154
+
155
+ Parameters
156
+ ----------
157
+ params : Dict
158
+ mkts : Int
159
+ Number of markets to chart. The default is 5.
160
+ trend : Str
161
+ Flag to select most or least trending markets.
162
+ Select from: 'up' - strongly trending upwards,
163
+ 'down - strongly trending downwards,
164
+ 'neutral' - weak trend,
165
+ 'strong' - up and down trends,
166
+ 'all' - up down and weak trends
167
+ The default is 'strong' which displays both up-trending
168
+ and down-trending markets.
169
+ days : Int
170
+ Number of days of history. The default is 60.
171
+ tables : Dict
172
+ Dictionary of key tables.
173
+
174
+ Returns
175
+ -------
176
+ tenor : DataFrame
177
+ DataFrame of closing prices, normalized for a given historic
178
+ window
179
+
180
+ """
181
+
182
+ chart_data = cls._create_chart_data(
183
+ params=params,
184
+ tables=tables,
185
+ flag=flag
186
+ )
187
+
188
+ # Copy the selected number of days history from the input
189
+ # DataFrame
190
+ tenor = copy.deepcopy(chart_data[-params['days']:])
191
+
192
+ # Normalize the closing price of each ticker to start from 100
193
+ # at the beginning of the history window
194
+ for ticker in tenor.columns:
195
+ tenor[ticker] = tenor[ticker].div(tenor[ticker].iloc[0]).mul(100)
196
+
197
+ return tenor
198
+
199
+
200
+ @classmethod
201
+ def _create_chart_data(
202
+ cls,
203
+ params: dict,
204
+ tables: dict,
205
+ flag: str) -> pd.DataFrame:
206
+ """
207
+ Create a time series of closing prices for selected markets.
208
+
209
+ Parameters
210
+ ----------
211
+ params : Dict
212
+ mkts : Int
213
+ Number of markets to chart. The default is 5.
214
+ trend : Str
215
+ Flag to select most or least trending markets.
216
+ Select from: 'up' - strongly trending upwards,
217
+ 'down - strongly trending downwards,
218
+ 'neutral' - weak trend,
219
+ 'strong' - up and down trends,
220
+ 'all' - up down and weak trends
221
+ The default is 'strong' which displays both up-trending
222
+ and down-trending markets.
223
+ tables : Dict
224
+ Dictionary of key tables.
225
+
226
+ Returns
227
+ -------
228
+ chart_data : DataFrame
229
+ DataFrame of closing prices of tickers selected by trend
230
+ strength.
231
+
232
+ """
233
+ if flag == 'Unfiltered':
234
+ table = tables['barometer']
235
+ elif flag == 'High Returns':
236
+ table = tables['return_barometer']
237
+ else:
238
+ table = tables['sectors'][flag]
239
+
240
+ data_list = cls.create_data_list(
241
+ params,
242
+ table,
243
+ market_chart=False,
244
+ num_charts=None
245
+ )
246
+
247
+ # Create a new DataFrame
248
+ chart_data = pd.DataFrame()
249
+
250
+ # For each ticker in the list of selected tickers, add the
251
+ # column of closing prices to new DataFrame
252
+ for ticker in data_list:
253
+ chart_data[ticker] = tables['ticker_dict'][ticker]['Close']
254
+
255
+ # Rename columns from tickers to short names and forward fill
256
+ # any NaN cells
257
+ chart_data = chart_data.rename(
258
+ columns=params['ticker_short_name_dict'])
259
+ chart_data = chart_data.ffill()
260
+
261
+ return chart_data
262
+
263
+
264
+ @classmethod
265
+ def create_data_list(
266
+ cls,
267
+ params: dict,
268
+ barometer: pd.DataFrame,
269
+ market_chart: bool,
270
+ num_charts: int | None) -> list:
271
+ """
272
+ Create a list of the most / least trending markets.
273
+
274
+ Parameters
275
+ ----------
276
+ params : Dict
277
+ mkts : Int
278
+ Number of markets to chart. The default is 5.
279
+ trend : Str
280
+ Flag to select most or least trending markets.
281
+ Select from: 'up' - strongly trending upwards,
282
+ 'down - strongly trending downwards,
283
+ 'neutral' - weak trend,
284
+ 'strong' - up and down trends,
285
+ 'all' - up down and weak trends
286
+ The default is 'strong' which displays both up-trending
287
+ and down-trending markets.
288
+ barometer : DataFrame
289
+ DataFrame showing trend strength for each ticker.
290
+ market_chart : Bool
291
+ Whether the data is used for the marketchart graph. The
292
+ default is False
293
+ num_charts : Int
294
+ The number of sub plots in the market chart. The default is 40.
295
+
296
+ Returns
297
+ -------
298
+ data_list : List
299
+ List of markets to be charted.
300
+
301
+ """
302
+ trend = params['trend']
303
+ mkts = params['mkts']
304
+
305
+ # if trend flag is 'up', select tickers of most up trending
306
+ # markets
307
+ if trend == 'up':
308
+ data_list = cls._up_trend(
309
+ barometer=barometer, market_chart=market_chart,
310
+ num_charts=num_charts, mkts=mkts)
311
+
312
+ # if trend flag is 'down', select tickers of most down trending
313
+ # markets
314
+ elif trend == 'down':
315
+ data_list = cls._down_trend(
316
+ barometer=barometer, market_chart=market_chart,
317
+ num_charts=num_charts, mkts=mkts)
318
+
319
+ # if trend flag is 'neutral', select tickers of least trending
320
+ # markets
321
+ elif trend == 'neutral':
322
+ data_list = cls._neutral_trend(
323
+ barometer=barometer, market_chart=market_chart,
324
+ num_charts=num_charts, mkts=mkts)
325
+
326
+ # if trend flag is 'strong', select tickers of most down trending
327
+ # markets
328
+ elif trend == 'strong':
329
+ data_list = cls._strong_trend(
330
+ barometer=barometer, market_chart=market_chart,
331
+ num_charts=num_charts, mkts=mkts)
332
+
333
+ # Otherwise select all 3
334
+ else:
335
+ data_list = cls._mixed_trend(
336
+ barometer=barometer, market_chart=market_chart,
337
+ num_charts=num_charts, mkts=mkts)
338
+
339
+ return data_list
340
+
341
+
342
+ @staticmethod
343
+ def _up_trend(
344
+ barometer: pd.DataFrame,
345
+ market_chart: bool,
346
+ num_charts: int | None,
347
+ mkts: int) -> list:
348
+
349
+ # Sort by Trend Strength
350
+ barometer = barometer.sort_values(
351
+ by=['Trend Strength'], ascending=False)
352
+
353
+ if market_chart:
354
+ # Select the specified number of highest values
355
+ data_list = list(barometer['Ticker'].iloc[:num_charts])
356
+
357
+ else:
358
+ # Select the highest values
359
+ data_list = list(barometer['Ticker'].iloc[:mkts])
360
+
361
+ return data_list
362
+
363
+
364
+ @staticmethod
365
+ def _down_trend(
366
+ barometer: pd.DataFrame,
367
+ market_chart: bool,
368
+ num_charts: int | None,
369
+ mkts: int) -> list:
370
+
371
+ # Sort by Trend Strength
372
+ barometer = barometer.sort_values(
373
+ by=['Trend Strength'], ascending=False)
374
+
375
+ if (market_chart and num_charts !=None) :
376
+ # Select the specified number of lowest values
377
+ data_list = list(barometer['Ticker'].iloc[-num_charts:])
378
+
379
+ else:
380
+ # Select the lowest values
381
+ data_list = list(barometer['Ticker'].iloc[-mkts:])
382
+
383
+ return data_list
384
+
385
+
386
+ @staticmethod
387
+ def _neutral_trend(
388
+ barometer: pd.DataFrame,
389
+ market_chart: bool,
390
+ num_charts: int | None,
391
+ mkts: int) -> list:
392
+
393
+ # Sort by Absolute Trend Strength
394
+ barometer = barometer.sort_values(
395
+ by=['Absolute Trend Strength'], ascending=False)
396
+
397
+ if (market_chart and num_charts !=None):
398
+ # Select the specified number of lowest values
399
+ data_list = list(barometer['Ticker'].iloc[-num_charts:])
400
+
401
+ else:
402
+ # Select the lowest values
403
+ data_list = list(barometer['Ticker'].iloc[-mkts:])
404
+
405
+ return data_list
406
+
407
+
408
+ @staticmethod
409
+ def _strong_trend(
410
+ barometer: pd.DataFrame,
411
+ market_chart: bool,
412
+ num_charts: int | None,
413
+ mkts: int) -> list:
414
+
415
+ # Sort by Trend Strength
416
+ barometer = barometer.sort_values(
417
+ by=['Trend Strength'], ascending=False)
418
+
419
+ if (market_chart and num_charts !=None):
420
+ # Select the specified number of highest values
421
+ top = list(barometer['Ticker'].iloc[:int(num_charts/2)])
422
+
423
+ # Select the lowest values
424
+ bottom = list(barometer['Ticker'].iloc[
425
+ -(num_charts-int(num_charts/2)):])
426
+
427
+ else:
428
+ # Select the highest values
429
+ top = list(barometer['Ticker'].iloc[:int(mkts/2)])
430
+
431
+ # Select the lowest values
432
+ bottom = list(barometer['Ticker'].iloc[
433
+ -(mkts-int(mkts/2)):])
434
+
435
+ # Combine this data
436
+ data_list = top + bottom
437
+
438
+ return data_list
439
+
440
+
441
+ @staticmethod
442
+ def _mixed_trend(
443
+ barometer: pd.DataFrame,
444
+ market_chart: bool,
445
+ num_charts: int | None,
446
+ mkts: int) -> list:
447
+
448
+ # Sort by Trend Strength
449
+ barometer = barometer.sort_values(
450
+ by=['Trend Strength'], ascending=False)
451
+
452
+ if (market_chart and num_charts !=None):
453
+ # Select 1/3 of the specified number of highest values
454
+ top = list(barometer['Ticker'].iloc[:int(num_charts/3)])
455
+
456
+ # Select 1/3 of the specified number of lowest values
457
+ bottom = list(barometer['Ticker'].iloc[-int(num_charts/3):])
458
+
459
+ # Sort by Absolute Trend Strength
460
+ barometer = barometer.sort_values(
461
+ by=['Absolute Trend Strength'], ascending=False)
462
+
463
+ # Select 1/3 of the specified number of lowest values
464
+ neutral = list(barometer['Ticker'].iloc[
465
+ -(num_charts-2*int(num_charts/3)):])
466
+
467
+ else:
468
+ # Select the highest values
469
+ top = list(barometer['Ticker'].iloc[:int(mkts/3)])
470
+
471
+ # Select the lowest values
472
+ bottom = list(barometer['Ticker'].iloc[-int(mkts/3):])
473
+
474
+ # Sort by Absolute Trend Strength
475
+ barometer = barometer.sort_values(
476
+ by=['Absolute Trend Strength'], ascending=False)
477
+
478
+ # Select the lowest values
479
+ neutral = list(barometer['Ticker'].iloc[
480
+ -(mkts-2*int(mkts/3)):])
481
+
482
+ # Combine this data
483
+ data_list = top + bottom + neutral
484
+
485
+ return data_list
486
+
487
+
488
+ @classmethod
489
+ def summary_config(
490
+ cls,
491
+ params: dict,
492
+ barometer: pd.DataFrame) -> tuple[dict, pd.DataFrame]:
493
+ """
494
+ Configure inputs for Trend Summary plots
495
+
496
+ Parameters
497
+ ----------
498
+ params : Dict
499
+ sector_level / returns_sector_level : Int, optional
500
+ The level of granularity of the assets.
501
+ For Commodities the choices are:
502
+ 1:'Asset Class',
503
+ 2:'Broad Sector',
504
+ 3:'Mid Sector',
505
+ 4:'Narrow Sector',
506
+ 5:'Underlying'. The default is 2:'Broad Sector'.
507
+ For Equities the choices are:
508
+ 1:'Sector',
509
+ 2:'Industry Group',
510
+ 3:'Industry',
511
+ 4:'Sub-Industry',
512
+ 5:'Security'
513
+
514
+ absolute : Bool, optional
515
+ Whether to show absolute trend strength (from 0 - 100%) or show
516
+ positive and negative trends seperately
517
+
518
+ data_types : Str or List
519
+ Data types to use. Choose from 'c': continuous futures,
520
+ 'r':ratios, 's':spot cash commodities, 'i':indices, 'y':yields.
521
+ The default is ['c', 's']
522
+ barometer : DataFrame
523
+ DataFrame showing trend strength for each ticker.
524
+
525
+ Returns
526
+ -------
527
+ params : Dict
528
+ sector_name : Str
529
+ The sector level used to summarize the data.
530
+ marker_size : Int
531
+ The size of the markers in the strip plot.
532
+ trend_type : Str
533
+ Absolute or relavive trend strength.
534
+ chart_barometer : DataFrame
535
+ Data source used to produce charts.
536
+
537
+ """
538
+ # Set sector names and marker size when using Norgate futures data
539
+ if params['asset_type'] == 'CTA':
540
+ params['sector_name'] = params[
541
+ 'commodity_sector_levels'][params['sector_level']-1]
542
+ params['returns_sector_name'] = params[
543
+ 'commodity_sector_levels'][params['returns_sector_level']-1]
544
+ params['marker_size'] = 10
545
+
546
+ # 'all' is used to select all of the Norgate data types
547
+ if params['data_types'] == 'all':
548
+ chart_barometer = barometer
549
+
550
+ # If the type is a string, remove all the other types
551
+ elif isinstance(params['data_types'], str):
552
+ chart_barometer = barometer
553
+ for tick in list(params['graph_ticker_types'].values()):
554
+ if tick[0] != params['data_types']:
555
+ chart_barometer.drop(
556
+ index=chart_barometer[
557
+ chart_barometer['Ticker'].str.contains(
558
+ tick)].index, inplace=True)
559
+
560
+ # If the result is a list, remove all the types not in the list
561
+ elif isinstance(params['data_types'], list):
562
+ chart_barometer = barometer
563
+ for tick in list(params['graph_ticker_types'].values()):
564
+ if tick[0] not in params['data_types']:
565
+ chart_barometer.drop(
566
+ index=chart_barometer[
567
+ chart_barometer['Ticker'].str.contains(
568
+ tick)].index, inplace=True)
569
+
570
+ # Print an error message if an incorrect type is supplied
571
+ else:
572
+ print('Enter a valid ticker type')
573
+
574
+ # Otherwise for Yahoo SPX data
575
+ else:
576
+ params['sector_name'] = params[
577
+ 'equity_sector_levels'][params['sector_level']-1]
578
+ params['returns_sector_name'] = params[
579
+ 'equity_sector_levels'][params['returns_sector_level']-1]
580
+ params['marker_size'] = 10
581
+ chart_barometer = barometer
582
+
583
+ # Set label for Absolute trend strength ranging from 0% to 100%
584
+ if params['absolute']:
585
+ params['trend_type'] = 'Absolute Trend Strength %'
586
+ params['axis_range'] = [-0.1,1]
587
+
588
+ # Or splitting uptrends and downtrends ranging from -100% to 100%
589
+ else:
590
+ params['trend_type'] = 'Trend Strength %'
591
+ params['axis_range'] = [-1,1]
592
+
593
+ trend_sector_group = chart_barometer[
594
+ [params['trend_type'], params['sector_name']]]
595
+
596
+ # Set the maximum height of the chart
597
+ params['plot_height'] = cls._set_height(
598
+ params=params, chart_barometer=chart_barometer,
599
+ trend_sector_group=trend_sector_group)
600
+
601
+ # Rank the sector split by average of the trend_type
602
+ sect_data = trend_sector_group.groupby([params['sector_name']]).mean()
603
+ params['sector_list'] = list(sect_data.sort_values(
604
+ params['trend_type'], ascending=False).index)
605
+
606
+ # Sort chart barometer by trend type
607
+ chart_barometer = chart_barometer.sort_values(
608
+ by=[params['trend_type']], ascending=True)
609
+
610
+ return params, chart_barometer
611
+
612
+
613
+ @staticmethod
614
+ def _set_height(
615
+ params: dict,
616
+ chart_barometer: pd.DataFrame,
617
+ trend_sector_group: pd.DataFrame) -> float:
618
+
619
+ max_bucket = chart_barometer[params['trend_type']].value_counts().max()
620
+
621
+ max_bucket_per_sector = trend_sector_group.groupby(
622
+ trend_sector_group.columns.tolist()).size().max()
623
+
624
+ num_sectors = len(chart_barometer[params['sector_name']].unique())
625
+
626
+ if params['summary_type'] == 'strip':
627
+ if num_sectors > 200:
628
+ plot_height = max_bucket_per_sector * num_sectors / 5
629
+ else:
630
+ plot_height = max_bucket_per_sector * num_sectors / 20
631
+ else:
632
+ if params['dodge']:
633
+ plot_height = max_bucket_per_sector * num_sectors / 8
634
+ else:
635
+ plot_height = max_bucket / 4
636
+
637
+ return plot_height