@@ -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