
    _O`jL                     r   % S r SSKJr  SSKJr  SSKrSSKrSSKJ	r	     S+S\R                  S\S\S	\S
\\\      4
S jjrS\R                  S
\R                  4S jrSS0SS0S.r\\\\\4   4   \S'   S/r\\   \S'   S\S\\   S
\4S jrS,S\S\S\S
\4S jjr    S-S\S\S\S\S\S\S
\4S jjrS.S\4S jjrS r\\\\\4   4   r\SS!4S\R                  S"\\\4   S#\S-  S$\\S%4   S
\\R                  \4   4
S& jjr\S':X  a)  SSKrS(\R@                  ;  r!\!(       a  \"" S)5        \" \!S*9  gg)/uP  
Модуль очистки данных: обнаружение и удаление аномальных спайков в свечных данных.

Проблема:
    В таблицах SNGSP_H1 и PLZL_H1 обнаружены свечи-спайки:
    - Цена скачет на 1000-5000% за одну свечу (O=H=L=C, flat candle)
    - Через несколько свечей цена возвращается на исходный уровень
    - Такие же спайки на тех же timestamp'ах у обоих тикеров
    - Это искажает ATR(14) до 200-500%, делая данные непригодными для ML

Пример (SNGSP):
    2026.07.12 08:00 Close=2.35
    2026.07.12 09:00 Close=35.89  ← спайк (+1429%) 
    2026.07.12 10:00 Close=36.15  ← продолжение на ошибочном уровне
    2026.07.12 11:00 Close=2.36   ← возврат

Логика детекции:
    Свеча считается частью спайка если:
    - Предидущая свеча была в другом ценовом диапазоне (>2× или <0.5×)
    - И следующая свеча возвращается в исходный диапазон
    - Это создаёт паттерн: норма → выброс → возврат
    )datetime)OptionalN)get_connectiondf	price_colspike_thresholdvolume_thresholdreturnc                    X   R                  [        5      R                  nU S   R                  [        5      R                  n[        U5      nSn[        R
                  " U5      R                  USS9R                  5       R                  n/ n	Sn
Sn[        Xv5       H  n[        R                  " X   5      (       d  X   S:X  a  M*  XL   X   -  nX:  =(       d    USU-  :  nU(       a  U
(       d  Sn
UnMZ  U(       a  Mc  U
(       d  Ml  UbC  XL   X   -  nXKS	-
     nXL   nS
Us=::  a  S::  a"  O  OX-
  nUS::  a  U	R                  XS	-
  /5        Sn
SnM     U	$ )u  
Обнаруживает цепочки свечей-спайков.

Args:
    df: DataFrame с колонками Date, Time, Open, High, Low, Close, Volume
    price_col: колонка цены для анализа
    spike_threshold: порог аномалии (во сколько раз цена отличается от окружения)
    volume_threshold: порог объёма для flat-детекции

Returns:
    list[list[int]] — список диапазонов [start_idx, end_idx] для удаления
Volume   F)windowcenterNr         ?T   g      ?g       @   )astypefloatvalueslenpdSeriesrollingmedianrangenpisnanappend)r   r   r   r	   closevolumenr   rolling_medianspike_rangesin_spikespike_startiratio
is_anomalyspike_ratiopre_spike_closepost_spike_close	chain_lens                      data/cleaner.pydetect_spike_chainsr.      s]   $ M  '..E\  '..FE
A FYYu%--VE-JQQSZZNLHK688N%&&.*;q*@>,, ,Mo8M0M
hHK&#h)::"'a"8#(8  +,, !I B$++[a%,@AHK9 >     c                 H   [        U 5      nU(       d  U $ / nU H"  u  p4UR                  [        X4S-   5      5        M$     [        [	        U5      5      n[        S[        U5       S[        U5       S35        U H  u  p4[        SU SU SU R                  U   S    S	U R                  U   S
    SU R                  U   S    S	U R                  U   S
    SU R                  U   S   S SU R                  U   S   S S35        M     U R                  US9R                  SS9$ )uu   
Удаляет спайк-цепи из DataFrame.

Returns:
    DataFrame без аномальных свечей
r   u!     Найдено спайков: u    цепочек (u    свечей)z    [:z] Date Timeu    — z (Close.2fu    → ... → ))indexT)drop)
r.   extendr   sortedsetprintr   ilocr9   reset_index)r   r#   indices_to_dropstartends        r-   clean_dataframerC   _   sG    'r*L	 O"
uU!G45 # S12O	-c,.?-@@QRUVeRfQggu
vw"
eWAcU"RWWU^F%;$<AbggenV>T=UUZf%&aV(<'= >''%.)#.mBGGCL<QRU;VVWY 	Z #
 777)5545@@r/   	min_priceg      @g     @@)SNGSPPLZLPRICE_THRESHOLDSVTBRPRICE_THRESHOLD_EXCEPTIONStable
timestampsc                    [        5        nUR                  5       n SnSn[        S[        U5      U5       HO  nXXd-    nSR	                  S/[        U5      -  5      nSU  SU S3n	UR                  X5        XSR                  -  nMQ     UUR                  5         sSSS5        $ ! UR                  5         f = f! , (       d  f       g= f)	u>   Массовое удаление свечей по timestamp.d   r   ,z%szDELETE FROM `z` WHERE `timestamp` IN (r7   N)r   cursorr   r   joinexecuterowcountr   )
rJ   rK   connrO   
batch_sizedeletedr&   batchplaceholdersquerys
             r-   _delete_from_dbrY      s    		T	JG1c*oz:"Q^4"xxU(;<'w.F|nTUVu,??* ; LLN 
	 LLN 
	s#   B:A.B%B:%B77B::
Cticker	timeframedry_runc                    SSK JnJn  U" X5      n[        SS 35        [        SU 35        [        S 5        U" XSS9nS H  n[        R
                  " Xg   S	S
9Xg'   M     [        S[        U5       35        [        5       nU [        ;   a  [        U    n	[        R                  " S5      n
[        R                  " US   SS	S9US'   US   U	S   :  US   U
:  -  nUR                  5       nUS:  aG  [        SU	S    SU S35        UR                  US4    H  nUR                  [        U5      5        M     UR                  S/SS9  [!        U5      nU HF  u  nn[#        UUS-   5       H-  nUR                  [        UR$                  U   S   5      5        M/     MH     U(       d  [        S5        g['        U5      n[        S[        U5       S35        XfS   R)                  U5         R+                  S5      n[        U5      S:  aa  [        S5        UR-                  S5      R/                  5        H3  u  nn[        SUS    SUS     S!US   S" S#[        US$   5       35        M5     U(       a  [        S%5        [        U5      $  [1        UU5      n[        S&U S'U 35        U$ ! [2         a  n[        S(U 35        e SnAff = f))u  
Очищает данные в MySQL: удаляет аномальные свечи из таблицы {TICKER}_{TF}.

Стратегия:
    1. Ценовой фильтр — свечи за пределами исторического диапазона (PRICE_THRESHOLDS)
    2. Spike chain детекция — цепочки аномальных свечей с возвратом

Args:
    ticker: тикер (напр. 'SNGSP')
    timeframe: таймфрейм
    dry_run: если True — только показывает, что будет удалено

Returns:
    количество удалённых свечей
r   load_dataframe_validate_table_name
<============================================================u     ОЧИСТКА: N)limitOpenHighLowr5   r   coerceerrors     Всего свечей: z
2026-06-18r2   z%Y.%m.%d)formatrj   _date_parsedr5   rD   u      Ценовой фильтр (<u    с 2026.06.18):     свечей	timestampT)columnsinplacer   *     ✅ Аномалий не найденоu   
  Будет удалено: u$   
  Примеры (первые 10):
       r3   r4   z: Close=r6   z Vol=r   uy   
  ⏸️  DRY RUN — ничего не удалено. Запустите с dry_run=False для применения.u   
  ✅ Удалено u    свечей из u   
  ❌ Ошибка: )data.loaderr_   r`   r=   r   
to_numericr   r<   rG   	Timestampto_datetimesumlocaddintr9   r.   r   r>   r;   isinsort_valuesheaditerrowsrY   	Exception)rZ   r[   r\   r_   r`   rJ   r   coltimestamps_to_delete
thresholdsanomaly_startmaskn_pricetsr#   rA   rB   idxtimestamps_listsample_s	n_deletedes                           r-   clean_ticker_in_dbr      s     A 3E	Bxj/	ug
&'	XJ 
	6B9--9 : 
'By
12%(U !!%f-
 \2^^BvJzRZ[>7j55"^:LP]:]^((*Q;4Z5L4MM^_f^ggtuvffT;./$((R1 0
($7 'r*L"
sa(C $$Sk)B%CD ) #  :<12O	+C,@+A
OP ;$$_56BB6JF
6{Q57KKO,,.DAqD61QvYKx'
37GuSQRS[Q\M]L^_` /   K  	M?###E?;	'	{2EeWMN &qc*+s   
J) )
K3KK
max_return	level_low
level_highc                 h
   SSK JnJn  U" X5      n[        SS 35        [        SU 35        [        S 5        U" XSSS9n	U	b  [	        U	5      S	:  a  [        S
5        gS H  n
[
        R                  " X   SS9X'   M     [	        U	5      n[        SU 35        U	S   R                  R                  [        5      n[	        U5      n[        R                  " U5      n[        R                  " USS USS -  S-
  5      USS& X:  nX)    nUR                  5       (       aN  [	        U5      S	:  a?  [        R                  " U5      nXU-  :  XU-  :  -  nUR                  5       (       a  UU-  nUR                  5       (       d  [        S5        g[        R                  " U5      S   n[	        U5      n[        SU S35        [        S5        USS	  HT  nU	R                   U   n[        SUS    SUS    SUS   S SUS   S S US!   S S"US   S S#[#        US$   5       35        MV     US	:  a  [        S%US	-
   35        U(       a  [        S&5        U$ 0 nS' GH  n
X   R                  R                  [        5      R%                  5       nU H  nUS-
  nUS:  a!  UU   (       a  US-  nUS:  a  UU   (       a  M  US-   nUU:  a!  UU   (       a  US-  nUU:  a  UU   (       a  M  US:  a,  UU:  a&  UU:  a   UU-
  nUU   UU   UU   -
  UU-
  -  U-  -   nOUS:  a  UU   nOUU:  a  UU   nOM  [#        U	R                   U   S(   5      nUU;  a  0 UU'   ['        US)5      UU   U
'   M     GM     U	S$   R                  R                  [        5      R%                  5       nU Hw  nUS:  a  UUS-
     OSnUS-   U:  a  UUS-      OSn US:  d  U S:  a  UU -   S*-  OUU   n![#        U	R                   U   S(   5      nUU;  a  0 UU'   ['        U!S5      UU   S$'   My     [)        5        n"U"R+                  5       n#Sn$UR-                  5        H  u  nn%/ n&/ n'U%R-                  5        H,  u  n
n(U&R/                  S+U
 S,35        U'R/                  U(5        M.     U'R/                  U5        S-U S.S/R1                  U&5       S03n)U#R3                  U)U'5        U$U#R4                  -  n$M     U"R7                  5         U#R9                  5         SSS5        [        S1W$ S2U 35        U$$ ! , (       d  f       N!= f)3ul  Интерполирует битые свечи прямо в MySQL (UPDATE, а не DELETE).

В отличие от clean_ticker_in_db() (который удаляет строки), эта функция
заменяет O/H/L/C/V аномальных свечей на интерполированные значения,
сохраняя структуру временного ряда без гэпов.

Два прохода детекции (как в data/loader.py clean_spikes):
  Pass 1 — смежные возвраты > max_return
  Pass 2 — абсолютный уровень цены < level_low×median или > level_high×median

Args:
    ticker: тикер (SNGSP, PLZL, ...)
    timeframe: таймфрейм
    max_return: порог аномального возврата (90%)
    level_low: нижний порог уровня от медианы (30%)
    level_high: верхний порог уровня от медианы (300%)
    dry_run: если True — только показать, без UPDATE

Returns:
    количество интерполированных свечей
r   r^   ra   rb   u     ИНТЕРПОЛЯЦИЯ: NF)rc   cleanrs   u+     ❌ Недостаточно данныхrd   rh   ri   rk   r5   r   r   rr   u#     Найдено аномалий: rn   u   
  Примеры:rt   r2   r3   r4   z: O=re   r6   z H=rf   z L=rg   z C=z V=r   u       ... и ещё u{   
  ⏸️  DRY RUN — ничего не изменено. Запустите с dry_run=False для применения.)re   rf   rg   r5   ro         `z` = %szUPDATE `z` SET z, z WHERE `timestamp` = %su&   
  ✅ Интерполировано u    свечей в )ru   r_   r`   r=   r   r   rv   r   r   r   r   zerosabsanyr   wherer>   r|   copyroundr   rO   itemsr   rP   rQ   rR   commitr   )*rZ   r[   r   r   r   r\   r_   r`   rJ   r   r   n_totalr   r!   returnsr   clean_closemedian_close
level_mask	spike_idxn_spikesr   rowupdatesvalsprev_idxnext_idxspannew_valr   volprev_vnext_vavg_vrS   rO   updatedcols	set_partsparamsvalrX   s*                                             r-   interpolate_ticker_in_dbr      si   2 A 3E	Bxj/	(
01	XJ	U	CB	zSWr\;=9--9 : "gG	'y
12wK%%e,EE
A hhqkG&&qrU3BZ/#56GABKD ,Kxxzzc+&+yy-,665P\C\;\]
>>*$D88:::<q!I9~H	/z
GH 
!"~ggclS[M3v;- 0v;s#3s6{3&7s3u:c:J Kw<$CCM(:';= 	> 
 "}"8b=/23  M  	O G/w~~$$U+002CQwHa-DNA a-DNN QwHQ,4>A Q,4>> 1}A(X2E(*x.DNT(^,KPSV^P^+_bf+ffQx.Ax.RWWS\+./B  $Wa0GBK-  08 X,


$
$U
+
0
0
2C!$qS1Wa!$q1S1W!*01*
&A%Sk*+WGBK %eQH  
	THBIF JJLS  1SE1c" ) MM"ugVDIIi,@+AAXYENN5&)v&G ( 	! 
$ 
3G9<MeW
UVN' 
	s   -CT##
T1c                     SSK Jn  SnU H  n [        X0S9nX$-  nM     [	        SS 35        [	        U (       a  S	U S
3OSU S35        [	        S 5        g! [         a  n[	        SU SU 35         SnAMm  SnAff = f)u<   Очищает все MOEX тикеры от спайков.r   )MOEX_TICKERSr\   u     ❌ z: Nra   rb   u     ИТОГО: u!    свечей к удалениюu     ИТОГО: удалено rn   )configr   r   r   r=   )r\   r   totalrZ   r!   r   s         r-   clean_all_tickersr     s    #E	*"6;AJE  
Bxj/	wN5'!B
CVstysz  {H  UI  J	XJ  	*F6("QC())	*s   A
A=!A88A=)g{Gz?gGz?)ro   r2   r4   re   rf   rg   r5   r   percentilesbounds	skip_cols.c                    U R                  5       n U R                  [        R                  /S9R                  nU Vs/ s H  oUU;  d  M
  UPM     nn0 nU(       d  X4$ U H~  nX   R                  5       n	[        U	5      S:  a  M&  Ub  X;   a  X(   u  pO-U	R                  US   5      n
U	R                  US   5      nX4Xx'   X:  d  Mi  X   R                  X5      X'   M     X4$ s  snf )u  
Winsorization engineered-фич: клиппинг экстремальных значений к заданным процентилям.

Args:
    df: DataFrame с фичами
    percentiles: (нижний, верхний) процентиль для клиппинга
    bounds: если переданы — использует ГОТОВЫЕ границы (для inference/val).
            Если None — вычисляет на основе переданного df (для train).
    skip_cols: колонки, которые НЕ нужно winsorить (сырые OHLCV + время)

Returns:
    (DataFrame с заwinsorенными фичами, dict границ {col: (lo, hi)})
)includerM   r   r   )	r   select_dtypesr   numberrp   dropnar   quantileclip)r   r   r   r   numeric_colsctarget_colscomputed_boundsr   r   lohis               r-   winsorize_featuresr     s    & 
B##RYYK#8@@L*Aly.@1lKA$&O""w~~t9s?#-[FB {1~.B{1~.B$&8O 7gll2*BG " 1 Bs   	C%
C%__main__z--applyuO     🏁 DRY RUN режим. Для применения добавьте --applyr   )r5         @g?)H1T)r   g?g333333?r   T)T)#__doc__r   typingr   numpyr   pandasr   db.connectionr   	DataFramestrr   listr|   r.   rC   rG   dict__annotations__rI   rY   boolr   r   r   FEATURE_WINSOR_PERCENTILEStupleWinsorBoundsr   __name__sysargvr\   r=    r/   r-   <module>r      s5  ,     (
  !	=
== = 	=
 
$s)_=@A A A> 35!1 $sDe,,-  ) DI 
3 DI # $Os Os OD OTW Od <@37LO/3PS PS P+0P*/PDIP )-P 9<Pft * * Cue|,,-
 (B"&!h	-
-ue|$- 4- S#X	-
 2<<%&-` zsxx'G_`g& r/   