Skip to content

Commit d3704c8

Browse files
committed
Fix price-repair unit-switch sometimes applying twice
1 parent 0940e44 commit d3704c8

2 files changed

Lines changed: 34 additions & 7 deletions

File tree

‎doc/source/advanced/price_repair.rst‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ The new argument ``repair=True`` in ``history()`` and ``download()`` will attemp
66

77
The returned table will have a new column ``Repaired?`` that specifies if row was repaired.
88

9+
This can also repair currency mixups, so check ``Ticker.history_metadata['currency']`` after for true currency. Not ``Ticker.info``.
10+
911
Price repair
1012
============
1113

‎yfinance/scrapers/history.py‎

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,6 +1088,7 @@ def _standardise_currency(self, df, currency):
10881088
for c in _PRICE_COLNAMES_:
10891089
df[c] *= m
10901090
self._history_metadata["currency"] = currency2
1091+
self._history_metadata["currencyRepaired"] = True
10911092

10921093
f_div = df['Dividends']!=0.0
10931094
if f_div.any():
@@ -1323,7 +1324,7 @@ def _fix_unit_switch(self, df, interval, tz_exchange):
13231324
n = 1000
13241325
else:
13251326
n = 100
1326-
return self._fix_prices_sudden_change(df, interval, tz_exchange, n, correct_dividend=True)
1327+
return self._fix_prices_sudden_change(df, interval, tz_exchange, n, unit_switch=True, correct_dividend=True)
13271328

13281329
@utils.log_indent_decorator
13291330
def _fix_zeroes(self, df, interval, tz_exchange, prepost):
@@ -2656,7 +2657,7 @@ def _fix_bad_stock_splits(self, df, interval, tz_exchange):
26562657
return df
26572658

26582659
@utils.log_indent_decorator
2659-
def _fix_prices_sudden_change(self, df, interval, tz_exchange, change, correct_volume=False, correct_dividend=False):
2660+
def _fix_prices_sudden_change(self, df, interval, tz_exchange, change, unit_switch=False, correct_volume=False, correct_dividend=False):
26602661
if df.empty:
26612662
return df
26622663

@@ -2668,7 +2669,7 @@ def _fix_prices_sudden_change(self, df, interval, tz_exchange, change, correct_v
26682669
interday = interval in ['1d', '1wk', '1mo', '3mo']
26692670
multiday = interval in ['1wk', '1mo', '3mo']
26702671

2671-
if change in [100.0, 0.01]:
2672+
if unit_switch:
26722673
fix_type = '100x error'
26732674
log_extras['yf_cat'] = 'price-repair-100x'
26742675
start_min = None
@@ -2834,8 +2835,7 @@ def _fix_prices_sudden_change(self, df, interval, tz_exchange, change, correct_v
28342835
threshold = (split_max + 1.0 + largest_change_pct) * 0.5
28352836
logger.debug(f"threshold={threshold:.3f}, threshold_rcp={1.0/threshold:.3f}", extra=log_extras)
28362837

2837-
if 'Repaired?' not in df2.columns:
2838-
df2['Repaired?'] = False
2838+
sudden_change_repaired = np.full(len(df2), False)
28392839

28402840
if correct_columns_individually:
28412841
_1d_change_x = np.full((n, 4), 1.0)
@@ -3124,6 +3124,8 @@ def map_signals_to_ranges(f, f_up, f_down):
31243124

31253125
return ranges
31263126

3127+
any_m_lt_1 = False
3128+
31273129
if idx_latest_active is not None:
31283130
idx_rev_latest_active = df.shape[0] - 1 - idx_latest_active
31293131
logger.debug(f'idx_latest_active={idx_latest_active}, idx_rev_latest_active={idx_rev_latest_active}', extra=log_extras)
@@ -3211,6 +3213,7 @@ def map_signals_to_ranges(f, f_up, f_down):
32113213
else:
32123214
m = split_rcp
32133215
m_rcp = split
3216+
any_m_lt_1 = any_m_lt_1 or m < 0.99
32143217
if interday:
32153218
msg = f"Corrected {fix_type} on col={c} range=[{df2.index[r[1]-1].date()}:{df2.index[r[0]].date()}] m={m:.4f}"
32163219
else:
@@ -3245,7 +3248,7 @@ def map_signals_to_ranges(f, f_up, f_down):
32453248
if f_open_xor_closed_fixed.any():
32463249
df2.loc[f_open_xor_closed_fixed, "Volume"] = (df2.loc[f_open_xor_closed_fixed, "Volume"] * 0.5 * m_rcp).round().astype('int')
32473250

3248-
df2.loc[f_corrected, 'Repaired?'] = True
3251+
sudden_change_repaired[f_corrected] = True
32493252

32503253
else:
32513254
n_corrected = 0
@@ -3293,6 +3296,7 @@ def map_signals_to_ranges(f, f_up, f_down):
32933296
else:
32943297
m = split_rcp
32953298
m_rcp = split
3299+
any_m_lt_1 = any_m_lt_1 or m < 0.99
32963300
logger.debug(f"range={r} m={m}", extra=log_extras)
32973301
for c in ['Open', 'High', 'Low', 'Close', 'Adj Close']:
32983302
df2.iloc[r[0]:r[1], df2.columns.get_loc(c)] *= m
@@ -3301,7 +3305,7 @@ def map_signals_to_ranges(f, f_up, f_down):
33013305
if correct_volume:
33023306
col_loc = df2.columns.get_loc("Volume")
33033307
df2.iloc[r[0]:r[1], col_loc] = (df2.iloc[r[0]:r[1], col_loc] * m_rcp).round().astype('int')
3304-
df2.iloc[r[0]:r[1], df2.columns.get_loc('Repaired?')] = True
3308+
sudden_change_repaired[r[0]:r[1]] = True
33053309
if r[0] == r[1] - 1:
33063310
if interday:
33073311
msg = f"Corrected {fix_type} on interval {df2.index[r[0]].date()}"
@@ -3326,6 +3330,27 @@ def map_signals_to_ranges(f, f_up, f_down):
33263330
msg = f"Corrected: {n_corrected}x"
33273331
logger.info(msg, extra=log_extras)
33283332

3333+
if unit_switch and any_m_lt_1:
3334+
# m < 1 means thats the switch was repaired in favour of the major currency
3335+
# e.g. USD beat cents
3336+
# But check if _standardise_currency() already did that.
3337+
if 'currencyRepaired' in self._history_metadata and self._history_metadata['currencyRepaired']:
3338+
# Yes it did, which means this repair did it again.
3339+
# Revert the second.
3340+
m = change
3341+
m_rcp = 1.0/change
3342+
for c in ['Open', 'High', 'Low', 'Close', 'Adj Close']:
3343+
df2[c] *= m
3344+
if correct_dividend:
3345+
df2['Dividends'] *= m
3346+
if correct_volume:
3347+
df2['Volume'] = (df2['Volume'] * m_rcp).round().astype('int')
3348+
sudden_change_repaired = ~sudden_change_repaired
3349+
3350+
if 'Repaired?' not in df2.columns:
3351+
df2['Repaired?'] = False
3352+
df2['Repaired?'] = df2['Repaired?'].to_numpy() | sudden_change_repaired
3353+
33293354
if correct_volume:
33303355
f_na = df2['Volume'].isna()
33313356
if f_na.any():

0 commit comments

Comments
 (0)