johnkim commited on
Commit
4ba615c
·
1 Parent(s): c2f6566

현재 상태 문구 수정

Browse files
Files changed (3) hide show
  1. README.md +2 -2
  2. app.py +3 -7
  3. buy_engine_dipfix.py +24 -28
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: 전량매도 최후보루 모니터
3
  emoji: 📉
4
  colorFrom: red
5
  colorTo: gray
@@ -8,7 +8,7 @@ app_file: app.py
8
  pinned: false
9
  ---
10
 
11
- # 전량매도 최후보루 모니터
12
 
13
  보유종목 코드를 넣으면 최근 매도신호 여부를 알려줍니다.
14
 
 
1
  ---
2
+ title: WTW Outlook
3
  emoji: 📉
4
  colorFrom: red
5
  colorTo: gray
 
8
  pinned: false
9
  ---
10
 
11
+ # WTW Outlook
12
 
13
  보유종목 코드를 넣으면 최근 매도신호 여부를 알려줍니다.
14
 
app.py CHANGED
@@ -436,7 +436,6 @@ def run_status(text):
436
  be.run(df, "1day")
437
  status = getattr(be, "LAST_STATUS", "") or "(상태 없음)"
438
  kind = getattr(be, "LAST_STATUS_KIND", "wait")
439
- short = getattr(be, "LAST_STATUS_SHORT", "") or "현상태"
440
  except Exception as e:
441
  out.append(f"- {_label(code)}: 분석 실패 ({e})"); continue
442
  last_date = str(df.index[-1])[:10]
@@ -444,23 +443,20 @@ def run_status(text):
444
  # 색상 일관성(최후보루와 동일): 🟢초록=좋음(매수 국면), ⚠️주황=주의(무너지는 중)
445
  if kind == "buy":
446
  line = f'<span style="color:#16a34a;font-weight:700">🟢 {status}</span>'
447
- lbl = f'<span style="color:#16a34a;font-weight:700">🟢 {short}</span>'
448
  elif kind == "danger":
449
  line = f'<span style="color:#ea580c;font-weight:700">⚠️ {status}</span>'
450
- lbl = f'<span style="color:#ea580c;font-weight:700">⚠️ {short}</span>'
451
  else:
452
  line = status
453
- lbl = short
454
  out.append(f"### 📈 {_label(code)} ({market_of(code)})\n"
455
- f"- **[일봉 — {lbl}]** {line}\n"
456
  f"- 기준: {last_date} 종가 {last_px:,.0f}")
457
  return "\n\n".join(out) if out else "결과 없음"
458
 
459
 
460
  # UI (탭 순서: 최후보루 → 볼린저 → 삼성·ETF(RS) → 지금 상태)
461
  # ====================================================================
462
- with gr.Blocks(title="주식 디버거") as demo:
463
- gr.Markdown("# 📊 주식 디버거")
464
 
465
  with gr.Tab("전량매도 최후보루"):
466
  gr.Markdown("보유종목 **코드 또는 종목명**을 넣으면 매도신호/현재단계를 알려줘. (시장 자동판별, 여러 종목 가능)")
 
436
  be.run(df, "1day")
437
  status = getattr(be, "LAST_STATUS", "") or "(상태 없음)"
438
  kind = getattr(be, "LAST_STATUS_KIND", "wait")
 
439
  except Exception as e:
440
  out.append(f"- {_label(code)}: 분석 실패 ({e})"); continue
441
  last_date = str(df.index[-1])[:10]
 
443
  # 색상 일관성(최후보루와 동일): 🟢초록=좋음(매수 국면), ⚠️주황=주의(무너지는 중)
444
  if kind == "buy":
445
  line = f'<span style="color:#16a34a;font-weight:700">🟢 {status}</span>'
 
446
  elif kind == "danger":
447
  line = f'<span style="color:#ea580c;font-weight:700">⚠️ {status}</span>'
 
448
  else:
449
  line = status
 
450
  out.append(f"### 📈 {_label(code)} ({market_of(code)})\n"
451
+ f"- **[일봉]** {line}\n"
452
  f"- 기준: {last_date} 종가 {last_px:,.0f}")
453
  return "\n\n".join(out) if out else "결과 없음"
454
 
455
 
456
  # UI (탭 순서: 최후보루 → 볼린저 → 삼성·ETF(RS) → 지금 상태)
457
  # ====================================================================
458
+ with gr.Blocks(title="WTW Outlook") as demo:
459
+ gr.Markdown("# 📊 WTW Outlook")
460
 
461
  with gr.Tab("전량매도 최후보루"):
462
  gr.Markdown("보유종목 **코드 또는 종목명**을 넣으면 매도신호/현재단계를 알려줘. (시장 자동판별, 여러 종목 가능)")
buy_engine_dipfix.py CHANGED
@@ -198,6 +198,7 @@ def _daily_peaks(df):
198
  LAST_STATUS = '' # be.run 후 '지금 상태' 한 줄 요약 (full_debug/app이 읽음)
199
  LAST_STATUS_KIND = 'wait' # 상태 종류(색칠용): buy/danger/dip/done/wait
200
  LAST_STATUS_SHORT = '' # 짧은 상태명(라벨용): '산봉우리 그리는 중' 등
 
201
 
202
 
203
  def run(df, tf='5min', sf=None, sell_dates=None, vcp_wins=None, block_ranges=None):
@@ -328,7 +329,7 @@ def run(df, tf='5min', sf=None, sell_dates=None, vcp_wins=None, block_ranges=Non
328
  vlife=None; vseg_i=len(buys); vbreak_dt=None
329
  mtn_s60 = m60; mtn_sdt = d
330
  life = min(bl, life if life else bl)
331
- phase='PEAK'; peak_gap=gap; peak_m60=m60
332
  endlow_gap=0.0; endlow_ma60=HI; endlow_px=HI
333
  hold=False; wave=0; armed=False; b240={}; flag240up=False
334
  daily_open=False; pend=[]; up_peak=0.0; up_days=0
@@ -354,7 +355,7 @@ def run(df, tf='5min', sf=None, sell_dates=None, vcp_wins=None, block_ranges=Non
354
  base_bbmin=HI # 새 동산 시작 → 베이스 추적 리셋
355
  mtn_s60=start_ma60; mtn_sdt=start_dt # 이 동산의 시작점 고정
356
  life_broken=False
357
- phase='PEAK'; peak_gap=gap; peak_m60=m60
358
  endlow_gap=0.0; endlow_ma60=HI; endlow_px=HI
359
  ev.append((d,'산봉우리시작',f'동산 시작점 {str(mtn_sdt)[:10]} (60선 {mtn_s60:,.0f}) / 생명선 {life:,.0f}'))
360
 
@@ -393,6 +394,7 @@ def run(df, tf='5min', sf=None, sell_dates=None, vcp_wins=None, block_ranges=Non
393
  life = min(endlow_bb60, endlow_bbpx)*(1-LIFELINE_MARGIN)
394
  life_broken=False
395
  mtn_count=1 # ★ 새 시퀀스의 첫 동산 → 위아래 매수 시작
 
396
  wave_fresh=True # 동산 완성 직후 복귀 → 모자 체크 대상
397
  one_wave=False # 새 동산 → 1·2번째 다 매수
398
  wave_confirmed=False; conf_logged=False; touch_logged=False; cross_up_dt=None; wave_hat=0.0 # 복귀확인 리셋
@@ -624,7 +626,7 @@ def run(df, tf='5min', sf=None, sell_dates=None, vcp_wins=None, block_ranges=Non
624
  f'깊이는 {dip_gap*100:.1f}%로 얕아도 이렇게 오래 끌면 눌림 아니라 무너짐')
625
  ev.append((d,_tag0,
626
  f'속았음 → 처음부터 다시 — {_rsn0} / 이번 봉우리는 직전 산보다 {(_pg0*100 if _pg0 is not None else 0):+.1f}%({_why0}) / 240선 복귀 안 기다리고 위아래 접음 / 새 시작점 {str(dip_dt)[:10]}(60선 {dip_ma60:,.0f}) / 새 생명선 {life:,.0f}'))
627
- phase='PEAK'; peak_gap=gap; peak_m60=m60
628
  endlow_gap=0.0; endlow_ma60=HI; endlow_px=HI
629
  hold=False; wave=0; armed=False; b240={}; flag240up=False
630
  daily_open=False; pend=[]; up_peak=0.0; up_days=0
@@ -690,7 +692,7 @@ def run(df, tf='5min', sf=None, sell_dates=None, vcp_wins=None, block_ranges=Non
690
  life = min(dip_bb60, dip_bbpx)*(1-LIFELINE_MARGIN)
691
  ev.append((d,'깊은눌림→새동산(재베이스)',
692
  f'한 바퀴 다 돌고 무너짐 → 처음부터 다시 — 봉우리가 직전 산보다 +{_pg*100:.1f}% 크게 올랐다가(기준 +{DOUBLE_MTN_GAP*100:.0f}% 넘음) 60선이 240선 밑으로 {dip_gap*100:.1f}% 깊이 빠짐(기준 -{emin*100:.0f}% 이하) / 새 시작점 {str(dip_dt)[:10]}(60선 {dip_ma60:,.0f}) / 새 생명선 {life:,.0f}'))
693
- phase='PEAK'; peak_gap=gap; peak_m60=m60
694
  endlow_gap=0.0; endlow_ma60=HI; endlow_px=HI
695
  hold=False; wave=0; armed=False; b240={}; flag240up=False
696
  daily_open=False; pend=[]; up_peak=0.0; up_days=0
@@ -708,7 +710,7 @@ def run(df, tf='5min', sf=None, sell_dates=None, vcp_wins=None, block_ranges=Non
708
  else ('끝지점 낮아짐' if not _end_ok else '피크데이터 없음'))
709
  ev.append((d,'속음→재베이스(깊은붕괴)',
710
  f'속았음 → 처음부터 다시 — 60선이 240선 밑으로 {dip_gap*100:.1f}% 깊이 빠짐(기준 -{REBASE_DEEP_PCT*100:.0f}% 이하) + 봉우리가 직전 산보다 {(_pg*100 if _pg is not None else 0):+.1f}%({_whyR}). 위아래 아님 / 새 시작점 {str(dip_dt)[:10]}(60선 {dip_ma60:,.0f}) / 새 생명선 {life:,.0f}'))
711
- phase='PEAK'; peak_gap=gap; peak_m60=m60
712
  endlow_gap=0.0; endlow_ma60=HI; endlow_px=HI
713
  hold=False; wave=0; armed=False; b240={}; flag240up=False
714
  daily_open=False; pend=[]; up_peak=0.0; up_days=0
@@ -765,7 +767,7 @@ def run(df, tf='5min', sf=None, sell_dates=None, vcp_wins=None, block_ranges=Non
765
  # ★ 마지막 봉 기준 '지금 상태' 한 줄 요약 (full_debug/app이 be.LAST_STATUS로 읽음). 반환값은 안 바꿈.
766
  # LAST_STATUS_KIND: 색칠용 종류 — 'buy'(매수국면,빨강) / 'danger'(무너지는중,주황) / 'dip'(눌림판정)
767
  # / 'done'(매수끝) / 'wait'(관망)
768
- global LAST_STATUS, LAST_STATUS_KIND, LAST_STATUS_SHORT
769
  try:
770
  _g = f"{gap*100:+.1f}%" if 'gap' in dir() else ''
771
  _gap_txt = f"60선이 240선보다 {_g}" if _g else ''
@@ -773,62 +775,56 @@ def run(df, tf='5min', sf=None, sell_dates=None, vcp_wins=None, block_ranges=Non
773
  _mtn_nm = {1:'첫 산', 2:'더블산(2번째)', 3:'트리플산(3번째)'}.get(mtn_count, f'{mtn_count}번째 산')
774
  if phase=='WAIT':
775
  LAST_STATUS_SHORT = '관망'
776
- LAST_STATUS = "관망 — 아직 산 시작 안 함 (60선이 240선 아래에서 바닥 다지는 중)"
777
  LAST_STATUS_KIND = 'wait'
778
  elif phase=='PEAK':
 
779
  if life_broken:
780
  LAST_STATUS_SHORT = '무너지는 중'
781
- LAST_STATUS = (f"무너지는 중 — 생명선 {life:,.0f} 아래로 내려감(손절됨). "
782
- f"산 실패로 보고 있고, 60선이 시작보다 높게 끝나면 그때 다시 성공 인정")
783
  LAST_STATUS_KIND = 'danger'
784
  elif above:
785
- # 60선이 240선 위 = 산봉우리 올라가는 중
786
  LAST_STATUS_SHORT = f'{str(mtn_sdt)[:10]} 시작하는 동산 그리는 중'
787
- LAST_STATUS = (f"산봉우리 그리는 중 — 60선이 240선 위로 올라가는 중. "
788
- f"시작 바닥 {str(mtn_sdt)[:10]}(60선 {mtn_s60:,.0f}), 봉우리 최고 +{peak_gap*100:.1f}% / 생명선 {life:,.0f}")
789
  LAST_STATUS_KIND = 'wait'
790
  else:
791
- # 60선이 240선 아래로 내려옴 = 끝지점 가는 중
792
  LAST_STATUS_SHORT = f'{str(mtn_sdt)[:10]} 시작하는 동산 후 끝지점 가는 중'
793
- LAST_STATUS = (f"끝지점 가는 중 — 60선이 240선 아래로 내려옴(이 산이 성공인지 판정 대기). "
794
- f"시작 바닥 {str(mtn_sdt)[:10]}(60선 {mtn_s60:,.0f}), 봉우리 최고 +{peak_gap*100:.1f}% / 생명선 {life:,.0f}")
795
  LAST_STATUS_KIND = 'wait'
796
  elif phase=='DIP':
797
  _durd = (idx[-1]-dip_start_dt).days if dip_start_dt is not None else 0
798
  LAST_STATUS_SHORT = '눌림 판정(매수멈춤)'
799
- LAST_STATUS = (f"매수 멈춤(눌림 판정 대기) — {str(dip_start_dt)[:10]}부터 {_durd}일째, "
800
- f"지금 60선이 240선보다 {dip_gap*100:.1f}% (더블동산인지 무너진 건지 지켜보는 중)")
801
  LAST_STATUS_KIND = 'dip'
802
  elif phase=='WAVE':
803
  if flag240up:
804
  LAST_STATUS_SHORT = '매수 끝(쉬는 중)'
805
- LAST_STATUS = f"위아래 매수 끝(2번째까지 다 삼) — 다시 240선 아래로 꺾일 때까지 쉬는 중 ({_gap_txt})"
806
  LAST_STATUS_KIND = 'done'
807
  elif daily_open:
808
- if up_days:
809
- _ud = f"지금은 60선 위로 튐({up_days}봉째) — 잠깐 매수 쉬는 중"
810
- else:
811
- _ud = "지금 60선 밑 — 매수 중"
812
  LAST_STATUS_SHORT = '매수 국면'
813
- LAST_STATUS = f"매수 국면 — {_mtn_nm}에서 {wave}번째 눌림 매수 중. {_ud} ({_gap_txt})"
814
  LAST_STATUS_KIND = 'buy'
815
  elif wave==0:
816
  if wave_confirmed:
817
  LAST_STATUS_SHORT = '매수 국면(대기)'
818
- LAST_STATUS = f"매수 국면 — {_mtn_nm} 위아래 열림, 1번째 눌림 대기(복귀 확인됨, 60선 밑으로 오면 매수) ({_gap_txt})"
819
  LAST_STATUS_KIND = 'buy'
820
  else:
821
  LAST_STATUS_SHORT = '복귀 확인 대기'
822
- LAST_STATUS = f"복귀 확인 대기 — {_mtn_nm} 위아래 열렸지만 가짜(모자)일 수 있어 아직 매수 전 ({_gap_txt})"
823
  LAST_STATUS_KIND = 'wait'
824
  else:
825
  LAST_STATUS_SHORT = '위 구간 대기'
826
- LAST_STATUS = f"{_mtn_nm} · {wave}번째 눌림 후 위 구간 대기 중 ({_gap_txt})"
827
  LAST_STATUS_KIND = 'wait'
828
  else:
829
- LAST_STATUS_SHORT = phase; LAST_STATUS = f"상태: {phase}"; LAST_STATUS_KIND = 'wait'
 
830
  except Exception as _e:
831
- LAST_STATUS = f"(상태 요약 실패: {_e})"; LAST_STATUS_KIND = 'wait'
832
  return ev,buys,mts
833
 
834
  if __name__=='__main__':
 
198
  LAST_STATUS = '' # be.run 후 '지금 상태' 한 줄 요약 (full_debug/app이 읽음)
199
  LAST_STATUS_KIND = 'wait' # 상태 종류(색칠용): buy/danger/dip/done/wait
200
  LAST_STATUS_SHORT = '' # 짧은 상태명(라벨용): '산봉우리 그리는 중' 등
201
+ LAST_STATUS_DETAIL = '' # 상태 상세(뒷부분): '시작 바닥 ... / 생명선 ...'
202
 
203
 
204
  def run(df, tf='5min', sf=None, sell_dates=None, vcp_wins=None, block_ranges=None):
 
329
  vlife=None; vseg_i=len(buys); vbreak_dt=None
330
  mtn_s60 = m60; mtn_sdt = d
331
  life = min(bl, life if life else bl)
332
+ phase='PEAK'; peak_gap=gap; peak_m60=m60; vlife=None; vbreak_dt=None; vseg_i=len(buys) # ★새 산 그리기 시작 → 옛 가상생명선 폐기
333
  endlow_gap=0.0; endlow_ma60=HI; endlow_px=HI
334
  hold=False; wave=0; armed=False; b240={}; flag240up=False
335
  daily_open=False; pend=[]; up_peak=0.0; up_days=0
 
355
  base_bbmin=HI # 새 동산 시작 → 베이스 추적 리셋
356
  mtn_s60=start_ma60; mtn_sdt=start_dt # 이 동산의 시작점 고정
357
  life_broken=False
358
+ phase='PEAK'; peak_gap=gap; peak_m60=m60; vlife=None; vbreak_dt=None; vseg_i=len(buys) # ★새 산 그리기 시작 → 옛 가상생명선 폐기
359
  endlow_gap=0.0; endlow_ma60=HI; endlow_px=HI
360
  ev.append((d,'산봉우리시작',f'동산 시작점 {str(mtn_sdt)[:10]} (60선 {mtn_s60:,.0f}) / 생명선 {life:,.0f}'))
361
 
 
394
  life = min(endlow_bb60, endlow_bbpx)*(1-LIFELINE_MARGIN)
395
  life_broken=False
396
  mtn_count=1 # ★ 새 시퀀스의 첫 동산 → 위아래 매수 시작
397
+ vlife=None; vbreak_dt=None; vseg_i=len(buys) # ★새 동산성공 = 새 시퀀스 → 옛 가상생명선 폐기
398
  wave_fresh=True # 동산 완성 직후 복귀 → 모자 체크 대상
399
  one_wave=False # 새 동산 → 1·2번째 다 매수
400
  wave_confirmed=False; conf_logged=False; touch_logged=False; cross_up_dt=None; wave_hat=0.0 # 복귀확인 리셋
 
626
  f'깊이는 {dip_gap*100:.1f}%로 얕아도 이렇게 오래 끌면 눌림 아니라 무너짐')
627
  ev.append((d,_tag0,
628
  f'속았음 → 처음부터 다시 — {_rsn0} / 이번 봉우리는 직전 산보다 {(_pg0*100 if _pg0 is not None else 0):+.1f}%({_why0}) / 240선 복귀 안 기다리고 위아래 접음 / 새 시작점 {str(dip_dt)[:10]}(60선 {dip_ma60:,.0f}) / 새 생명선 {life:,.0f}'))
629
+ phase='PEAK'; peak_gap=gap; peak_m60=m60; vlife=None; vbreak_dt=None; vseg_i=len(buys) # ★새 산 그리기 시작 → 옛 가상생명선 폐기
630
  endlow_gap=0.0; endlow_ma60=HI; endlow_px=HI
631
  hold=False; wave=0; armed=False; b240={}; flag240up=False
632
  daily_open=False; pend=[]; up_peak=0.0; up_days=0
 
692
  life = min(dip_bb60, dip_bbpx)*(1-LIFELINE_MARGIN)
693
  ev.append((d,'깊은눌림→새동산(재베이스)',
694
  f'한 바퀴 다 돌고 무너짐 → 처음부터 다시 — 봉우리가 직전 산보다 +{_pg*100:.1f}% 크게 올랐다가(기준 +{DOUBLE_MTN_GAP*100:.0f}% 넘음) 60선이 240선 밑으로 {dip_gap*100:.1f}% 깊이 빠짐(기준 -{emin*100:.0f}% 이하) / 새 시작점 {str(dip_dt)[:10]}(60선 {dip_ma60:,.0f}) / 새 생명선 {life:,.0f}'))
695
+ phase='PEAK'; peak_gap=gap; peak_m60=m60; vlife=None; vbreak_dt=None; vseg_i=len(buys) # ★새 산 그리기 시작 → 옛 가상생명선 폐기
696
  endlow_gap=0.0; endlow_ma60=HI; endlow_px=HI
697
  hold=False; wave=0; armed=False; b240={}; flag240up=False
698
  daily_open=False; pend=[]; up_peak=0.0; up_days=0
 
710
  else ('끝지점 낮아짐' if not _end_ok else '피크데이터 없음'))
711
  ev.append((d,'속음→재베이스(깊은붕괴)',
712
  f'속았음 → 처음부터 다시 — 60선이 240선 밑으로 {dip_gap*100:.1f}% 깊이 빠짐(기준 -{REBASE_DEEP_PCT*100:.0f}% 이하) + 봉우리가 직전 산보다 {(_pg*100 if _pg is not None else 0):+.1f}%({_whyR}). 위아래 아님 / 새 시작점 {str(dip_dt)[:10]}(60선 {dip_ma60:,.0f}) / 새 생명선 {life:,.0f}'))
713
+ phase='PEAK'; peak_gap=gap; peak_m60=m60; vlife=None; vbreak_dt=None; vseg_i=len(buys) # ★새 산 그리기 시작 → 옛 가상생명선 폐기
714
  endlow_gap=0.0; endlow_ma60=HI; endlow_px=HI
715
  hold=False; wave=0; armed=False; b240={}; flag240up=False
716
  daily_open=False; pend=[]; up_peak=0.0; up_days=0
 
767
  # ★ 마지막 봉 기준 '지금 상태' 한 줄 요약 (full_debug/app이 be.LAST_STATUS로 읽음). 반환값은 안 바꿈.
768
  # LAST_STATUS_KIND: 색칠용 종류 — 'buy'(매수국면,빨강) / 'danger'(무너지는중,주황) / 'dip'(눌림판정)
769
  # / 'done'(매수끝) / 'wait'(관망)
770
+ global LAST_STATUS, LAST_STATUS_KIND, LAST_STATUS_SHORT, LAST_STATUS_DETAIL
771
  try:
772
  _g = f"{gap*100:+.1f}%" if 'gap' in dir() else ''
773
  _gap_txt = f"60선이 240선보다 {_g}" if _g else ''
 
775
  _mtn_nm = {1:'첫 산', 2:'더블산(2번째)', 3:'트리플산(3번째)'}.get(mtn_count, f'{mtn_count}번째 산')
776
  if phase=='WAIT':
777
  LAST_STATUS_SHORT = '관망'
778
+ LAST_STATUS_DETAIL = '아직 산 시작 안 함 (60선이 240선 아래에서 바닥 다지는 중)'
779
  LAST_STATUS_KIND = 'wait'
780
  elif phase=='PEAK':
781
+ _pk_detail = f"시작 바닥 {str(mtn_sdt)[:10]}(60선 {mtn_s60:,.0f}), 봉우리 최고 +{peak_gap*100:.1f}% / 생명선 {life:,.0f}"
782
  if life_broken:
783
  LAST_STATUS_SHORT = '무너지는 중'
784
+ LAST_STATUS_DETAIL = f"생명선 {life:,.0f} 아래로 내려감(손절됨). 60선이 시작보다 높게 끝나면 그때 다시 성공 인정"
 
785
  LAST_STATUS_KIND = 'danger'
786
  elif above:
 
787
  LAST_STATUS_SHORT = f'{str(mtn_sdt)[:10]} 시작하는 동산 그리는 중'
788
+ LAST_STATUS_DETAIL = _pk_detail
 
789
  LAST_STATUS_KIND = 'wait'
790
  else:
 
791
  LAST_STATUS_SHORT = f'{str(mtn_sdt)[:10]} 시작하는 동산 후 끝지점 가는 중'
792
+ LAST_STATUS_DETAIL = _pk_detail
 
793
  LAST_STATUS_KIND = 'wait'
794
  elif phase=='DIP':
795
  _durd = (idx[-1]-dip_start_dt).days if dip_start_dt is not None else 0
796
  LAST_STATUS_SHORT = '눌림 판정(매수멈춤)'
797
+ LAST_STATUS_DETAIL = (f"{str(dip_start_dt)[:10]}부터 {_durd}일째, 지금 60선이 240선보다 {dip_gap*100:.1f}% "
798
+ f"(더블동산인지 무너진 건지 지켜보는 중)")
799
  LAST_STATUS_KIND = 'dip'
800
  elif phase=='WAVE':
801
  if flag240up:
802
  LAST_STATUS_SHORT = '매수 끝(쉬는 중)'
803
+ LAST_STATUS_DETAIL = f"2번째까지 다 삼 — 다시 240선 아래로 꺾일 때까지 쉬는 중 ({_gap_txt})"
804
  LAST_STATUS_KIND = 'done'
805
  elif daily_open:
806
+ _ud = f"지금은 60선 위로 튐({up_days}봉째) — 잠깐 매수 쉬는 중" if up_days else "지금 60선 밑 — 매수 중"
 
 
 
807
  LAST_STATUS_SHORT = '매수 국면'
808
+ LAST_STATUS_DETAIL = f"{_mtn_nm}에서 {wave}번째 눌림 매수 중. {_ud} ({_gap_txt})"
809
  LAST_STATUS_KIND = 'buy'
810
  elif wave==0:
811
  if wave_confirmed:
812
  LAST_STATUS_SHORT = '매수 국면(대기)'
813
+ LAST_STATUS_DETAIL = f"{_mtn_nm} 위아래 열림, 1번째 눌림 대기(복귀 확인됨, 60선 밑으로 오면 매수) ({_gap_txt})"
814
  LAST_STATUS_KIND = 'buy'
815
  else:
816
  LAST_STATUS_SHORT = '복귀 확인 대기'
817
+ LAST_STATUS_DETAIL = f"{_mtn_nm} 위아래 열렸지만 가짜(모자)일 수 있어 아직 매수 전 ({_gap_txt})"
818
  LAST_STATUS_KIND = 'wait'
819
  else:
820
  LAST_STATUS_SHORT = '위 구간 대기'
821
+ LAST_STATUS_DETAIL = f"{_mtn_nm} · {wave}번째 눌림 후 위 구간 대기 중 ({_gap_txt})"
822
  LAST_STATUS_KIND = 'wait'
823
  else:
824
+ LAST_STATUS_SHORT = phase; LAST_STATUS_DETAIL = ''; LAST_STATUS_KIND = 'wait'
825
+ LAST_STATUS = f"{LAST_STATUS_SHORT} — {LAST_STATUS_DETAIL}" if LAST_STATUS_DETAIL else LAST_STATUS_SHORT
826
  except Exception as _e:
827
+ LAST_STATUS = f"(상태 요약 실패: {_e})"; LAST_STATUS_KIND = 'wait'; LAST_STATUS_SHORT=''; LAST_STATUS_DETAIL=''
828
  return ev,buys,mts
829
 
830
  if __name__=='__main__':