QQuery / buy_engine_dipfix.py
johnkim
ENDLOW_MIN_PCT = {'5min':0.010,'15min':0.010,'1hour':0.02,'1day':0.07} # ๋์ง€์  ์ตœ์†Œ(60์„ ์ด 240์„  ์•„๋ž˜๋กœ, 60์„  ๊ธฐ์ค€). ์œผ ํ™•์ •
1b4271b
Raw
History Blame Contribute Delete
76 kB
# -*- coding: utf-8 -*-
"""
๋งค์ˆ˜ ๋กœ์ง ์—”์ง„ (๋ถ„๋ด‰) - ๋™์‚ฐ/๋์ง€์ /์œ„์•„๋ž˜/์ƒ๋ช…์„  [v4: 60์„ ์ตœ์ €์  ์•ต์ปค]
CSV(datetime,Open,High,Low,Close,Volume) ๊ฒฝ๋กœ๋งŒ ์ธ์ž๋กœ.
[๋™์‚ฐ ์„ฑ๊ณต ์กฐ๊ฑด]
(1) 60์„ ์ด 240์„  ์•„๋ž˜ โ†’ ๊ทธ ๊ตฌ๊ฐ„ '60์„  ์ตœ์ €์ ' = ์‹œ์ž‘์  (์ƒ๋ช…์„ /๋น„๊ต ๊ธฐ์ค€)
โ€ป 60์„  ์ตœ์ €์ ์— ์•ต์ปค๋ง (๊ฐ€๊ฒฉ ๊ธ‰๋ฝ๋ด‰ ์•„๋‹˜ โ†’ BBํ•˜๋‹จ ํ›„ํ–‰ ๋ฒ„๊ทธ ๋ฐฉ์ง€)
(2) 60์„ ์ด 240์„  ์œ„๋กœ ์˜ฌ๋ผ๊ฐ (์‚ฐ๋ด‰์šฐ๋ฆฌ)
โ€ป ์‚ฐ๋ด‰์šฐ๋ฆฌ ์ถฉ๋ถ„ํžˆ ์ปค์•ผ: 60์„ ์ด 240์„  ์œ„๋กœ PEAK_MIN_PCT ์ด์ƒ (๋…ธ์ด์ฆˆ ํ•„ํ„ฐ)
(3) 60์„ ์ด ๋‹ค์‹œ 240์„  ์•„๋ž˜๋กœ ๋‚ด๋ ค๊ฐ€ ๋์ง€์ 
โ€ป ๋์ง€์ ๋„ ์ถฉ๋ถ„ํžˆ ๊นŠ์–ด์•ผ: 60์„ ์ด 240์„  ์•„๋ž˜๋กœ ENDLOW_MIN_PCT ์ด์ƒ (๋…ธ์ด์ฆˆ ํ•„ํ„ฐ)
(4) ๋์ง€์ ์˜ 60์„ ์ด ์‹œ์ž‘์  60์„ ๋ณด๋‹ค '๋†’์•„์•ผ' ๋™์‚ฐ ์„ฑ๊ณต (๋‚ฎ์œผ๋ฉด ์‹คํŒจ)
(5) 60์„ ์ด ๋์ง€์ ์—์„œ ๋‹ค์‹œ 240์„  ์œ„๋กœ ์˜ฌ๋ผ๊ฐ
(6) ์ด๋•Œ๋ถ€ํ„ฐ ์œ„์•„๋ž˜ ๋งค์ˆ˜ (60>240์—์„œ 60์„  ๋ˆŒ๋ฆด ๋•Œ)
- 1๋ฒˆ์งธ ์œ„์•„๋ž˜ ร—2, 2๋ฒˆ์งธ ร—1
- 3ยท4ยท5๋ฒˆ์งธ... = '240์˜ฌ' (์ถ”๋งค ์•ˆ ํ•จ, ๋‹ค์‹œ 240์„  ์•„๋ž˜๋กœ ๊บพ์ผ ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ)
โ€ป ์‚ฐ๋ด‰์šฐ๋ฆฌ/๋์ง€์  ์ž‘์œผ๋ฉด(๋…ธ์ด์ฆˆ) ๋ฌด์‹œํ•˜๊ณ  ๊ณ„์† ๊ด€์ฐฐ
์ƒ๋ช…์„  = ์‹œ์ž‘์ (60์„ ์ตœ์ €์ ) ๋ณผ๋ฆฐ์ € ํ•˜๋‹จ ร— (1 - LIFELINE_MARGIN). ๋™์‚ฐ ๋‚ด๋‚ด ๊ณ ์ •.
240๊ทผ์ฒ˜ ์ถ”๊ฐ€๋งค์ˆ˜ = ์ €๊ฐ€๊ฐ€ 240์„  ์‹ค์ œ๋กœ ๋‹ฟ์„ ๋•Œ (์ €๊ฐ€ โ‰ค 240์„ )
์‹คํ–‰: python buy_engine.py 007340_5min.csv 5min
"""
import sys
import pandas as pd, numpy as np
# ===== ์กฐ์ • ํŒŒ๋ผ๋ฏธํ„ฐ =====
MA_60, MA_240 = 60, 240
BB_WIN, BB_STD = 20, 2
LIFELINE_BASE_MIN = True # [์ƒ๋ช…์„ ] True๋ฉด '๋ฒ ์ด์Šค ๊ตฌ๊ฐ„ ์ „์ฒด์˜ ์ตœ์ € BBํ•˜๋‹จ'์„ ์ƒ๋ช…์„  ์•ต์ปค๋กœ ์‚ฌ์šฉ(B์•ˆ).
# ๊ธฐ์กด(False)์€ 60์„ ์ตœ์ €์ผยท๊ฐ€๊ฒฉ์ตœ์ €์ผ '๊ทธ๋‚ ์˜' BBํ•˜๋‹จ๋งŒ ๋ด์„œ, ๋ฒ ์ด์Šค ์ค‘๊ฐ„์˜
# ๋‚ ์นด๋กœ์šด ๊ธ‰๋ฝ(BB๊ฐ€ ์ญ‰ ๋ฒŒ์–ด์ง„ ๋‚ )์„ ๋†“์ณ ์ƒ๋ช…์„ ์ด ๋„ˆ๋ฌด ๋†’๊ฒŒ ์žกํž˜.
# โ€ป 60์„ ์ด 240์„  ์œ„/์•„๋ž˜์ธ์ง€ ๋ฌด๊ด€ํ•˜๊ฒŒ WAIT(๋ฒ ์ด์Šค) ๋‚ด๋‚ด ์ถ”์  โ€”
# ์ƒ์žฅ์ดˆ๊ธฐ ํ‰ํ–‰์—ฐ์žฅ 240 ๊ตฌ๊ฐ„์—์„œ 60์„ ์ด ์œ„์•„๋ž˜๋กœ ์™”๋‹ค๊ฐ”๋‹คํ•ด๋„ ๊ธ‰๋ฝ์„ ๋†“์น˜์ง€ ์•Š์Œ.
LIFELINE_MARGIN = 0.01 # ์ƒ๋ช…์„  = ์‹œ์ž‘์  BBํ•˜๋‹จ ร— (1-1%)
VIRTUAL_LIFELINE = True # [์ผ๋ด‰] โ˜…๊ฐ€์ƒ ์ƒ๋ช…์„  (์‚ฌ์šฉ์ž ์•„์ด๋””์–ด 2026-08):
# ์ง„์งœ ์ƒ๋ช…์„ (์ฒซ ๋™์‚ฐ ๊ฒƒ)์€ '๊ตฌ์กฐ์šฉ'์œผ๋กœ ๊ทธ๋Œ€๋กœ ๋‘๊ณ ,
# ๋”๋ธ”๋™์‚ฐ/์œ„์•„๋ž˜ ์žฌ๊ฐœ๋กœ '์ž‘์€ ์–ธ๋•'์ด ์ƒ๊ธธ ๋•Œ๋งˆ๋‹ค ๊ทธ ๊ตฌ๊ฐ„ ์ „์šฉ ๋ณดํ˜ธ์„ ์„ ๋”ฐ๋กœ ๊ธ‹๋Š”๋‹ค.
# ๊ฐ€์ƒ ์ƒ๋ช…์„  = ์ง์ „ ๋ˆŒ๋ฆผ(DIP) ๊ตฌ๊ฐ„์˜ ์ตœ์ € BBํ•˜๋‹จ ร—(1-๋งˆ์ง„). ์ง„์งœ ์ƒ๋ช…์„ ๋ณด๋‹ค ์œ„์ผ ๋•Œ๋งŒ ์œ ํšจ.
# ์ด ์„ ์„ ๊นจ๋ฉด: โ‘  ๊ทธ ์ž‘์€ ์–ธ๋•์—์„œ ์‚ฐ ๋ฌผ๋Ÿ‰๋งŒ ๋งค๋„(์ „๋Ÿ‰ ์•„๋‹˜) โ‘ก ์žฌ๋ฒ ์ด์Šค๋กœ ๋™์‚ฐ๋ถ€ํ„ฐ ๋‹ค์‹œ ๊ทธ๋ฆผ.
# โ†’ ๋ฌผํƒ€๊ธฐ๋Š” ๋Š๋˜ ์ „์ฒด ํฌ์ง€์…˜ยท๊ตฌ์กฐ๋Š” ์•ˆ ๋‚ ๋ฆผ. False๋ฉด ์ด ๊ธฐ๋Šฅ ๋”.
DEEP_DIP_ONE_WAVE = False # [์ผ๋ด‰] โ˜…๊นŠ๊ฒŒ ๋น ์ง„ ๋’ค ์žฌ๊ฐœํ•  ๋• '1๋ฒˆ์งธ ์œ„์•„๋ž˜'๋งŒ ๋งค์ˆ˜ (2026-08 ์‚ฌ์šฉ์ž):
# ์ง์ „ ๋ˆŒ๋ฆผ์ด DEEP_DIP_ONE_WAVE_PCT ๋ณด๋‹ค ๊นŠ์—ˆ์œผ๋ฉด, ๊ทธ ๋’ค ์—ด๋ฆฌ๋Š” ์œ„์•„๋ž˜๋Š”
# 1๋ฒˆ์งธ๋งŒ ์‚ฌ๊ณ  2๋ฒˆ์งธ๋Š” ์•ˆ ์‚ผ(๊ตฌ์กฐ๊ฐ€ ์ด๋ฏธ ์ƒํ•œ ๊ตฌ๊ฐ„์ด๋ผ ์ถ”๊ฐ€ ๋ฌผํƒ€๊ธฐ ์ถ•์†Œ).
# False๋ฉด ๊ธฐ์กด์ฒ˜๋Ÿผ 1ยท2๋ฒˆ์งธ ๋‹ค ๋งค์ˆ˜.
DEEP_DIP_ONE_WAVE_PCT = 0.20 # ์œ„ ๊ทœ์น™ ์ ์šฉ ๊ธฐ์ค€ ๊นŠ์ด(๋์ง€์ ์ด 240์„  ๋Œ€๋น„ -20% ์ดํ•˜)
VLIFE_GRACE_DAYS = 20 # [์ผ๋ด‰] โ˜…๊ฐ€์ƒ์ƒ๋ช…์„  ์ดํƒˆ ์œ ์˜ˆ์ผ (2026-08):
# ์„ ์„ ๊นฌ ์ฆ‰์‹œ ํŒ”์ง€ ์•Š๊ณ , ์ด ์ผ์ˆ˜ ์•ˆ์— ์ข…๊ฐ€๊ฐ€ ์„  ์œ„๋กœ ํšŒ๋ณตํ•˜๋ฉด ์—†๋˜ ์ผ๋กœ ํ•จ.
# ์œ ์˜ˆ ๋‚ด๋‚ด ํšŒ๋ณต ๋ชป ํ•˜๋ฉด ๊ทธ๋•Œ ๊ตฌ๊ฐ„๋งค๋„.
# ์ด์œ : ์ฆ‰์‹œ๋งค๋„๋Š” 'ํŒจ๋‹‰ ํˆฌ๋งค ๋ฐ”๋‹ฅ'์—์„œ ํŒŒ๋Š” ๊ฒฝํ–ฅ(166090 2020-03-19=์ฝ”๋กœ๋‚˜ ๋ฐ”๋‹ฅ ๋‹น์ผ,
# 033100 2016-01-20, 236200 2022-06-23 ๋ชจ๋‘ ๋งค๋„ ์งํ›„๊ฐ€ ์ตœ์ €์ ์ด์—ˆ์Œ).
# ์ง„์งœ ๋ถ•๊ดด๋Š” ๊ณ„์† ์„  ์•„๋ž˜ ๋จธ๋ฌผ๊ณ , ๊ธ‰๋ฝ ๋ฐ”๋‹ฅ์€ ๋ฉฐ์น  ๋งŒ์— ๋˜๋Œ์•„์˜ค๋ฏ€๋กœ ์‹œ๊ฐ„์œผ๋กœ ๊ฑธ๋Ÿฌ๋ƒ„.
# 0 = ์œ ์˜ˆ ์—†์Œ(์ฆ‰์‹œ ๋งค๋„).
DOUBLE_MTN_RELIFE = False # [์ผ๋ด‰] (์‹คํ—˜) ๋”๋ธ”๋™์‚ฐ/์žฌ๊ฐœ ์‹œ '์ง„์งœ ์ƒ๋ช…์„ ' ์ž์ฒด๋ฅผ ์˜ฌ๋ ค์žก๊ธฐ โ€” ์ „์ˆ˜์กฐ์‚ฌ ๊ฒฐ๊ณผ ์„ฑ์  ์•…ํ™”๋กœ OFF.
# ๋Œ€์‹  ์œ„ VIRTUAL_LIFELINE ๋ฐฉ์‹ ์‚ฌ์šฉ. True๋กœ ์ผœ๋ฉด ์ง„์งœ ์ƒ๋ช…์„ ์„ ์˜ฌ๋ฆผ(์˜› ์‹คํ—˜).
_DOUBLE_MTN_RELIFE_DOC = """[์ผ๋ด‰] โ˜…๋”๋ธ”๋™์‚ฐ/์žฌ๊ฐœ ์‹œ ์ƒ๋ช…์„ ์„ '์ง์ „ ๋ˆŒ๋ฆผ(DIP) ๊ตฌ๊ฐ„์˜ ์ตœ์ € BBํ•˜๋‹จ'์œผ๋กœ ์˜ฌ๋ ค์žก๊ธฐ.
# ๋ฌธ์ œ(096770): ์ฒซ ๋™์‚ฐ ์ƒ๋ช…์„ (58,739)์„ ์‹œํ€€์Šค ๋‚ด๋‚ด ์œ ์ง€ํ•˜๋‹ค ๋ณด๋‹ˆ, ์ฃผ๊ฐ€๊ฐ€ 15๋งŒ์›๋Œ€์ธ๋ฐ
# ๋ณดํ˜ธ์„ ์ด 5.8๋งŒ์ด๋ผ ์‚ฌ์‹ค์ƒ ๋ณดํ˜ธ๊ฐ€ ์—†์Œ โ†’ ๊ณ„์† ๋–จ์–ด์ง€๋Š”๋ฐ ์œ„์•„๋ž˜ ๋งค์ˆ˜๋งŒ ๋ฐ˜๋ณต.
# ํ•ด๊ฒฐ(์‚ฌ์šฉ์ž): 60์„ ์ด 240์„ ์„ ๋‹ค์‹œ ๋งŒ๋‚˜ ๋”๋ธ”๋™์‚ฐ/์žฌ๊ฐœ๊ฐ€ ํ™•์ •๋˜๋Š” ๊ทธ ์‹œ์ ์—,
# ๋ฐฉ๊ธˆ ์ง€๋‚˜์˜จ DIP ๊ตฌ๊ฐ„์—์„œ ๊ฐ€์žฅ ๋‚ฎ์•˜๋˜ BBํ•˜๋‹จ์„ ์ƒˆ ์ƒ๋ช…์„ ์œผ๋กœ ์‚ผ๋Š”๋‹ค.
# (096770: 2022-10-11 BBํ•˜๋‹จ 127,575 ร— 0.99 = 126,299)
# โ€ป ์ƒ๋ช…์„ ์€ '์˜ฌ๋ฆฌ๊ธฐ๋งŒ' ํ•จ(๋‚ด๋ฆฌ์ง€ ์•Š์Œ) โ€” ์˜› ์ƒ๋ช…์„ ๋ณด๋‹ค ๋‚ฎ์œผ๋ฉด ๊ทธ๋Œ€๋กœ ๋‘ .
# False๋ฉด ์˜ˆ์ „์ฒ˜๋Ÿผ ์ฒซ ๋™์‚ฐ ์ƒ๋ช…์„ ์„ ๊ณ„์† ์œ ์ง€."""
LIFELINE_KEEP_MTN = True # [์ผ๋ด‰/PEAK] โ˜…์ƒ๋ช…์„  ์ดํƒˆํ•ด๋„ ๋™์‚ฐ ๊ตฌ์กฐ๋Š” ์œ ์ง€:
# ์ฃผ๊ฐ€๊ฐ€ ์ƒ๋ช…์„ ์„ ๊นจ๋ฉด ์ „๋Ÿ‰๋งค๋„(์†์ ˆ)๋Š” ํ•˜๋˜, ๋™์‚ฐ์„ ํ๊ธฐํ•˜์ง€ ์•Š๊ณ  ๊ณ„์† ๊ด€์ฐฐ.
# ์ดํ›„ 60์„ ์ด 240์„  ์œ„๋กœ ๋ณต๊ท€ํ•  ๋•Œ '๋์ง€์  60์„  > ์‹œ์ž‘์  60์„ '(๋” ๋†’์€ ์ €์ )์ด๋ฉด
# ๋™์‚ฐ ์„ฑ๊ณต์œผ๋กœ ์ธ์ • โ†’ ์œ„์•„๋ž˜ ๋งค์ˆ˜ ์‹œ์ž‘. ๋” ๋‚ฎ์€ ์ €์ ์ด๋ฉด ๊ธฐ์กด๋Œ€๋กœ ์žฌ๋ฒ ์ด์Šค.
# ๊ทผ๊ฑฐ(236200): ์ฃผ๊ฐ€๋Š” ๊ธ‰๋ฝํ•ด ์ƒ๋ช…์„ ์„ ๊นผ์ง€๋งŒ 60์„ ์€ ์•ˆ ๊บพ์—ฌ
# ์‹œ์ž‘์  19,898(2017-03-20) < ๋์ง€์  20,178(2018-11-19) = ๊ตฌ์กฐ์ƒ ์„ฑ๊ณตํ•œ ๋™์‚ฐ์ธ๋ฐ,
# ๊ธฐ์กด ๋กœ์ง์€ 2018-10-19์— ํ†ต์งธ ํ๊ธฐ โ†’ ์œ„์•„๋ž˜๊ฐ€ 2020-01-06๊นŒ์ง€ 1๋…„ ์ด์ƒ ๋ฐ€๋ ธ์Œ.
# ๋™์‚ฐ์„ฑ๊ณต ์ธ์ • ์‹œ ์ƒ๋ช…์„ ์€ ์ƒˆ ์ €์ (๋์ง€์  BBํ•˜๋‹จ)์œผ๋กœ ์žฌ์„ค์ •.
# False๋ฉด ์˜ˆ์ „์ฒ˜๋Ÿผ ์ƒ๋ช…์„  ์ดํƒˆ=๋™์‚ฐ ์‹คํŒจ(WAIT๋กœ).
PEAK_MIN_PCT = {'5min':0.01,'15min':0.01,'1hour':0.02,'1day':0.05} # ์‚ฐ๋ด‰์šฐ๋ฆฌ ์ตœ์†Œ(60์„ ์ด 240์„  ์œ„๋กœ). ์ผ๋ด‰์€ ํฌ๊ฒŒ
ENDLOW_MIN_PCT = {'5min':0.010,'15min':0.010,'1hour':0.02,'1day':0.07} # ๋์ง€์  ์ตœ์†Œ(60์„ ์ด 240์„  ์•„๋ž˜๋กœ, 60์„  ๊ธฐ์ค€).
# 2026-08 ๋ด‰๋ณ„ ์˜์  ์žฌ์กฐ์ •(๊นŠ์ด๋ถ„ํฌ๋กœ '๊ฑธ๋Ÿฌ๋‚ด๋Š” ๋น„์œจ'์„ ์ผ๋ด‰(~37%) ์ˆ˜์ค€์— ๋งž์ถค):
# 5min 1.0% / 15min 0.5%โ†’1.0%(5๋ถ„๋ด‰๊ณผ ํ†ต์ผ, ์˜ํ–ฅ 420770 1๊ฑด) / 1hour 1.0%โ†’2.0%(204320 2025-04 ์‚ฐ ํ•ฉ์ณ์ง)
# 1day 7%. (๋ด‰ ํด์ˆ˜๋ก ๋ˆŒ๋ฆผ ๊นŠ์ด ์ปค์ง€๋ฏ€๋กœ ๊ธฐ์ค€๋„ ์ปค์ง€๋Š” ๊ฒŒ ๋…ผ๋ฆฌ์ )
NEAR_240 = 0.003 # 240๊ทผ์ฒ˜ ๋งค์ˆ˜ = ์ €๊ฐ€ โ‰ค 240์„  +0.3% (๋ฐ”์ง ๋ถ™์œผ๋ฉด ๋งค์ˆ˜). 6/12 15:15(+0.17%) ์žก์œผ๋ ค๊ณ 
RECOVER_PCT = {'5min': 0.05, '15min': 0.08, '1hour': 0.10, '1day': 0.08} # ์ง์ „ ์œ„์•„๋ž˜ '์ข…๊ฐ€' ๋Œ€๋น„ ์žฌ๋ฌด์žฅ %
WAVE_RISE_PCT = {'5min': 0.01, '15min': 0.01, '1hour': 0.01, '1day': 0.01} # 1๋ฒˆ์งธ ์œ„์•„๋ž˜ ์ „: ์ข…๊ฐ€๊ฐ€ 60์„  ์œ„๋กœ ์ด๋งŒํผ
WAVE_EXIT_PCT = 0.05 # [์ผ๋ด‰] '์œ„' ๊ตฌ๊ฐ„์—์„œ ์ข…๊ฐ€๊ฐ€ 60์„  ์œ„๋กœ ์ตœ๊ณ  +5% ์ด์ƒ ์˜ฌ๋ผ์•ผ ๊ทธ ์œ„์•„๋ž˜(์•„๋ž˜๊ตฌ๊ฐ„) ์ข…๋ฃŒ
# ์‚ด์ง ์˜ฌ๋ผ๊ฐ”๋‹ค ๋งˆ๋Š” ๊ฑด(์˜ˆ: +3.5%) ์•„์ง ๊ฐ™์€ ์•„๋ž˜ ๊ตฌ๊ฐ„์œผ๋กœ ๋ด„
DOUBLE_MTN_GAP = 0.44 # [์ผ๋ด‰] ๋”๋ธ”๋™์‚ฐ ์ƒํ•œ: ์ด๋ฒˆ ์‚ฐ ํ”ผํฌ๊ฐ€ ์ง์ „ ์‚ฐ ํ”ผํฌ ๋Œ€๋น„ +44% ๋ฏธ๋งŒ์ด๋ฉด '์ œ์ž๋ฆฌ=๋”๋ธ”๋™์‚ฐ'. 0=OFF
# (2026-08: 42%โ†’44%. 006260 2025-05-15 ํ”ผํฌ๊ฐญ +43.3%๋ฅผ ๋”๋ธ”๋™์‚ฐ์œผ๋กœ ์ธ์ •. ์‹ค์ œ '์™„์ฃผ ํ›„ ๋ถ•๊ดด'๋Š”
# 005930'23=+45.4%, 078520'17=+46.1%๋กœ 45%โ†‘๋ผ ๊ทธ๋Œ€๋กœ ์žฌ๋ฒ ์ด์Šค ์œ ์ง€. 44%๊ฐ€ ๊ทธ ์‚ฌ์ด ๊น”๋”ํ•œ ์ปท)
DOUBLE_MTN_GAP_MIN = -0.10 # [์ผ๋ด‰] ๋”๋ธ”๋™์‚ฐ ํ•˜ํ•œ: ํ”ผํฌ๊ฐ€ ์ง์ „ ๋Œ€๋น„ -10%๋ณด๋‹ค ๋” ๋‚ด๋ ค๊ฐ€๋ฉด '์ œ์ž๋ฆฌ'๊ฐ€ ์•„๋‹ˆ๋ผ
# ๊ณ„๋‹จ์‹ ํ•˜๋ฝ(์ถ”์„ธ๋ถ•๊ดด) โ†’ ๋”๋ธ”๋™์‚ฐ ์•„๋‹˜. ๊นŠ์€๋ˆŒ๋ฆผโ†’์ƒˆ๋™์‚ฐ์œผ๋กœ ๋„˜๊น€
DOUBLE_MTN_END_TOL = 0.0 # [์ผ๋ด‰] ๋”๋ธ”๋™์‚ฐ ๋์ง€์  ์กฐ๊ฑด: ๋์ง€์ (2) 60์„  >= ๋์ง€์ (1) 60์„  ร— (1-TOL).
# ๋์ง€์ ์ด ๊ณ„์† ๋‚ฎ์•„์ง€๋ฉด ๋”๋ธ”๋™์‚ฐ ์•„๋‹˜(์ง„์งœ ๋์ง€์ ์„ ํ–ฅํ•ด ๊ฐ€๋Š” ์ค‘)
DOUBLE_MTN_PCT = 0.02 # [์ผ๋ด‰] ์œ„์•„๋ž˜ ์ค‘ 60์„ ์ด 240์„  -2% ์•„๋ž˜๋กœ ๋‚ด๋ ค๊ฐ€์•ผ '๋”๋ธ”๋™์‚ฐ'(๋์ง€์ 2) ์ธ์ •. ์‚ด์ง ์Šค์นจ์€ ๋ฌด์‹œ
WAVE_UP_DAYS = 10 # [์ผ๋ด‰] '์œ„' ์ธ์ •: 60์„  ์œ„๋กœ +5% ๋„˜๊ณ  N์ผ ์—ฐ์† ์œ„์— ์žˆ์–ด์•ผ (ํ•˜๋ฃจ ๊ธ‰๋“ฑ ๋ฌด์‹œ)
WAVE_DEEP_PCT = 0.01 # [์ผ๋ด‰] 2๋ฒˆ์งธ ์œ„์•„๋ž˜(์žฌ์ง„์ž…)๋Š” ์ข…๊ฐ€๊ฐ€ 60์„  -1% ์ด์ƒ ๊นŠ๊ฒŒ ๋‚ด๋ ค๊ฐ€์•ผ ์ธ์ • (7/8 ๊ฐ™์€ ์‚ด์ง ์Šค์นจ ๋ฐฉ์ง€)
# ์ฒซ ์œ„์•„๋ž˜๋Š” ์–•์•„๋„ ์ธ์ •. ํ™•์ •๋˜๋ฉด ๊ทธ ์•„๋ž˜๊ตฌ๊ฐ„ ์ฒซ๋‚ ๋ถ€ํ„ฐ ์†Œ๊ธ‰ ๋งค์ˆ˜
MAX_MTN_WAVE = 2 # [์ผ๋ด‰] ์œ„์•„๋ž˜ ๋งค์ˆ˜๋Š” ๋™์‚ฐ N๊ฐœ๊นŒ์ง€ (์ฒซ ๋™์‚ฐ + ๋”๋ธ”๋™์‚ฐ = 2). ํŠธ๋ฆฌํ”Œ(3๋ฒˆ์งธ)+ ๋ถ€ํ„ฐ๋Š” ๋งค์ˆ˜ ์•ˆ ํ•จ(๊ด€์ฐฐ๋งŒ).
# ์žฌ๋ฒ ์ด์Šค(๊นŠ์€๋ˆŒ๋ฆผโ†’์ƒˆ๋™์‚ฐ)๋กœ ๊ตฌ์กฐ๊ฐ€ ์ƒˆ๋กœ ์‹œ์ž‘ํ•˜๋ฉด ์นด์šดํŠธ ๋ฆฌ์…‹.
REBASE_DEEP_PCT = 0.25 # [์ผ๋ด‰] โ˜…์†์Œโ†’์žฌ๋ฒ ์ด์Šค: ์œ„์•„๋ž˜์ธ ์ค„ ์•Œ๊ณ  ์ƒ€๋Š”๋ฐ 240์˜ฌ(์‚ฌ์ดํด ์™„์ฃผ)๋กœ ๋ชป ๊ฐ€๊ณ 
# ๋์ง€์ (2)์ด 240์„  ๋Œ€๋น„ ์ด๋ณด๋‹ค ๋” ๊นŠ์ด(์˜ˆ: -25%) ๋ถ•๊ดดํ•˜๋ฉด, '์ž‘์€๋ด‰์šฐ๋ฆฌโ†’์œ„์•„๋ž˜ ์žฌ๊ฐœ' ๋Œ€์‹ 
# ์žฌ๋ฒ ์ด์Šค(๋™์‚ฐ ๊ทธ๋ฆฌ๊ธฐ๋ถ€ํ„ฐ ๋‹ค์‹œ)๋กœ ๋ณด๋ƒ„. ํ”ผํฌ๊ฐ€ ๊ณ„๋‹จ์‹ํ•˜๋ฝ(์ œ์ž๋ฆฌ ์•„๋‹˜)์ธ ๊ฒฝ์šฐ์—๋งŒ ์ ์šฉ.
# 0=๋”(๊ธฐ์กด: ๊นŠ์–ด๋„ ์œ„์•„๋ž˜ ์žฌ๊ฐœ). ํ•˜์ด๋‹‰์Šค'23(-16.3%)์€ ์žฌ๊ฐœ๋กœ ๋‚จ๊ธฐ๋ ค๋ฉด ์ด๋ณด๋‹ค ์–•๊ฒŒ.
REBASE_EARLY = True # [์ผ๋ด‰] โ˜…์กฐ๊ธฐ ํŒ์ •: ์œ„ ์žฌ๋ฒ ์ด์Šค๋ฅผ 'DIP ํƒˆ์ถœ(60์„  240 ๋ณต๊ท€)'๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ๋ง๊ณ ,
# DIP ์ถ”์  ์ค‘์— ๊นŠ์ด๊ฐ€ -REBASE_DEEP_PCT๋ฅผ ์ฐ๋Š” ์ˆœ๊ฐ„ ๋ฐ”๋กœ ์žฌ๋ฒ ์ด์Šค.
# (์•„๋ชจ๋ ˆ 2018-08 DIP์ง„์ž… โ†’ 2019-12 ํƒˆ์ถœ๊นŒ์ง€ 16๊ฐœ์›”์„ 'ํŒ์ •๋Œ€๊ธฐ'๋กœ ๋Œ๋˜ ๋ฌธ์ œ ํ•ด๊ฒฐ)
# ์•ˆ์ „์žฅ์น˜: ๋”๋ธ”๋™์‚ฐ ๊ฐ€๋Šฅ์„ฑ์ด ๋‚จ์•„์žˆ์œผ๋ฉด(ํ”ผํฌ๊ฐญ์ด ๋ฐด๋“œ ์•ˆ + ๋์ง€์  ์œ ์ง€) ์กฐ๊ธฐ๋ฐœ๋™ ์•ˆ ํ•จ.
# ํ”ผํฌ๊ฐญ์€ DIP ์ง„์ž…์‹œ ํ™•์ •, ๋์ง€์ ๊ฐญ์€ ๋‹จ์กฐ๊ฐ์†Œ(ํ•œ๋ฒˆ ์‹คํŒจ=์˜๊ตฌ์‹คํŒจ)๋ผ ์กฐ๊ธฐํŒ์ •์ด ์•ˆ์ „.
# False๋ฉด ์˜ˆ์ „์ฒ˜๋Ÿผ DIP ํƒˆ์ถœ ๋•Œ๋งŒ ํŒ์ •.
REBASE_DIP_DAYS = 600 # [์ผ๋ด‰] โ˜…๊ธฐ๊ฐ„ ๊ธฐ๋ฐ˜ ์žฌ๋ฒ ์ด์Šค: DIP(๋์ง€์ 2 ์ถ”์ =๋งค์ˆ˜์ค‘๋‹จ)๊ฐ€ ์ด ์ผ์ˆ˜๋ฅผ ๋„˜๊ฒŒ ์ง€์†๋˜๋ฉด
# ๊นŠ์ด(60์„  gap)์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ์žฌ๋ฒ ์ด์Šค. '2๋…„์งœ๋ฆฌ ๋ˆŒ๋ฆผ'์€ ๋ˆŒ๋ฆผ์ด ์•„๋‹ˆ๋ผ ๊ตฌ์กฐ ๋ถ•๊ดด ํ›„ ์žฌ๊ฑด.
# ํ•„์š”ํ•œ ์ด์œ (236200): 2020-10-06~2022-10-07 DIP๊ฐ€ 731์ผ. ์ฃผ๊ฐ€๋Š” -58.4% ๋น ์กŒ๋Š”๋ฐ
# 60์„  vs 240์„  gap์€ ์ตœ์ € -11.5%๋ฟ์ด๋ผ REBASE_DEEP_PCT(-25%)์— ์•ˆ ๊ฑธ๋ฆผ.
# โ†’ ์ฒœ์ฒœํžˆ ์˜ค๋ž˜ ํ˜๋Ÿฌ๋‚ด๋ฆฌ๋ฉด 240์„ ๋„ ๊ฐ™์ด ๋‚ด๋ ค๊ฐ€ ๋‘ ์„  ๊ฐ„๊ฒฉ์ด ์•ˆ ๋ฒŒ์–ด์ง€๋Š” ๋งน์ .
# ๊ฒ€์ฆ: ์œ„์•„๋ž˜์žฌ๊ฐœ๋กœ ๋๋‚œ DIP๋Š” ์ตœ๋Œ€ 490์ผ์ธ๋ฐ 236200๋งŒ 731์ผ โ†’ ์‚ฌ์ด๊ฐ€ ํ…… ๋น”.
# 232140 ๋”๋ธ”๋™์‚ฐ(577์ผ)ยทํ•˜์ด๋‹‰์Šค'23 ์žฌ๊ฐœ(359์ผ)๋Š” 600์ผ ๋ฏธ๋งŒ์ด๋ผ ์•ˆ์ „.
# ์•ˆ์ „์žฅ์น˜๋Š” REBASE_EARLY์™€ ๋™์ผ(๋”๋ธ”๋™์‚ฐ ๊ฐ€๋Šฅ์„ฑ ์žˆ์œผ๋ฉด ๋ฐœ๋™ ์•ˆ ํ•จ). 0=๋”.
DOUBLE_MTN_LIFELINE_FLOOR = True # [์ผ๋ด‰] โ˜…๋”๋ธ”๋™์‚ฐ ์žฌ์ •์˜(์‚ฌ์šฉ์ž, 030530):
# ๋์ง€์ (2)์ด ๋์ง€์ (1)๋ณด๋‹ค ๋‚ฎ์•„์ ธ๋„, DIP ๊ตฌ๊ฐ„ '์ €๊ฐ€'๊ฐ€ ์ฒซ ๋™์‚ฐ ์ƒ๋ช…์„  ์œ„์— ์žˆ์œผ๋ฉด
# ์‹œํ€€์Šค๊ฐ€ ์•ˆ ์ฃฝ์€ ๊ฒƒ โ†’ ๋”๋ธ”๋™์‚ฐ์œผ๋กœ ์ธ์ •(์ƒ๋ช…์„ ยท์‹œํ€€์Šค ์œ ์ง€, ์žฌ๋ฒ ์ด์Šค ์•ˆ ํ•จ).
# ํ”ผํฌ๊ฐญ ๋ฐด๋“œ(-10%~+42%) ๋ฐ–(๊ณ„๋‹จ์‹ํ•˜๋ฝ/ํ”ผํฌ์ „์ง„)์€ ์—ฌ์ „ํžˆ ์žฌ๋ฒ ์ด์Šค โ€” ๊ธฐ์กด ์ฐจ๋‹จ ์œ ์ง€.
# โ€ป 600์ผ ์žฅ๊ธฐDIP ์žฌ๋ฒ ์ด์Šค๋Š” ์ด ๊ทœ์น™๋ณด๋‹ค ์šฐ์„ (236200: ์‹œ๊ฐ„์ด ๊ตฌ์กฐ๋ฅผ ์ด๊น€).
# False๋ฉด ๊ธฐ์กด(๋์ง€์ (2)โ‰ฅ๋์ง€์ (1) ์กฐ๊ฑด๋งŒ).
WAVE_CONFIRM_GAP = 0.07 # [์ผ๋ด‰] โ˜…์œ„์•„๋ž˜ ๋ณต๊ท€ ํ™•์ธ: ๋™์‚ฐ์„ฑ๊ณต/๋”๋ธ”๋™์‚ฐ/์žฌ๊ฐœ๋กœ ์œ„์•„๋ž˜๊ฐ€ ์—ด๋ ค๋„,
# 60์„ ์ด 240์„  ์œ„๋กœ ์ด๋งŒํผ(+7%) ์˜ฌ๋ผ๊ฐ€์•ผ ์‹ค์ œ ๋งค์ˆ˜ ์‹œ์ž‘.
# 60์„ ์ด 240์„ ์„ ์ฐ”๋” ๋šซ๊ณ  24~70์ผ ๋งŒ์— ๋˜๊บพ์ด๋Š” '๊ฐ€์งœ ๋ณต๊ท€'(๋ชจ์žํ˜•ํƒœ)์—์„œ
# ๋ฌผ๋ฆฌ๋Š” ๊ฒƒ ๋ฐฉ์ง€. ๊ฒ€์ฆ: ๊ฐ€์งœ 9๊ฑด ์ตœ๋Œ€ 5.7% vs ์ง„์งœ 21๊ฑด ์ตœ์†Œ 9.5% โ†’ ์‚ฌ์ด๊ฐ€ ๋น„์–ด 100% ๋ถ„๋ฆฌ.
# ๋Œ€๊ฐ€: ์ง„์งœ๋„ ์ง„์ž…์ด 6~141์ผ ๋Šฆ์–ด์ง(๋Œ€๋ถ€๋ถ„ 12~16์ผ). 0 = ์ด ํ•„ํ„ฐ ๋”(์˜ˆ์ „ ๋™์ž‘).
WAVE_CONFIRM_DAYS = 90 # [์ผ๋ด‰] โ˜…์‹œ๊ฐ„ ๊ธฐ๋ฐ˜ ๋ณต๊ท€ ํ™•์ธ (WAVE_CONFIRM_GAP๊ณผ OR):
# gap์ด +7%์— ๋ชป ๋ฏธ์ณ๋„, 60์„ ์ด 240์„  ์œ„(gap>0)์—์„œ ์ด ์ผ์ˆ˜๋งŒํผ '์—ฐ์† ์ƒ์กด'ํ•˜๋ฉด
# ์ง„์งœ ๋ณต๊ท€๋กœ ์ธ์ • โ†’ ์œ„์•„๋ž˜ ๋งค์ˆ˜ ์‹œ์ž‘. ์ค‘๊ฐ„์— gapโ‰ค0(240 ์•„๋ž˜๋กœ)๋˜๋ฉด ์‹œ๊ฐ„์นด์šดํ„ฐ ๋ฆฌ์…‹(๊ฐ€์งœ).
# ๊ทผ๊ฑฐ: ๊ฐ€์งœ๋ชจ์ž๋Š” 240 ์œ„ ์ƒ์กด ์ตœ๋Œ€ 85์ผ(000660'08=34, 090430'17=85)์— ๋˜๊บพ์ด๋Š”๋ฐ,
# ์ง„์งœ(์‚ผ์„ฑ'19)๋Š” gap์ด ๋๋‚ด +7% ๋ฏธ๋‹ฌ(์ตœ๋Œ€+1.3%)์ด์–ด๋„ 240 ์œ„ 399์ผ ์ƒ์กด โ†’ ์‹œ๊ฐ„์ด ํŒ๋ณ„.
# 0 = ์‹œ๊ฐ„ ํ™•์ธ ๋”(gap๋งŒ์œผ๋กœ ํ™•์ธ).
MINBAR_STATE = False # [๋ถ„๋ด‰] โ˜…๋ถ„๋ด‰(5/15/60๋ถ„)๋„ ์ผ๋ด‰์‹ ์ƒํƒœ๊ฐ์ง€ ์‚ฌ์šฉ (2026-08):
# โš ๏ธ True๋กœ ์ผœ๋ฉด ๋ถ„๋ด‰์ด ์ผ๋ด‰ DIP '๋‹จ๊ณ„'์— ๋“ค์–ด๊ฐ€ ๋งค์ˆ˜๊ตฌ์กฐ๊ฐ€ ํ™• ๋ฐ”๋€œ(๋งค์ˆ˜ 4~8๋ฐฐ ํญ์ฆ).
# John์€ "ํ„ฐ์น˜๋งค์ˆ˜ ๊ทธ๋Œ€๋กœ"๋ฅผ ์›ํ•˜๋ฏ€๋กœ ์ด ๋ฐฉ์‹(๋‹จ๊ณ„ ์žฌ๋ผ์šฐํŒ…)์€ ๋ถ€์ ํ•ฉ โ†’ ๊ธฐ๋ณธ OFF.
# ์ƒํƒœํ‘œ์‹œ๋Š” '๋‹จ๊ณ„ ์žฌ๋ผ์šฐํŒ…' ๋ง๊ณ  '๋ณ‘๋ ฌ ์˜ค๋ฒ„๋ ˆ์ด'๋กœ ํ•ด์•ผ ํ•จ(๋งค์ˆ˜ ๋ถˆ๋ณ€). ์•„๋ž˜๋Š” ์˜› ์„ค๋ช…:
# DIP(๋งค์ˆ˜๋ฉˆ์ถค)ยท๋ณต๊ท€ํ™•์ธยท์Šค์นจยท๋ชจ์ž๋ฅผ ๋ถ„๋ด‰์—๋„ ์ ์šฉ. ์‹œ๊ฐ„๊ธฐ์ค€(์ผ)์€ '๋ด‰ ๊ฐœ์ˆ˜'๋กœ ํ™˜์‚ฐ.
# ๋‹จ ๋งค์ผ๋งค์ˆ˜ยท์†Œ๊ธ‰ยท๊ฐ€์ƒ์ƒ๋ช…์„ ์€ ๋ถ„๋ด‰์— ์•ˆ ์”€(ํ„ฐ์น˜๊ธฐ๋ฐ˜ ๋งค์ˆ˜ ๋ฐฉ์‹์€ ๊ทธ๋Œ€๋กœ).
# False๋ฉด ์˜ˆ์ „์ฒ˜๋Ÿผ ๋ถ„๋ด‰์€ ์ˆœ์ˆ˜ ํ„ฐ์น˜๊ธฐ๋ฐ˜(์ƒํƒœ๊ฐ์ง€ ์—†์Œ).
WAVE_TOUCH_BUY = True # [์ผ๋ด‰] โ˜…์Šค์นจ ๊ตฌ๊ฐ„ ๋งค์ˆ˜ ํ—ˆ์šฉ (WAVE_CONFIRM_GAP๊ณผ ์ง):
# 60์„ ์ด 240์„ ์„ '์Šค์น˜๊ฑฐ๋‚˜ ๊ทธ ์•„๋ž˜'(gap โ‰ค 0, DIP -2% ์ง„์ž… ์ „)์— ์žˆ์œผ๋ฉด
# ๋ชจ์ž(๊ฐ€์งœ ๋ณต๊ท€)๊ฐ€ ์•„์˜ˆ ์•ˆ ์ƒ๊ธด ๊ฒƒ = ๋ฐ”๋‹ฅ ๋‹ค์ง€๋Š” ์ค‘ โ†’ ์œ„์•„๋ž˜ ๋งค์ˆ˜ ํ—ˆ์šฉ.
# ๊ฒฐ๊ณผ: ๋งค์ˆ˜ ๊ธˆ์ง€ ๊ตฌ๊ฐ„์€ gap์ด (0, +7%) ์—ด๋ฆฐ ๋ฐด๋“œ๋ฟ. ๊ทธ ์œ„(์ง„์งœ ๋ณต๊ท€)ยท์•„๋ž˜(์Šค์นจ) ๋‹ค ๋งค์ˆ˜.
# False๋ฉด ์˜ˆ์ „์ฒ˜๋Ÿผ 60>240 + ํ™•์ธ(+7%) ๋ผ์•ผ๋งŒ ๋งค์ˆ˜.
WAVE_HAT_ONLY_FRESH = True # [์ผ๋ด‰] โ˜…๋ชจ์ž ์ฒดํฌ๋ฅผ '๋™์‚ฐ ์™„์„ฑ ์งํ›„ ๋ณต๊ท€'์—๋งŒ ์ ์šฉ (์‚ฌ์šฉ์ž ์ง€์‹œ 2026-08):
# ๋ชจ์ž๋ฅผ ์˜์‹ฌํ•  ์ƒํ™ฉ์€ '๋งŽ์ด ์˜ค๋ฅธ ๋’ค ๊บพ์ด๋Š” ์ˆœ๊ฐ„' = ๋™์‚ฐ์„ ํฌ๊ฒŒ ๊ทธ๋ฆฌ๊ณ  60์„ ์ด 240์„  ์œ„๋กœ
# ๋ณต๊ท€ํ•˜๋Š” ๊ทธ ์ˆœ๊ฐ„. ์ด๋ฏธ ์œ„์•„๋ž˜ ์‹œํ€€์Šค๊ฐ€ ๋Œ์•„๊ฐ€๋Š” ์ค‘(๋”๋ธ”๋™์‚ฐ ๋ฆฌ์…‹ / ์œ„์•„๋ž˜ ์žฌ๊ฐœยท๊ณ„์†)์—๋Š”
# ๋ชจ์ž๊ฐ€ ์ƒ๊ธฐ๋“  ๋ง๋“  ๋ฌด์‹œํ•˜๊ณ  ์Šค์นจ ๋งค์ˆ˜ ํ—ˆ์šฉ.
# โ†’ ์œ„์•„๋ž˜๋ฅผ ์—ฐ ์‚ฌ๊ฑด์ด '๋™์‚ฐ์„ฑ๊ณตโ†’์œ„์•„๋ž˜์‹œ์ž‘'์ผ ๋•Œ๋งŒ ๋ชจ์ž ์ฒดํฌ. False๋ฉด ํ•ญ์ƒ ์ฒดํฌ.
WAVE_HAT_PCT = 0.0103 # [์ผ๋ด‰] โ˜…์Šค์นจ vs ๋ชจ์ž ๊ตฌ๋ถ„ (WAVE_TOUCH_BUY ๋ณด์ •):
# '์Šค์นจ(๋ฐ”๋‹ฅ ๋‹ค์ง€๊ธฐ)'์€ ์•„์ง ํ•œ ๋ฒˆ๋„ ์•ˆ ์˜ฌ๋ผ๊ฐ„ ์ง„์งœ ๋ฐ”๋‹ฅ์ด์–ด์•ผ ํ•จ. ๊ทธ๋Ÿฐ๋ฐ ์ด ์œ„์•„๋ž˜์—์„œ
# ์ด๋ฏธ 60์„ ์ด 240์„  ์œ„๋กœ ์ด๋งŒํผ(+1%) ์˜ฌ๋ผ๊ฐ€ '๋ชจ์ž ๋ด‰์šฐ๋ฆฌ'๋ฅผ ๋งŒ๋“  ๋’ค ๋‹ค์‹œ gapโ‰ค0๋กœ
# ๋‚ด๋ ค์˜ค๋ฉด, ๊ทธ๊ฑด ๋ฐ”๋‹ฅ์ด ์•„๋‹ˆ๋ผ '๋ชจ์ž์˜ ํ•˜๊ฐ• ๋‹ค๋ฆฌ' โ†’ ์Šค์นจ ๋งค์ˆ˜ ๊ธˆ์ง€(ํ™•์ธ ๋Œ€๊ธฐ ์œ ์ง€).
# ๊ทผ๊ฑฐ(166090): 2019-04~05 gap์ด +1.5%๊นŒ์ง€ ์˜ฌ๋ž๋‹ค๊ฐ€(๋ชจ์ž) 5/22 gap-0.0%๋กœ ๋‚ด๋ ค์˜ค์ž
# ๊ธฐ์กด ์ฝ”๋“œ๊ฐ€ '์Šค์นจ=๋ฐ”๋‹ฅ'์œผ๋กœ ์˜คํŒํ•ด ๋งค์ˆ˜. ๋ด‰์šฐ๋ฆฌ๋ฅผ ๋งŒ๋“  ์ด๋ ฅ์„ ๋ด์•ผ ์ง„์งœ ๋ฐ”๋‹ฅ๊ณผ ๊ตฌ๋ถ„๋จ.
# 0 = ์ด ๋ณด์ • ๋”(์˜ˆ์ „์ฒ˜๋Ÿผ gapโ‰ค0์ด๋ฉด ๋ฌด์กฐ๊ฑด ์Šค์นจ ๋งค์ˆ˜).
# โ€ป ์ž„๊ณ„ 1.03%์ธ ์ด์œ : 090430 2008 ๋ชจ์ž ์‹ค์ธก 1.0208%๋ฅผ '๋ชจ์ž ์•„๋‹˜'์œผ๋กœ ๋‘๋ผ๋Š” ์‚ฌ์šฉ์ž ์ง€์‹œ
# ("1.02% ์ดํ•˜๋Š” ๋ชจ์ž ์•„๋‹˜, ์ดˆ๊ณผ๊ฐ€ ๋ชจ์ž"). ๋ฐ”๋กœ ์œ„ ์ผ€์ด์Šค๋Š” 030530 1.0445%, 112610 1.1473%.
WAVE_BUY_FLOOR = 0.08 # [์ผ๋ด‰] ์œ„์•„๋ž˜ ๋งค์ˆ˜ ํ•˜ํ•œ: ์ข…๊ฐ€๊ฐ€ 240์„ ๋ณด๋‹ค -8% ๋„˜๊ฒŒ ๊นŠ์œผ๋ฉด '๊นŠ์€ ๊ตฌ๊ฐ„'.
# ๊นŠ์€ ๊ตฌ๊ฐ„์—์„  ํ•˜๋ฝ ์ค‘์—” ๋งค์ˆ˜ ๋ณด๋ฅ˜('์œ„์•„๋ž˜ ์†์•˜๋‹ค'), ๋ฐ˜๋“ฑํ•œ ๋‚ (์ข…๊ฐ€>์ „์ผ์ข…๊ฐ€)์—๋งŒ ๋งค์ˆ˜.
# โ†’ 2008ํ˜• ๊ณ„์†ํ•˜๋ฝ=์นผ ํšŒํ”ผ / 2020ํ˜• V๋ฐ”๋‹ฅ=๋ฐ˜๋“ฑ์ˆœ๊ฐ„ ์žฌ๊ฐœ. 0์ด๋ฉด ํ•„ํ„ฐ ๋”.
EARLY_EXTEND = True # [์ผ๋ด‰] ์ƒ์žฅ์ดˆ๊ธฐ 240์„  ๋’ค๋กœ์—ฐ์žฅ ON/OFF. ๊ธฐ๋ณธ ON(ํ‰ํ–‰์—ฐ์žฅ ์ฑ„ํƒ). OFF๋ฉด ๊ธฐ์กด(i<240 ์Šคํ‚ต)๊ณผ ๋™์ผ
EXTEND_MODE = 'flat' # 'flat'=์ฒซ ์ง„์งœ240๊ฐ’ ์ˆ˜ํ‰ ์—ฐ์žฅ(๊ฒ€์ฆ์ƒ ์ดˆ๊ธฐ๋™์‚ฐ ํฌ์ฐฉ๋จ) / 'slope'=๊ธฐ์šธ๊ธฐ์—ฐ์žฅ(์ดˆ๊ธฐ born-above๋ผ ๋ฏธํฌ์ฐฉ)
EXTEND_SLOPE_WIN = 60 # slope ๋ชจ๋“œ ์ „์šฉ: ๊ธฐ์šธ๊ธฐ = ์ฒซ ์ง„์งœ240 ์ดํ›„ N์ผ ํ‰๊ท  ์ผ๋ณ€ํ™”
CYCLE_END = 0.10 # 60์„ ์ด 240์„  -10% ์•„๋ž˜ โ†’ ์‚ฌ์ดํด ์ข…๋ฃŒ (์ง„์งœ ์ถ”์„ธ๋ถ•๊ดด. -5%๋Š” ๋„ˆ๋ฌด ๋นก๋นกํ•ด์„œ ๊นŠ์€ ๋ˆŒ๋ฆผ๋„ ๋Š๊น€)
SELL_ORPHAN_ON_CYCLEEND = True # [์ฑ„ํƒ 2026-08] โ˜…์‚ฌ์ดํด์ข…๋ฃŒ๋กœ hold=False ๋œ ๋’ค์—๋„, ํŒ ์  ์—†๋Š” ๋ฌผ๋Ÿ‰(held)์ด
# ๋‚จ์•„์žˆ๊ณ  ์ €๊ฐ€๊ฐ€ ์ƒ๋ช…์„  ๋ฐ‘์œผ๋กœ ๊ฐ€๋ฉด ๊ทธ ๋ฌผ๋Ÿ‰ ์ „๋Ÿ‰๋งค๋„.
# ๋ฌธ์ œ: ์‚ฌ์ดํด์ข…๋ฃŒ๊ฐ€ hold๋ฅผ ๊บผ๋ฒ„๋ ค ๊ทธ ๋ฌผ๋Ÿ‰์ด ๋ณดํ˜ธ ์—†์ด ๋ฐฉ์น˜๋˜๋Š” ๋นˆํ‹ˆ์ด ์žˆ์—ˆ์Œ
# (006260 5๋ถ„๋ด‰: ์‚ฌ์ดํด1 60๋งŒ์›์ด ์ƒ๋ช…์„  439,368 ๊นจ์ ธ๋„ ์•ˆ ํŒ”๋ฆผ โ†’ ๋ฐฉ์น˜).
# โ€ป ์ ์šฉ ๋ฒ”์œ„๋Š” SELL_ORPHAN_TFS ๋กœ ์ œํ•œ(ํ˜„์žฌ 5๋ถ„/15๋ถ„๋ด‰๋งŒ). ์ผ๋ด‰์€ ์ตœํ›„๋ณด๋ฃจ๊ฐ€ ๋‹ด๋‹นํ•˜๋ฏ€๋กœ ์ œ์™ธ.
SELL_ORPHAN_TFS = ('5min','15min') # โ˜…์œ„ ๊ทœ์น™์„ ์ ์šฉํ•  ํƒ€์ž„ํ”„๋ ˆ์ž„. ์ผ๋ด‰/1์‹œ๊ฐ„๋ด‰ ์ œ์™ธ(์›ํ•˜๋ฉด ์—ฌ๊ธฐ์— ์ถ”๊ฐ€).
SELL_COOLDOWN_DAYS = 90 # ์ „๋Ÿ‰๋งค๋„(์ตœํ›„๋ณด๋ฃจ ๋“ฑ) ํ›„ ์ด ๊ธฐ๊ฐ„ ๋งค์ˆ˜ ๊ธˆ์ง€ (30โ†’90์ผ๋กœ ํ™•๋Œ€)
WAVE_OVERRIDE_COOLDOWN = True # [์ผ๋ด‰] โ˜…์ผ๋ด‰ 1ยท2๋ฒˆ์งธ ์œ„์•„๋ž˜ ๋งค์ˆ˜๋Š” 90์ผ ์ฟจ๋‹ค์šด๋ณด๋‹ค ์ตœ์šฐ์„  โ†’ ์ฟจ๋‹ค์šด ๋ฌด์‹œํ•˜๊ณ  ๋งค์ˆ˜.
# (๋ถ„๋ด‰ 5ยท15ยท60๋ถ„ยท๊ธฐํƒ€ ๋งค์ˆ˜๋Š” ๊ทธ๋Œ€๋กœ 90์ผ ๊ธˆ์ง€ ์œ ์ง€). False๋ฉด ์ผ๋ด‰๋„ ์ฟจ๋‹ค์šด ์ ์šฉ.
VCP_MULT = 3 # VCP ์‚ด์•„์žˆ๋Š” ๊ตฌ๊ฐ„์—์„œ ์œ„์•„๋ž˜ ๋งค์ˆ˜ ์‹œ ๋ฐฐ์ˆ˜
AMT = {'5min': {'a': 200_000, 'b': 300_000},
'15min': {'a': 300_000, 'b': 500_000},
'1hour': {'a': 800_000, 'b': 1_200_000},
# ์ผ๋ด‰: ํ„ฐ์น˜ 1ํšŒ ๋งค์ˆ˜๊ฐ€ ์•„๋‹ˆ๋ผ '60์„  ์•„๋ž˜ ์žˆ๋Š” ๋™์•ˆ ๋งค์ผ ๋ฐ˜๋ณต ๋งค์ˆ˜'
'1day': {'daily1': 1_000_000, 'daily2': 500_000}}
def _daily_peaks(df):
"""์ผ๋ด‰ ํ”ผํฌ ์‚ฌ์ „ ๊ณ„์‚ฐ (peak_gap_debug์™€ ๋™์ผ ๋กœ์ง).
60์„ ์ด 240์„  ์œ„์— ์žˆ๋Š” ๊ฐ ๊ตฌ๊ฐ„์˜ 60์„  ์ตœ๊ณ ์ . ๋ฐ˜ํ™˜ [(ํ”ผํฌ์ผ์žstr, ํ”ผํฌ60์„ ๊ฐ’)]"""
d = df[(df['Close'] > 0)].copy()
c = d['Close']
m60 = c.rolling(MA_60, min_periods=20).mean()
m240 = c.rolling(MA_240, min_periods=60).mean()
ok = m60.notna() & m240.notna()
above = (m60 > m240) & ok
grp = (above != above.shift()).cumsum()
peaks = []
for g, idx in d.index.to_series().groupby(grp):
seg = idx.index
if not above.loc[seg[0]]:
continue
if len(seg) < 5:
continue
seg_m60 = m60.loc[seg]
pk_i = seg_m60.idxmax()
pk_gap = m60.loc[pk_i] / m240.loc[pk_i] - 1
if pk_gap < PEAK_MIN_PCT['1day']:
continue
peaks.append((str(pk_i)[:10], float(m60.loc[pk_i])))
return peaks
LAST_STATUS = '' # be.run ํ›„ '์ง€๊ธˆ ์ƒํƒœ' ํ•œ ์ค„ ์š”์•ฝ (full_debug/app์ด ์ฝ์Œ)
LAST_STATUS_KIND = 'wait' # ์ƒํƒœ ์ข…๋ฅ˜(์ƒ‰์น ์šฉ): buy/danger/dip/done/wait
LAST_STATUS_SHORT = '' # ์งง์€ ์ƒํƒœ๋ช…(๋ผ๋ฒจ์šฉ): '์‚ฐ๋ด‰์šฐ๋ฆฌ ๊ทธ๋ฆฌ๋Š” ์ค‘' ๋“ฑ
LAST_STATUS_DETAIL = '' # ์ƒํƒœ ์ƒ์„ธ(๋’ท๋ถ€๋ถ„): '์‹œ์ž‘ ๋ฐ”๋‹ฅ ... / ์ƒ๋ช…์„  ...'
def run(df, tf='5min', sf=None, sell_dates=None, vcp_wins=None, block_ranges=None):
# sell_dates: ์ „๋Ÿ‰๋งค๋„ ๋ฐœ์ƒ์ผ ๋ฆฌ์ŠคํŠธ. ์ดํ›„ SELL_COOLDOWN_DAYS ๋งค์ˆ˜๊ธˆ์ง€
# block_ranges: [(start,end)] ์ด ๊ตฌ๊ฐ„์—” ๋งค์ˆ˜ ๊ธˆ์ง€ (๋ถ„๋ด‰์šฉ โ€” ์ผ๋ด‰์ด DIP(๋งค์ˆ˜์ค‘๋‹จ)์ด๋ฉด ๋ถ„๋ด‰๋„ ๋ฉˆ์ถค)
# vcp_wins: VCP ์œ ํšจ๊ตฌ๊ฐ„ [{'start','end'}]. ์ด ๊ตฌ๊ฐ„์˜ ์œ„์•„๋ž˜ ๋งค์ˆ˜๋Š” VCP_MULT(3๋ฐฐ)
daily = (tf == '1day') # ์ผ๋ด‰ = ๋งค์ผ ๋ฐ˜๋ณต๋งค์ˆ˜ ๋ชจ๋“œ
if daily:
d1 = AMT['1day']['daily1']; d2 = AMT['1day']['daily2']
a = b = 0
else:
a = AMT[tf]['a']; b = AMT[tf]['b']
recov = RECOVER_PCT[tf]; wrise = WAVE_RISE_PCT[tf]
day_peaks = _daily_peaks(df) if tf=='1day' else []
def _peak_before(ds):
"""ds(YYYY-MM-DD) ์ด์ „์˜ ๋งˆ์ง€๋ง‰ ํ”ผํฌ๊ฐ’. ์—†์œผ๋ฉด None"""
out=None
for pdt,pv in day_peaks:
if pdt < ds: out=pv
else: break
return out
pmin = PEAK_MIN_PCT[tf]; emin = ENDLOW_MIN_PCT[tf]
# ๋ฐ์ดํ„ฐ ํด๋ฆฌ๋‹: 0/์Œ์ˆ˜ ๊ฐ€๊ฒฉ ๋ด‰ ์ œ๊ฑฐ (์•ก๋ฉด๋ถ„ํ•  ๋“ฑ ์•„ํ‹ฐํŒฉํŠธ)
df = df[(df['Close']>0) & (df['Low']>0) & (df['High']>0)].copy()
# ์ „๋Ÿ‰๋งค๋„ ํ›„ ์ฟจ๋‹ค์šด(๋งค์ˆ˜๊ธˆ์ง€) ํŒ์ •
_sd = sorted(pd.to_datetime(sell_dates)) if sell_dates else []
_br = [(pd.Timestamp(a), (pd.Timestamp(b) if b is not None else None)) for a,b in (block_ranges or [])]
def _blocked(ts):
# ๋ง‰ํ˜”์œผ๋ฉด ์‚ฌ์œ  ๋ฌธ์ž์—ด ๋ฐ˜ํ™˜(์–ด๋А ์ „๋Ÿ‰๋งค๋„/DIP ๋•Œ๋ฌธ์ธ์ง€), ์•ˆ ๋ง‰ํ˜”์œผ๋ฉด None
for sd in _sd:
if sd <= ts <= sd + pd.Timedelta(days=SELL_COOLDOWN_DAYS):
return f"{str(sd)[:10]} ์ „๋Ÿ‰๋งค๋„ ํ›„ {SELL_COOLDOWN_DAYS}์ผ ๋งค์ˆ˜๊ธˆ์ง€"
for a,b in _br: # ์ผ๋ด‰ DIP(๋งค์ˆ˜์ค‘๋‹จ) ๊ตฌ๊ฐ„ โ†’ ๋ถ„๋ด‰๋„ ๊ธˆ์ง€
if ts >= a and (b is None or ts < b):
return f"์ผ๋ด‰ {str(a)[:10]}๋ถ€ํ„ฐ ๋งค์ˆ˜๋ฉˆ์ถค(๋ˆŒ๋ฆผ ํŒ์ •) ๊ตฌ๊ฐ„์ด๋ผ ๊ธˆ์ง€"
return None
c = df['Close']; lo = df['Low']
ma60 = c.rolling(MA_60, min_periods=20).mean()
bbl = c.rolling(BB_WIN).mean() - BB_STD * c.rolling(BB_WIN).std()
# โ”€โ”€ 240์„ : ์ง„์งœ๊ฐ’ + ์ƒ์žฅ์ดˆ๊ธฐ ์„ ํ˜• ๋’ค๋กœ์—ฐ์žฅ(๊ธฐ์šธ๊ธฐ ๋ณด์กด) โ”€โ”€
# 240๋ด‰์ด ์•ˆ ์Œ“์ธ ๊ทน์ดˆ๊ธฐ๋Š” ์ฒซ ์ง„์งœ240๊ฐ’์—์„œ ์‹œ์ž‘๊ธฐ์šธ๊ธฐ๋กœ ๊ณผ๊ฑฐ๋กœ ์™ธ์‚ฝ โ†’ ์„ ์„ ์™ผ์ชฝ์œผ๋กœ ๋Š˜๋ ค ๊ทธ๋ฆผ
ma240_true = c.rolling(MA_240).mean() # min_periods=240 (์ง„์งœ)
ma240 = ma240_true.copy()
if EARLY_EXTEND and ma240_true.notna().any():
i0 = int(ma240_true.notna().values.argmax()) # ์ฒซ ์ง„์งœ240 ๋ฐ”
v0 = float(ma240_true.iloc[i0])
slope = 0.0 if EXTEND_MODE=='flat' else float(ma240_true.diff().iloc[i0:i0+EXTEND_SLOPE_WIN].mean())
vals = ma240.values.copy()
for _j in range(i0):
vals[_j] = v0 - slope*(i0 - _j)
ma240 = pd.Series(vals, index=ma240.index)
idx = df.index
HI = 9e12
phase='WAIT' # WAIT โ†’ PEAK(์‚ฐ๋ด‰์šฐ๋ฆฌ,๋์ง€์ ) โ†’ WAVE(์œ„์•„๋ž˜)
start_ma60=HI; start_bb60=None # ์‹œ์ž‘์  ํ›„๋ณดA: 60์„  ์ตœ์ € + ๊ทธ BB
start_pxlow=HI; start_bbpx=None # ์‹œ์ž‘์  ํ›„๋ณดB: ๊ฐ€๊ฒฉ ์ตœ์ € + ๊ทธ BB
base_bbmin=HI # ํ›„๋ณดC(B์•ˆ): ๋ฒ ์ด์Šค ๊ตฌ๊ฐ„ ์ „์ฒด์˜ ์ตœ์ € BBํ•˜๋‹จ
life=None
peak_gap=-9.9
endlow_gap=0.0; endlow_ma60=HI; endlow_px=HI; endlow_bb60=None; endlow_bbpx=None; endlow_dt=None # ๋์ง€์ 
start_dt=None; mtn_s60=HI; mtn_sdt=None
prev_end60=None; peak_m60=0.0
dip_ma60=HI; dip_bb60=None; dip_px=HI; dip_bbpx=None; dip_dt=None; dip_gap=0.0; wave_peak_ma60=0.0; prev_mtn_peak=None; dip_peak=0.0
dip_start_dt=None; dip_start_i=None # DIP ์‹œ์ž‘์ผ/์‹œ์ž‘๋ด‰ (๊ธฐ๊ฐ„ ํŒ์ •: ์ผ๋ด‰=์ผ์ˆ˜, ๋ถ„๋ด‰=๋ด‰์ˆ˜)
dip_floor_logged=False # '์กฐ๊ธฐ์žฌ๋ฒ ์ด์Šค ๋ณด๋ฅ˜(์ƒ๋ช…์„ ๋ฐ”๋‹ฅ)' ์ด๋ฒคํŠธ 1ํšŒ ์ถœ๋ ฅ์šฉ
vlife=None; vseg_i=0 # ๊ฐ€์ƒ ์ƒ๋ช…์„  / ์ด ์ž‘์€์–ธ๋• ๋งค์ˆ˜ ์‹œ์ž‘ ์ธ๋ฑ์Šค(buys ๊ธฐ์ค€)
vbreak_dt=None # ๊ฐ€์ƒ์ƒ๋ช…์„  ์•„๋ž˜๋กœ ์ฒ˜์Œ ๋‚ด๋ ค๊ฐ„ ๋‚  (์œ ์˜ˆ ํŒ์ •์šฉ)
one_wave=False # ์ด๋ฒˆ ์œ„์•„๋ž˜๋Š” 1๋ฒˆ์งธ๋งŒ ๋งค์ˆ˜(๊นŠ์€ ๋ˆŒ๋ฆผ ๋’ค ์žฌ๊ฐœ)
dip_bbmin=HI # DIP ๊ตฌ๊ฐ„ ์ „์ฒด์˜ ์ตœ์ € BBํ•˜๋‹จ (DOUBLE_MTN_RELIFE: ์ƒˆ ์ƒ๋ช…์„  ํ›„๋ณด)
wave_confirmed=False; conf_logged=False; touch_logged=False # ์œ„์•„๋ž˜ ๋ณต๊ท€ ํ™•์ธ ์—ฌ๋ถ€(๊ฐ€์งœ ๋ณต๊ท€ ์ฐจ๋‹จ)
cross_up_dt=None # ์ด๋ฒˆ ์œ„์•„๋ž˜์—์„œ 60์„ ์ด 240์„  ์œ„(gap>0)๋กœ ์ฒ˜์Œ ์˜ฌ๋ผ๊ฐ„ ๋‚  (์‹œ๊ฐ„ ๊ธฐ๋ฐ˜ ํ™•์ธ์šฉ)
wave_hat=0.0 # ์ด๋ฒˆ ์œ„์•„๋ž˜์—์„œ 60์„ ์ด 240์„  ์œ„๋กœ ๋งŒ๋“  ์ตœ๋Œ€ gap (๋ชจ์ž ๋ด‰์šฐ๋ฆฌ ๊ฐ์ง€ โ€” ์Šค์นจ ์˜คํŒ ๋ฐฉ์ง€)
wave_fresh=True # ์ด ์œ„์•„๋ž˜๋ฅผ ์—ฐ ์‚ฌ๊ฑด์ด '๋™์‚ฐ์„ฑ๊ณต'์ธ๊ฐ€(=๋งŽ์ด ์˜ค๋ฅธ ๋’ค ๋ณต๊ท€) โ€” ๋ชจ์ž ์ฒดํฌ ๋Œ€์ƒ ์—ฌ๋ถ€
life_broken=False # PEAK ์ค‘ ์ƒ๋ช…์„  ์ดํƒˆํ–ˆ๋Š”์ง€ (LIFELINE_KEEP_MTN: ์†์ ˆํ–ˆ์ง€๋งŒ ๋™์‚ฐ์€ ๊ณ„์† ๊ด€์ฐฐ)
wave=0; armed=False; b240={}; flag240up=False; mtn_count=0 # mtn_count: ์œ„์•„๋ž˜ ๋งค์ˆ˜ํ•œ ๋™์‚ฐ ์ˆœ๋ฒˆ(์ฒซ=1, ๋”๋ธ”=2, ํŠธ๋ฆฌํ”Œ=3โ€ฆ)
hold=False; lwl=None; lwl_c=None; daily_open=False; pend=[]; up_peak=0.0; up_days=0
held=False # ์‹ค๋ณด์œ (ํŒ ์  ์—†๋Š” ๋ฌผ๋Ÿ‰ ์žˆ์Œ) โ€” hold(๋งค์ˆ˜๊ตญ๋ฉด์ค‘)์™€ ๋ณ„๊ฐœ. ์‹ค์ œ ์ „๋Ÿ‰๋งค๋„ ๋•Œ๋งŒ False
prev_above=None
ev=[]; buys=[]; mts=[]; blocked_n=[0]
def _in_vcp(dt_):
if not vcp_wins: return False
ds_ = str(dt_)[:10]
for w in vcp_wins:
if ds_ >= w['start'] and (w['end'] is None or ds_ < w['end']):
return True
return False
def _buy(dt_, px_, amt_, lbl_, force=False):
# force=True โ†’ ์ „๋Ÿ‰๋งค๋„ 90์ผ ์ฟจ๋‹ค์šด ๋ฌด์‹œ(์ผ๋ด‰ 1ยท2๋ฒˆ์งธ ์œ„์•„๋ž˜ ์ตœ์šฐ์„ ). ๋ถ„๋ด‰/๊ธฐํƒ€๋Š” force=False๋ผ ์ฟจ๋‹ค์šด ์œ ์ง€.
_why = None if force else _blocked(dt_)
if _why:
blocked_n[0]+=1
ev.append((dt_,' โ””๋งค์ˆ˜๋ณด๋ฅ˜',_why))
return False
if _in_vcp(dt_): # โ˜… VCP ์‚ด์•„์žˆ๋Š” ๊ตฌ๊ฐ„ โ†’ 3๋ฐฐ
amt_ = amt_ * VCP_MULT
lbl_ = lbl_ + f' ร—{VCP_MULT}(VCP)'
nonlocal held
buys.append((dt_,px_,amt_,lbl_)); held=True; return True
for i in range(len(df)):
if i < (MA_60 if EARLY_EXTEND else MA_240):
# โ˜… ์ƒ์žฅ ์ดˆ๊ธฐ(60์„  ์ƒ๊ธฐ๊ธฐ ์ „) ๊ธ‰๋ฝ BBํ•˜๋‹จ๋„ ์ƒ๋ช…์„  ํ›„๋ณด๋กœ ์ถ”์ .
# 60์„ ์ด ์—†์–ด ์ด ๋ด‰๋“ค์€ ๊ด€์ฐฐ์—์„œ ๋น ์ง€์ง€๋งŒ, BBํ•˜๋‹จ(20๋ด‰)์€ ์ด๋ฏธ ์žˆ์œผ๋ฏ€๋กœ ์ตœ์ €๊ฐ’์€ ๊ธฐ์–ตํ•ด ๋‘ .
# (166090: 2017-05-31 BBํ•˜๋‹จ 7,473์ด ์ฒซ ๋™์‚ฐ ์ƒ๋ช…์„  ํ›„๋ณด์—์„œ ๋น ์ง€๋˜ ๋ฌธ์ œ ํ•ด๊ฒฐ)
if EARLY_EXTEND and LIFELINE_BASE_MIN:
_bl0 = float(bbl.iloc[i])
if not np.isnan(_bl0) and _bl0 < base_bbmin: base_bbmin = _bl0
continue
d=idx[i]; C=float(c.iloc[i]); L=float(lo.iloc[i])
m60=float(ma60.iloc[i]); m240=float(ma240.iloc[i]); bl=float(bbl.iloc[i])
if np.isnan(m60) or np.isnan(m240) or np.isnan(bl): continue
above = m60 > m240
gap = m60/m240 - 1
# โ˜… [์ธก์ •์šฉ] ๊ณ ์•„๋ฌผ๋Ÿ‰ ๋งค๋„: ์‚ฌ์ดํด์ข…๋ฃŒ๋กœ hold=False ๋์ง€๋งŒ ํŒ ์  ์—†๋Š” ๋ฌผ๋Ÿ‰(held)์ด ๋‚จ์•„
# ์ƒ๋ช…์„  ๋ฐ‘์œผ๋กœ ๊ฐ€๋ฉด ๊ทธ๋•Œ ์ „๋Ÿ‰๋งค๋„. WAIT ๋‹จ๊ณ„ ์ „์šฉ(๋‹ค๋ฅธ ๋‹จ๊ณ„๋Š” ์ž์ฒด ์ƒ๋ช…์„  ์ฒดํฌ๊ฐ€ ์žˆ์Œ).
if SELL_ORPHAN_ON_CYCLEEND and tf in SELL_ORPHAN_TFS and held and (not hold) and phase=='WAIT' and life and L < life:
ev.append((d,'์ƒ๋ช…์„ ์ดํƒˆโ†’์ „๋Ÿ‰๋งค๋„(์‚ฌ์ดํด์ข…๋ฃŒํ›„ ์ž”์—ฌ๋ฌผ๋Ÿ‰)',
f'์ €๊ฐ€ {L:,.0f} < ์ƒ๋ช…์„  {life:,.0f} โ€” ์‚ฌ์ดํด ๋๋‚œ ๋’ค ๋‚จ์•„์žˆ๋˜ ๋ฌผ๋Ÿ‰ ์†์ ˆ'))
held=False
# โ˜… ๊ฐ€์ƒ ์ƒ๋ช…์„  ์ดํƒˆ: ์ด ์ž‘์€์–ธ๋•์—์„œ ์‚ฐ ๋ฌผ๋Ÿ‰๋งŒ ๋งค๋„ + ์žฌ๋ฒ ์ด์Šค(๋™์‚ฐ๋ถ€ํ„ฐ ๋‹ค์‹œ)
if VIRTUAL_LIFELINE and vlife and phase in ('WAVE','DIP'):
if C >= vlife:
vbreak_dt=None # ์ข…๊ฐ€๊ฐ€ ์„  ์œ„๋กœ ํšŒ๋ณต โ†’ ์œ ์˜ˆ ๋ฆฌ์…‹(์—†๋˜ ์ผ)
elif L < vlife and vbreak_dt is None:
vbreak_dt=d # ์ฒ˜์Œ ๊นฌ ๋‚  ๊ธฐ๋ก
if (VIRTUAL_LIFELINE and vlife and phase in ('WAVE','DIP') and vbreak_dt is not None
and (d - vbreak_dt).days >= VLIFE_GRACE_DAYS):
_sq = buys[vseg_i:]
_amt = sum(x[2] for x in _sq)
_sh = sum(x[2]/x[1] for x in _sq)
_avg = (_amt/_sh) if _sh else 0
ev.append((d,'๊ฐ€์ƒ์ƒ๋ช…์„ ์ดํƒˆโ†’๊ตฌ๊ฐ„๋งค๋„',
f'์ €๊ฐ€ {L:,.0f} < ๊ฐ€์ƒ์ƒ๋ช…์„  {vlife:,.0f} โ€” ์ด ์ž‘์€์–ธ๋• ๋งค์ˆ˜๋ถ„๋งŒ ๋งค๋„(์ง„์งœ ์ƒ๋ช…์„  {life:,.0f}๋Š” ์œ ์ง€) '
f'| ๊ตฌ๊ฐ„๋งค์ˆ˜ {len(_sq)}ํšŒ {_amt:,.0f}์› ํ‰๋‹จ {_avg:,.0f} | {VLIFE_GRACE_DAYS}์ผ ์œ ์˜ˆ ๋‚ด๋‚ด ํšŒ๋ณต ์‹คํŒจ({str(vbreak_dt)[:10]}๋ถ€ํ„ฐ) โ†’ ๋™์‚ฐ๋ถ€ํ„ฐ ๋‹ค์‹œ'))
vlife=None; vseg_i=len(buys); vbreak_dt=None
mtn_s60 = m60; mtn_sdt = d
life = min(bl, life if life else bl)
phase='PEAK'; peak_gap=gap; peak_m60=m60; vlife=None; vbreak_dt=None; vseg_i=len(buys) # โ˜…์ƒˆ ์‚ฐ ๊ทธ๋ฆฌ๊ธฐ ์‹œ์ž‘ โ†’ ์˜› ๊ฐ€์ƒ์ƒ๋ช…์„  ํ๊ธฐ
endlow_gap=0.0; endlow_ma60=HI; endlow_px=HI
hold=False; wave=0; armed=False; b240={}; flag240up=False
daily_open=False; pend=[]; up_peak=0.0; up_days=0
mtn_count=0; life_broken=False
wave_confirmed=False; conf_logged=False; touch_logged=False; cross_up_dt=None; wave_hat=0.0
continue
if phase=='WAIT':
# โ˜… ๋ฒ ์ด์Šค ๊ตฌ๊ฐ„ ์ „์ฒด์˜ ์ตœ์ € BBํ•˜๋‹จ ์ถ”์  (above/below ๋ฌด๊ด€ โ€” ํ‰ํ–‰์—ฐ์žฅ ๊ตฌ๊ฐ„ ๋Œ€์‘)
if bl < base_bbmin: base_bbmin = bl
# 60์„ ์ด 240 ์•„๋ž˜ โ†’ '60์„  ์ตœ์ €์ ' ์ถ”์  (๊ฐ€๊ฒฉ ๊ธ‰๋ฝ๋ด‰ ์•„๋‹˜)
if not above:
if m60 < start_ma60: # 60์„  ์ตœ์ €์ 
start_ma60=m60; start_bb60=bl; start_dt=d
if L < start_pxlow: # ๊ฐ€๊ฒฉ ์ตœ์ €์ 
start_pxlow=L; start_bbpx=bl
# 60์„ ์ด 240 ์œ„๋กœ ํฌ๋กœ์Šค โ†’ ์‚ฐ๋ด‰์šฐ๋ฆฌ ์‹œ์ž‘
if prev_above is False and above and start_bb60 is not None:
bb_low = min(start_bb60, start_bbpx) # โ˜… ๋‘˜ ์ค‘ ๋” ๋‚ฎ์€ BBํ•˜๋‹จ (์ŠคํŒŒ์ดํฌ ํ›„ํ–‰๊ฐ’ ์ž๋™ ํšŒํ”ผ)
if LIFELINE_BASE_MIN and base_bbmin < HI:
bb_low = min(bb_low, base_bbmin) # โ˜…B์•ˆ: ๋ฒ ์ด์Šค ์ „์ฒด ์ตœ์ € BBํ•˜๋‹จ๊นŒ์ง€ ๊ณ ๋ ค
life=bb_low*(1-LIFELINE_MARGIN)
base_bbmin=HI # ์ƒˆ ๋™์‚ฐ ์‹œ์ž‘ โ†’ ๋ฒ ์ด์Šค ์ถ”์  ๋ฆฌ์…‹
mtn_s60=start_ma60; mtn_sdt=start_dt # ์ด ๋™์‚ฐ์˜ ์‹œ์ž‘์  ๊ณ ์ •
life_broken=False
phase='PEAK'; peak_gap=gap; peak_m60=m60; vlife=None; vbreak_dt=None; vseg_i=len(buys) # โ˜…์ƒˆ ์‚ฐ ๊ทธ๋ฆฌ๊ธฐ ์‹œ์ž‘ โ†’ ์˜› ๊ฐ€์ƒ์ƒ๋ช…์„  ํ๊ธฐ
endlow_gap=0.0; endlow_ma60=HI; endlow_px=HI
ev.append((d,'์‚ฐ๋ด‰์šฐ๋ฆฌ์‹œ์ž‘',f'๋™์‚ฐ ์‹œ์ž‘์  {str(mtn_sdt)[:10]} (60์„  {mtn_s60:,.0f}) / ์ƒ๋ช…์„  {life:,.0f}'))
elif phase=='PEAK':
if peak_gap < gap: peak_gap=gap
if above and m60 > peak_m60: peak_m60=m60
_life_hit = bool(life) and L < life
if _life_hit and not LIFELINE_KEEP_MTN:
ev.append((d,'์ƒ๋ช…์„ ์ดํƒˆโ†’์‹คํŒจ',f'์ €๊ฐ€ {L:,.0f} < ์ƒ๋ช…์„  {life:,.0f}'))
phase='WAIT'; start_ma60=HI; start_bb60=None; start_pxlow=HI; start_bbpx=None
else:
# โ˜… LIFELINE_KEEP_MTN: ์†์ ˆ(์ „๋Ÿ‰๋งค๋„)์€ ํ•˜๋˜ ๋™์‚ฐ ๊ตฌ์กฐ๋Š” ํ๊ธฐํ•˜์ง€ ์•Š๊ณ  ๊ณ„์† ๊ด€์ฐฐ.
# ๋‚˜์ค‘์— 60์„ ์ด 240 ์œ„๋กœ ๋ณต๊ท€ํ•  ๋•Œ '๋์ง€์  60์„  > ์‹œ์ž‘์  60์„ '์ด๋ฉด ๋™์‚ฐ์„ฑ๊ณต ์ธ์ •.
if _life_hit and not life_broken:
# โ˜… hold ์ฒดํฌ ํ•„์ˆ˜: ์žฌ๋ฒ ์ด์Šค ์งํ›„ ๋“ฑ ๋ณด์œ ๋ฌผ๋Ÿ‰์ด ์—†์œผ๋ฉด '์ „๋Ÿ‰๋งค๋„'๊ฐ€ ์•„๋‹ˆ๋ผ ๋‹จ์ˆœ ์ดํƒˆ ๊ธฐ๋ก.
# (์•ˆ ๊ทธ๋Ÿฌ๋ฉด full_debug๊ฐ€ ์œ ๋ น ์†์ ˆ์„ P&L์— ๋„ฃ์Œ)
_t = '์ƒ๋ช…์„ ์ดํƒˆโ†’์ „๋Ÿ‰๋งค๋„(๋™์‚ฐ์œ ์ง€)' if hold else '์ƒ๋ช…์„ ์ดํƒˆ(๋ฏธ๋ณด์œ ยท๋™์‚ฐ์œ ์ง€)'
if hold: held=False # ์‹ค์ œ ์ „๋Ÿ‰๋งค๋„ โ†’ ์‹ค๋ณด์œ  ์ฒญ์‚ฐ
life_broken=True; hold=False
ev.append((d,_t,
f'์ €๊ฐ€ {L:,.0f} < ์ƒ๋ช…์„  {life:,.0f} โ€” '
+ ('์†์ ˆํ•˜๋˜' if _t.startswith('์ƒ๋ช…์„ ์ดํƒˆโ†’์ „๋Ÿ‰') else '๋ณด์œ ๋ฌผ๋Ÿ‰ ์—†์Œ(๋งค๋„ ์•„๋‹˜).')
+ ' ๋™์‚ฐ ํ๊ธฐ ์•ˆ ํ•จ(๋์ง€์  60์„ ์ด ์‹œ์ž‘์ ๋ณด๋‹ค ๋†’์œผ๋ฉด ๋™์‚ฐ์„ฑ๊ณต ์ธ์ •)'))
if not above: # ๋์ง€์  dip
if gap < endlow_gap: endlow_gap=gap
if m60 < endlow_ma60: endlow_ma60=m60; endlow_bb60=bl; endlow_dt=d
if L < endlow_px: endlow_px=L; endlow_bbpx=bl
elif above and endlow_ma60 < HI: # 60์„  240 ์œ„๋กœ ๋ณต๊ท€ โ†’ ์„ฑํŒจ ํŒ์ •
big_peak = peak_gap >= pmin
deep_end = endlow_gap <= -emin
higher = endlow_ma60 > mtn_s60 # 60์„  ๋” ๋†’์€ ์ €์ 
if big_peak and deep_end and higher:
# โ˜… ์ƒ๋ช…์„ ์„ ๊นผ์—ˆ์–ด๋„(life_broken) ๋์ง€์  60์„ ์ด ์‹œ์ž‘์ ๋ณด๋‹ค ๋†’์œผ๋ฉด = ๊ตฌ์กฐ ์œ ์ง€ โ†’ ๋™์‚ฐ์„ฑ๊ณต.
# ๋‹จ ์ƒ๋ช…์„ ์€ ์ด๋ฒˆ ์ €์ (๋์ง€์  BBํ•˜๋‹จ)์œผ๋กœ ์žฌ์„ค์ • (์˜› ์ƒ๋ช…์„ ์€ ์ด๋ฏธ ๊นจ์กŒ์œผ๋ฏ€๋กœ ๋ฌด์˜๋ฏธ).
_revived = life_broken
if life_broken:
life = min(endlow_bb60, endlow_bbpx)*(1-LIFELINE_MARGIN)
life_broken=False
mtn_count=1 # โ˜… ์ƒˆ ์‹œํ€€์Šค์˜ ์ฒซ ๋™์‚ฐ โ†’ ์œ„์•„๋ž˜ ๋งค์ˆ˜ ์‹œ์ž‘
vlife=None; vbreak_dt=None; vseg_i=len(buys) # โ˜…์ƒˆ ๋™์‚ฐ์„ฑ๊ณต = ์ƒˆ ์‹œํ€€์Šค โ†’ ์˜› ๊ฐ€์ƒ์ƒ๋ช…์„  ํ๊ธฐ
wave_fresh=True # ๋™์‚ฐ ์™„์„ฑ ์งํ›„ ๋ณต๊ท€ โ†’ ๋ชจ์ž ์ฒดํฌ ๋Œ€์ƒ
one_wave=False # ์ƒˆ ๋™์‚ฐ โ†’ 1ยท2๋ฒˆ์งธ ๋‹ค ๋งค์ˆ˜
wave_confirmed=False; conf_logged=False; touch_logged=False; cross_up_dt=None; wave_hat=0.0 # ๋ณต๊ท€ํ™•์ธ ๋ฆฌ์…‹
phase='WAVE'; wave=0; armed=False; b240={}; flag240up=False; hold=False; lwl=None; lwl_c=None; daily_open=False; pend=[]; up_peak=0.0; up_days=0; pend=[]
mts.append({'start':d,'s60':mtn_s60,'e60':endlow_ma60,'life':life,'endpx':endlow_px,'sdt':mtn_sdt,'edt':endlow_dt})
prev_end60=endlow_ma60 # โ˜… ๋์ง€์ (1) 60์„  โ€” ๋”๋ธ”๋™์‚ฐ ๋์ง€์  ๋น„๊ต ๊ธฐ์ค€
prev_mtn_peak=peak_m60; wave_peak_ma60=0.0 # โ˜… ์ด ๋™์‚ฐ์˜ ํ”ผํฌ(60์„  ์ตœ๊ณ ) = ๋‹ค์Œ ํŒ์ •์˜ ๋น„๊ต ๊ธฐ์ค€
ev.append((d,'๋™์‚ฐ์„ฑ๊ณตโ†’์œ„์•„๋ž˜์‹œ์ž‘',
f'์‹œ์ž‘์  {str(mtn_sdt)[:10]}({mtn_s60:,.0f}) โ†’ ๋์ง€์  {str(endlow_dt)[:10]}({endlow_ma60:,.0f}) โ†‘ | ๋ด‰์šฐ๋ฆฌ+{peak_gap*100:.1f}% [1๋ฒˆ์งธ ๋™์‚ฐ]'
+ (f' โ˜…์ƒ๋ช…์„  ๊นผ์—ˆ์ง€๋งŒ ๋์ง€์  60์„ ์ด ์‹œ์ž‘์ ๋ณด๋‹ค ๋†’์•„ ๊ตฌ์กฐ์œ ์ง€ โ†’ ๋™์‚ฐ์„ฑ๊ณต ์ธ์ • / ์ƒ๋ช…์„  ์žฌ์„ค์ • {life:,.0f}' if _revived else '')))
elif not higher:
# ๋์ง€์ ์ด ์‹œ์ž‘์ ๋ณด๋‹ค ๋‚ฎ์Œ = ๋” ๋‚ฎ์€ ์ €์ (๊ตฌ์กฐ ๋ถ•๊ดด) โ†’ ๊ทธ ์ €์ ์œผ๋กœ ์žฌ๋ฒ ์ด์Šค
_os60=mtn_s60; _osdt=mtn_sdt # ์‹คํŒจํ•œ ์‚ฐ์˜ ์‹œ์ž‘์ (๋น„๊ต์šฉ, ๋ฎ์–ด์“ฐ๊ธฐ ์ „ ์ €์žฅ)
_drop=(endlow_ma60/_os60-1)*100 if _os60 else 0
mtn_s60 = endlow_ma60; mtn_sdt = endlow_dt
life = min(endlow_bb60, endlow_bbpx)*(1-LIFELINE_MARGIN)
mtn_count=0 # ๊ตฌ์กฐ ๋ถ•๊ดด โ†’ ์‹œํ€€์Šค ๋ฆฌ์…‹
life_broken=False
ev.append((d,'๋™์‚ฐ์‹คํŒจโ†’์žฌ๋ฒ ์ด์Šค(๋”๋‚ฎ์€์ €์ )',
f'์ด ์‚ฐ ์‹คํŒจ โ€” ์‹œ์ž‘ ๋ฐ”๋‹ฅ {str(_osdt)[:10]}(60์„  {_os60:,.0f})๋ณด๋‹ค ๋ ๋ฐ”๋‹ฅ {str(endlow_dt)[:10]}(60์„  {endlow_ma60:,.0f})์ด {_drop:+.1f}% ๋” ๋‚ฎ์Œ(=๋” ๋‚ฎ์€ ์ €์ , ๊ตฌ์กฐ ๋ถ•๊ดด). ์ด ๋‚ฎ์€ ๋ ๋ฐ”๋‹ฅ์„ ์ƒˆ ์‹œ์ž‘์ ์œผ๋กœ ์‚ฐ ๋‹ค์‹œ ๊ทธ๋ฆผ / ์ƒˆ ์ƒ๋ช…์„  {life:,.0f}'))
peak_gap=gap; peak_m60=m60; endlow_gap=0.0; endlow_ma60=HI; endlow_px=HI # ์ƒˆ ์‚ฐ๋ด‰์šฐ๋ฆฌ๋กœ
else:
# ๋ด‰์šฐ๋ฆฌ/๋์ง€์ ์ด ์ž‘์Œ(๋…ธ์ด์ฆˆ) โ†’ ๋ฌด์‹œํ•˜๊ณ  ๊ฐ™์€ PEAK ์œ ์ง€
why=[]
if not big_peak: why.append(f'๋ด‰์šฐ๋ฆฌ+{peak_gap*100:.2f}%<{pmin*100:.1f}%')
if not deep_end: why.append(f'๋์ง€์ {endlow_gap*100:.2f}%>-{emin*100:.1f}%')
ev.append((d,'๋™์‚ฐ์•„๋‹˜(๋…ธ์ด์ฆˆ/์ž‘์Œ)โ†’๋ฌด์‹œ',' / '.join(why)))
endlow_gap=0.0; endlow_ma60=HI; endlow_px=HI
elif phase=='WAVE':
if life and L < life:
tag='์ƒ๋ช…์„ ์ดํƒˆโ†’์ „๋Ÿ‰๋งค๋„' if hold else '์ƒ๋ช…์„ ์ดํƒˆโ†’๋งค๋„'
ev.append((d,tag,f'์ €๊ฐ€ {L:,.0f} < ์ƒ๋ช…์„  {life:,.0f}'))
if hold: held=False # ์‹ค์ œ ์ „๋Ÿ‰๋งค๋„ โ†’ ์‹ค๋ณด์œ  ์ฒญ์‚ฐ
phase='WAIT'; hold=False; start_ma60=HI; start_bb60=None; start_pxlow=HI; start_bbpx=None
elif m60 < m240*(1-CYCLE_END):
ev.append((d,'์‚ฌ์ดํด์ข…๋ฃŒ',f'60MA {m60:,.0f} < 240 -{CYCLE_END*100:.0f}%'))
phase='WAIT'; hold=False; start_ma60=HI; start_bb60=None; start_pxlow=HI; start_bbpx=None
elif (daily or MINBAR_STATE) and m60 < m240*(1-DOUBLE_MTN_PCT):
# โ˜… ๋”๋ธ”๋™์‚ฐ ํ›„๋ณด: ์œ„์•„๋ž˜ ์ค‘ 60์„ ์ด 240์„  -2% ์•„๋ž˜๋กœ โ†’ ๋์ง€์ (2) ์ถ”์  ์‹œ์ž‘ (์ผ๋ด‰+๋ถ„๋ด‰)
# ๋‚˜์ค‘์— 60์„ ์ด 240 ์œ„๋กœ ๋ณต๊ท€ํ•  ๋•Œ ํŒ์ •:
# ๋์ง€์ (2) < ๋์ง€์ (1) โ†’ ๋”๋ธ”๋™์‚ฐ ํ™•์ • (๋งค์ˆ˜ ์ค‘๋‹จ, ์œ„์•„๋ž˜ ๋ฆฌ์…‹)
# ๋์ง€์ (2) โ‰ฅ ๋์ง€์ (1) โ†’ ๊ทธ๋ƒฅ ๋ˆŒ๋ฆผ (์œ„์•„๋ž˜ ๊ณ„์†)
phase='DIP'
dip_ma60=m60; dip_bb60=bl; dip_px=L; dip_bbpx=bl; dip_dt=d; dip_gap=gap
dip_start_dt=d; dip_start_i=i # โ˜… DIP ์‹œ์ž‘์ผ/๋ด‰ (์žฌ๋ฒ ์ด์Šค ๊ธฐ๊ฐ„ํŒ์ •: ์ผ๋ด‰=์ผ์ˆ˜/๋ถ„๋ด‰=๋ด‰์ˆ˜)
dip_bbmin=bl # โ˜… DIP ๊ตฌ๊ฐ„ ์ตœ์ € BBํ•˜๋‹จ ์ถ”์  ์‹œ์ž‘
dip_floor_logged=False
dip_peak=wave_peak_ma60 # DIP ์ง์ „๊นŒ์ง€ = ์ด๋ฒˆ ์‚ฐ์˜ ํ”ผํฌ ํ™•์ •
ev.append((d,'๋์ง€์ (2) ์ถ”์ ์ค‘โ†’๋งค์ˆ˜์ค‘๋‹จ',
f'๋งค์ˆ˜ ์ž ์‹œ ๋ฉˆ์ถค โ€” 60์„  {m60:,.0f}์ด 240์„  ๋ฐ‘์œผ๋กœ -{DOUBLE_MTN_PCT*100:.0f}% ๋„˜๊ฒŒ ๊บพ์ž„. ์—ฌ๊ธฐ๊ฐ€ ์ด๋ฒˆ ๋ˆŒ๋ฆผ ์‹œ์ž‘({str(d)[:10]}). ๋”๋ธ”๋™์‚ฐ์ธ์ง€ ๋ฌด๋„ˆ์ง„ ๊ฑด์ง€ ์ง€์ผœ๋ณด๋Š” ์ค‘'))
elif above or (daily and WAVE_TOUCH_BUY and gap <= 0):
# above=60>240 (์ •์ƒ ์œ„์•„๋ž˜) / gap<=0 = ์Šค์นจ๊ตฌ๊ฐ„(240์„  ์œ„๋กœ ๋ชป ์˜ฌ๋ผ๊ฐ, DIP ์ „) โ†’ ๋ฐ”๋‹ฅ ๋‹ค์ง€๊ธฐ๋กœ ๋ณด๊ณ  ๋งค์ˆ˜ ํ—ˆ์šฉ
if m60 > wave_peak_ma60: wave_peak_ma60 = m60 # ํ˜„์žฌ ์œ„์•„๋ž˜ ๊ตฌ๊ฐ„์˜ 60์„  ์ตœ๊ณ (ํ”ผํฌ)๋งŒ ์ถ”์ 
if gap > wave_hat: wave_hat = gap # โ˜… ์ด ์œ„์•„๋ž˜์—์„œ ๋งŒ๋“  ์ตœ๋Œ€ gap (๋ชจ์ž ๋ด‰์šฐ๋ฆฌ ๊ฐ์ง€)
if daily:
# ===== ์ผ๋ด‰: '์œ„์•„๋ž˜' = 60์„  ์•„๋ž˜๋กœ ๋‚ด๋ ค๊ฐ„ ๊ตฌ๊ฐ„(์—ํ”ผ์†Œ๋“œ) =====
bnc = (i>0 and C > float(c.iloc[i-1])) # ๋ฐ˜๋“ฑ์ผ(์ข…๊ฐ€>์ „์ผ์ข…๊ฐ€) โ€” ๊นŠ์€ ๊ตฌ๊ฐ„ ๋งค์ˆ˜ ๊ฒŒ์ดํŠธ
# โ˜… ๋ณต๊ท€ ํ™•์ธ: 60์„ ์ด 240์„  ์œ„๋กœ WAVE_CONFIRM_GAP ๋„๋‹ฌํ•ด์•ผ ๋งค์ˆ˜ ์‹œ์ž‘ (๊ฐ€์งœ ๋ณต๊ท€=๋ชจ์žํ˜•ํƒœ ์ฐจ๋‹จ)
if WAVE_CONFIRM_GAP and not wave_confirmed:
if gap >= WAVE_CONFIRM_GAP:
wave_confirmed = True
ev.append((d,'์œ„์•„๋ž˜ ํ™•์ธ(์ง„์งœ ๋ณต๊ท€)',
f'60์„ ์ด 240์„  +{gap*100:.1f}% โ‰ฅ +{WAVE_CONFIRM_GAP*100:.0f}% ๋„๋‹ฌ โ†’ ์œ„์•„๋ž˜ ๋งค์ˆ˜ ์‹œ์ž‘'))
elif gap > 0:
# gap์ด +7% ๋ฏธ๋‹ฌ์ด๋ผ๋„ 60์„ ์ด 240์„  ์œ„์—์„œ '์—ฐ์† ์ƒ์กด'ํ•œ ์ผ์ˆ˜๋กœ ํ™•์ธ (์‹œ๊ฐ„ ๊ธฐ๋ฐ˜, OR)
if cross_up_dt is None: cross_up_dt = d # 240 ์œ„๋กœ ์˜ฌ๋ผ์˜จ ์ฒซ๋‚  ์•ต์ปค
surv = (d - cross_up_dt).days
if WAVE_CONFIRM_DAYS and surv >= WAVE_CONFIRM_DAYS:
wave_confirmed = True
ev.append((d,'์œ„์•„๋ž˜ ํ™•์ธ(์‹œ๊ฐ„์ƒ์กด)',
f'60์„ ์ด 240์„  ์œ„(gap {gap*100:+.1f}%)์—์„œ {surv}์ผ ์—ฐ์† ์ƒ์กด โ‰ฅ {WAVE_CONFIRM_DAYS}์ผ โ†’ ์ง„์งœ ๋ณต๊ท€(๋ชจ์ž ์•„๋‹˜) โ†’ ์œ„์•„๋ž˜ ๋งค์ˆ˜ ์‹œ์ž‘'))
elif not conf_logged:
conf_logged = True
ev.append((d,'๋ณต๊ท€ํ™•์ธ ๋Œ€๊ธฐ(๋งค์ˆ˜๋ณด๋ฅ˜)',
f'60์„  +{gap*100:.1f}% < +{WAVE_CONFIRM_GAP*100:.0f}% โ€” ๊ฐ€์งœ ๋ณต๊ท€(๋ชจ์ž) ๊ฐ€๋Šฅ์„ฑ, gap๋„๋‹ฌ or {WAVE_CONFIRM_DAYS}์ผ์ƒ์กด๊นŒ์ง€ ๋งค์ˆ˜ ์•ˆ ํ•จ'))
else:
cross_up_dt = None # 240 ์•„๋ž˜๋กœ(gapโ‰ค0) ๋˜๊บพ์ž„ โ†’ ์‹œ๊ฐ„ ์นด์šดํ„ฐ ๋ฆฌ์…‹(๊ฐ€์งœ ๊ฐ€๋Šฅ์„ฑ)
# โ˜… ์Šค์นจ๊ตฌ๊ฐ„(gap โ‰ค 0): ๋ชจ์ž๊ฐ€ '์•„์ง ์•ˆ ์ƒ๊ธด' ๊ฒƒ = ๋ฐ”๋‹ฅ ๋‹ค์ง€๊ธฐ โ†’ ํ™•์ธ ์—†์ด๋„ ๋งค์ˆ˜ ํ—ˆ์šฉ.
# ๋‹จ ์ด ์œ„์•„๋ž˜์—์„œ ์ด๋ฏธ ๋ชจ์ž ๋ด‰์šฐ๋ฆฌ(gap โ‰ฅ WAVE_HAT_PCT)๋ฅผ ๋งŒ๋“  ๋’ค ๋‚ด๋ ค์˜จ ๊ฑฐ๋ฉด
# ๊ทธ๊ฑด ๋ฐ”๋‹ฅ์ด ์•„๋‹ˆ๋ผ ๋ชจ์ž ํ•˜๊ฐ•๋‹ค๋ฆฌ โ†’ ์Šค์นจ ๋งค์ˆ˜ ๊ธˆ์ง€(ํ™•์ธ ๋Œ€๊ธฐ ์œ ์ง€).
_hat_formed = (WAVE_HAT_PCT and wave_hat >= WAVE_HAT_PCT
and ((not WAVE_HAT_ONLY_FRESH) or wave_fresh))
_touch = (WAVE_TOUCH_BUY and gap <= 0 and not _hat_formed)
if WAVE_HAT_PCT and gap <= 0 and _hat_formed and not conf_logged:
conf_logged = True
ev.append((d,'๋ณต๊ท€ํ™•์ธ ๋Œ€๊ธฐ(๋ชจ์žํ•˜๊ฐ•)',
f'์ด ์œ„์•„๋ž˜์—์„œ 60์„ ์ด 240์„  +{wave_hat*100:.1f}%๊นŒ์ง€ ์˜ฌ๋ž๋‹ค ๋‚ด๋ ค์˜ด(๋ชจ์ž ๋ด‰์šฐ๋ฆฌ) โ€” ์Šค์นจ ์•„๋‹˜, ๋งค์ˆ˜ ๋ณด๋ฅ˜'))
_can_buy = (not WAVE_CONFIRM_GAP) or wave_confirmed or _touch
if _touch and not touch_logged:
touch_logged = True
ev.append((d,'์Šค์นจ๊ตฌ๊ฐ„(๋ฐ”๋‹ฅ) ๋งค์ˆ˜ํ—ˆ์šฉ',
f'60์„  {gap*100:+.1f}% โ‰ค 0 (240์„  ์Šค์นจ/์•„๋ž˜) โ€” ๋ชจ์ž ์•„๋‹˜ โ†’ ์œ„์•„๋ž˜ ๋งค์ˆ˜ ์ง„ํ–‰'))
if daily_open:
if C < m60:
# ์•„๋ž˜ ๊ตฌ๊ฐ„ ์ง„ํ–‰ ์ค‘ โ†’ ๋งค์ผ ๋งค์ˆ˜ (์œ„๋กœ ํŠ€์—ˆ๋‹ค ์™€๋„ ๊ฐ™์€ ๊ตฌ๊ฐ„)
amt = d1 if wave==1 else d2; hold=True
if WAVE_BUY_FLOOR and C < m240*(1-WAVE_BUY_FLOOR) and not bnc:
# 240์„  -8% ์•„๋ž˜ + ํ•˜๋ฝ(๋ฐ˜๋“ฑ ์•„๋‹˜) โ†’ ๋ณด๋ฅ˜. ๋ฐ˜๋“ฑํ•˜๋ฉด ๊ทธ๋‚  ๋งค์ˆ˜ ์žฌ๊ฐœ
ev.append((d,' โ””๋งค์ˆ˜๋ณด๋ฅ˜(240 -{:.0f}%ยท๋ฐ˜๋“ฑ๋Œ€๊ธฐ)'.format(WAVE_BUY_FLOOR*100),
f'์ข…๊ฐ€ {C:,.0f} < 240 -{WAVE_BUY_FLOOR*100:.0f}% ({(C/m240-1)*100:+.1f}%) ํ•˜๋ฝ์ค‘ โ€” ์œ„์•„๋ž˜ ์†์•˜๋‹ค, ๋ฐ˜๋“ฑ์‹œ ์žฌ๊ฐœ'))
elif _buy(d,C,amt,f'{wave}๋ฒˆ ์ผ๋ด‰๋งค์ˆ˜', force=(daily and WAVE_OVERRIDE_COOLDOWN)):
_deeptag = ' (240 -{:.0f}%โ†“ ๋ฐ˜๋“ฑํ™•์ธ)'.format(WAVE_BUY_FLOOR*100) if (WAVE_BUY_FLOOR and C < m240*(1-WAVE_BUY_FLOOR)) else ''
ev.append((d,' โ””๋งค์ˆ˜',f'{amt:,}์› (์ผ๋ด‰ ๋ฐ˜๋ณต){_deeptag}'))
up_peak=0.0; up_days=0 # ์•„๋ž˜๋กœ ๋‚ด๋ ค์˜ค๋ฉด ์œ„ ์ตœ๊ณ ์น˜/์—ฐ์†์ผ์ˆ˜ ๋ฆฌ์…‹
else:
# 60์„  ์œ„ โ†’ ์ตœ๊ณ  ์ƒ์Šน๋ฅ  + ์—ฐ์†์ผ์ˆ˜ ์ถ”์  (๋งค์ˆ˜๋Š” ์‰ผ)
rise = C/m60 - 1
if rise > up_peak: up_peak = rise
up_days += 1
if up_peak >= WAVE_EXIT_PCT and up_days >= WAVE_UP_DAYS:
# ์ถฉ๋ถ„ํžˆ ๋†’์ด + ์ถฉ๋ถ„ํžˆ ์˜ค๋ž˜ ์œ„์— ์žˆ์Œ โ†’ ์ด ์œ„์•„๋ž˜(์•„๋ž˜ ๊ตฌ๊ฐ„) ์ข…๋ฃŒ
daily_open=False; pend=[]
ev.append((d,f'{wave}๋ฒˆ์งธ ์•„๋ž˜ ์ข…๋ฃŒ',
f'์œ„ ๊ตฌ๊ฐ„ ์ตœ๊ณ  +{up_peak*100:.1f}% โ‰ฅ +{WAVE_EXIT_PCT*100:.0f}%, {up_days}์ผ ์—ฐ์† ์œ„ โ†’ ์ง„์งœ ์œ„ ๊ตฌ๊ฐ„'))
if wave>=2:
flag240up=True
ev.append((d,'240์˜ฌ(์ถ”๋งค์ข…๋ฃŒ)','2๋ฒˆ์งธ ์œ„์•„๋ž˜ ๋ โ€” ๋‹ค์‹œ 240์„  ์•„๋ž˜๋กœ ๊บพ์ผ ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ'))
up_peak=0.0; up_days=0
else:
# ์•„๋ž˜ ๊ตฌ๊ฐ„ ๋Œ€๊ธฐ ์ค‘
if flag240up:
pass
elif C < m60 and _can_buy:
# 60์„  ์•„๋ž˜๋กœ ๋‚ด๋ ค์˜ด โ†’ ํ›„๋ณด ๊ตฌ๊ฐ„์— ์Œ“์•„๋‘  (์•„์ง ํ™•์ • ์ „). bnc=๊ทธ๋‚  ๋ฐ˜๋“ฑ์—ฌ๋ถ€
pend.append((d,C,m240,bnc))
first_wave = (wave==0)
deep_ok = first_wave or (C <= m60*(1-WAVE_DEEP_PCT))
if deep_ok:
# ํ™•์ •! โ†’ ์ƒˆ ์œ„์•„๋ž˜ ์‹œ์ž‘, ๋ชป ์‚ฐ ๋‚ ๋“ค ์†Œ๊ธ‰ ๋งค์ˆ˜
wave+=1
_cap = 1 if (DEEP_DIP_ONE_WAVE and one_wave) else 2
if wave<=_cap:
daily_open=True; hold=True
amt = d1 if wave==1 else d2
d0 = pend[0][0]
tagdeep = '' if first_wave else f' (๊นŠ์ด {(C/m60-1)*100:+.1f}% โ‰ค -{WAVE_DEEP_PCT*100:.0f}%)'
ev.append((d,f'{wave}๋ฒˆ์งธ ์•„๋ž˜ ์‹œ์ž‘',
f'์ข…๊ฐ€ {C:,.0f} < 60์„  {m60:,.0f}{tagdeep}'))
if len(pend)>1:
ev.append((d,' โ””์†Œ๊ธ‰๋งค์ˆ˜',f'{str(d0)[:10]}๋ถ€ํ„ฐ ๋ชป์‚ฐ {len(pend)-1}์ผ์น˜ ํ•จ๊ป˜ ๋งค์ˆ˜'))
for pd_, pc_, pm_, pb_ in pend:
if WAVE_BUY_FLOOR and pc_ < pm_*(1-WAVE_BUY_FLOOR) and not pb_:
ev.append((pd_,' โ””๋งค์ˆ˜๋ณด๋ฅ˜(240 -{:.0f}%ยท๋ฐ˜๋“ฑ๋Œ€๊ธฐ)'.format(WAVE_BUY_FLOOR*100),
f'์ข…๊ฐ€ {pc_:,.0f} < 240 -{WAVE_BUY_FLOOR*100:.0f}% ({(pc_/pm_-1)*100:+.1f}%) ํ•˜๋ฝ์ค‘'))
elif _buy(pd_,pc_,amt,f'{wave}๋ฒˆ ์ผ๋ด‰๋งค์ˆ˜', force=(daily and WAVE_OVERRIDE_COOLDOWN)):
ev.append((pd_,' โ””๋งค์ˆ˜',f'{amt:,}์› (๋งค์ผ {amt//10000}๋งŒ)'))
pend=[]
else:
flag240up=True; pend=[]
ev.append((d,'240์˜ฌ(์ถ”๋งค์ข…๋ฃŒ)','2๋ฒˆ์งธ ์œ„์•„๋ž˜ ํ›„ โ€” ๋‹ค์‹œ 240์„  ์•„๋ž˜๋กœ ๊บพ์ผ ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ'))
else:
# 60์„  ์œ„๋กœ ๋ณต๊ท€ โ†’ ํ™•์ • ๋ชปํ•œ ํ›„๋ณด ๊ตฌ๊ฐ„์€ ๋ฒ„๋ฆผ (์‚ด์ง ์Šค์นจ์ด์—ˆ์Œ)
if pend: pend=[]
else:
# ===== ๋ถ„๋ด‰/์‹œ๊ฐ„๋ด‰: ๊ธฐ์กด ํ„ฐ์น˜ ๊ธฐ๋ฐ˜ (๊ฒ€์ฆ๋จ, ๋ณ€๊ฒฝ ์—†์Œ) =====
if armed and L <= m60:
wave+=1; armed=False; lwl=L; lwl_c=C
if wave <= 2:
ev.append((d,f'{wave}๋ฒˆ์งธ ์œ„์•„๋ž˜',f'์ €๊ฐ€ {L:,.0f} โ‰ค 60MA {m60:,.0f}'))
mlt=2 if wave==1 else 1; amt=a*mlt; hold=True
if _buy(d,C,amt,f'{wave}๋ฒˆ 60์•„๋ž˜ x{mlt}'):
ev.append((d,' โ””๋งค์ˆ˜',f'{amt:,}์›'))
elif not flag240up:
flag240up=True
ev.append((d,'240์˜ฌ(์ถ”๋งค์ข…๋ฃŒ)','2๋ฒˆ์งธ ์œ„์•„๋ž˜ ํ›„ โ€” ๋‹ค์‹œ 240์„  ์•„๋ž˜๋กœ ๊บพ์ผ ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ'))
# 240๊ทผ์ฒ˜ = ๋Œ€๊ธฐ ์ง€์ •๊ฐ€ ์ฃผ๋ฌธ. ํ˜„์žฌ ์œ„์•„๋ž˜ ์Šฌ๋กฏ๋งŒ ์ฒด๊ฒฐ.
# ์ด์ „ ์œ„์•„๋ž˜ ๋ฏธ์ฒด๊ฒฐ๋ถ„์€ ์†Œ๋ฉธ(ํ•˜๋ฝ ๋ฌผํƒ€๊ธฐ ๋ฐฉ์ง€).
# โ˜… 240์˜ฌ(3๋ฒˆ์งธ ์œ„) ๋˜๋ฉด ๋Œ€๊ธฐ์ฃผ๋ฌธ๋„ ์ทจ์†Œ โ€” ์‚ฌ์ดํด ๊บพ์ด๋Š” ์ค‘์—” ์ถ”๋งค ๊ธˆ์ง€
if (not flag240up) and wave in (1,2) and not b240.get(wave):
if L <= m240*(1+NEAR_240):
mlt=2 if wave==1 else 1; amt=b*mlt; hold=True; b240[wave]=True
if _buy(d,C,amt,f'{wave}๋ฒˆ 240๊ทผ์ฒ˜ x{mlt}'):
ev.append((d,' โ””๋งค์ˆ˜',f'{amt:,}์› ({wave}๋ฒˆ 240๊ทผ์ฒ˜)'))
if (not daily) and (not armed) and lwl_c and C > lwl_c*(1+recov):
armed=True
# 1๋ฒˆ์งธ ์œ„์•„๋ž˜ ๋ฌด์žฅ: ๊ฐ€๊ฒฉ ์ข…๊ฐ€๊ฐ€ 60์„  ์œ„๋กœ +WAVE_RISE_PCT ์˜ฌ๋ผ์™€์•ผ ('์œ„' ํ™•์ธ)
if (not armed) and wave==0 and C > m60*(1+wrise):
armed=True
elif phase=='DIP':
# ๋์ง€์ (2) ์ถ”์  ์ค‘ (๋งค์ˆ˜ ์ค‘๋‹จ ์ƒํƒœ)
if life and L < life:
tag='์ƒ๋ช…์„ ์ดํƒˆโ†’์ „๋Ÿ‰๋งค๋„' if hold else '์ƒ๋ช…์„ ์ดํƒˆโ†’๋งค๋„'
ev.append((d,tag,f'์ €๊ฐ€ {L:,.0f} < ์ƒ๋ช…์„  {life:,.0f}'))
if hold: held=False # ์‹ค์ œ ์ „๋Ÿ‰๋งค๋„ โ†’ ์‹ค๋ณด์œ  ์ฒญ์‚ฐ
phase='WAIT'; hold=False; start_ma60=HI; start_bb60=None; start_pxlow=HI; start_bbpx=None
elif not above:
# ๋์ง€์ (2) ์ตœ์ € ๊ฐฑ์‹  (โ˜… ์‚ฌ์ดํด์ข…๋ฃŒ ์—†์Œ โ€” ๋์ง€์ (1)๊ณผ ๋™์ผํ•˜๊ฒŒ ์ƒ๋ช…์„ ๋งŒ์ด ์‚ฌ๋ง์กฐ๊ฑด)
if gap < dip_gap: dip_gap=gap
if bl < dip_bbmin: dip_bbmin=bl # โ˜… DIP ๊ตฌ๊ฐ„ ์ตœ์ € BBํ•˜๋‹จ
if m60 < dip_ma60: dip_ma60=m60; dip_bb60=bl; dip_dt=d
if L < dip_px: dip_px=L; dip_bbpx=bl
# โ˜… ์กฐ๊ธฐ '์†์Œ' ํŒ์ •: 240 ๋ณต๊ท€๋ฅผ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ , ๊นŠ์ด๊ฐ€ ์ž„๊ณ„๋ฅผ ์ฐ๋Š” ์ˆœ๊ฐ„ ์žฌ๋ฒ ์ด์Šค.
# ๋‹จ ๋”๋ธ”๋™์‚ฐ ๊ฐ€๋Šฅ์„ฑ์ด ์‚ด์•„์žˆ์œผ๋ฉด(ํ”ผํฌ๊ฐญ ๋ฐด๋“œ ์•ˆ + ๋์ง€์  ์œ ์ง€) ๋ฐœ๋™ ์•ˆ ํ•จ โ†’ ๋”๋ธ”๋™์‚ฐ ๋ณด์กด.
# โ˜… ๊ธฐ๊ฐ„ ๊ธฐ๋ฐ˜(REBASE_DIP_DAYS)๋„ ๊ฐ™์€ ์ž๋ฆฌ์—์„œ OR๋กœ ํŒ์ •:
# ๊นŠ์ด๊ฐ€ ์–•์•„๋„ DIP๊ฐ€ ๋„ˆ๋ฌด ์˜ค๋ž˜ ์ง€์†๋˜๋ฉด(2๋…„์งœ๋ฆฌ ๋ˆŒ๋ฆผ=๋ˆŒ๋ฆผ ์•„๋‹˜) ์žฌ๋ฒ ์ด์Šค.
_deep_hit = bool(REBASE_EARLY and REBASE_DEEP_PCT and dip_gap <= -REBASE_DEEP_PCT)
_elapsed = ((d - dip_start_dt).days if daily else (i - dip_start_i)) if dip_start_dt is not None else 0
_long_hit = bool(REBASE_DIP_DAYS and dip_start_dt is not None and _elapsed >= REBASE_DIP_DAYS)
if _deep_hit or _long_hit:
_p0 = dip_peak if dip_peak > 0 else None
_pg0 = (_p0/prev_mtn_peak - 1) if (prev_mtn_peak and _p0) else None
_eg0 = (dip_ma60/prev_end60 - 1) if prev_end60 else None
_endok0 = (_eg0 is None) or (_eg0 >= -DOUBLE_MTN_END_TOL)
_floor0 = bool(DOUBLE_MTN_LIFELINE_FLOOR and life and dip_px > life) # ์ €๊ฐ€๊ฐ€ ์ฒซ ์ƒ๋ช…์„  ์œ„
_band0 = (DOUBLE_MTN_GAP > 0 and _pg0 is not None
and DOUBLE_MTN_GAP_MIN <= _pg0 < DOUBLE_MTN_GAP)
_dbl_strict = _band0 and _endok0 # ์›๋ž˜ ๊ธฐ์ค€(๋์ง€์  ์œ ์ง€)
_dbl_floor = _band0 and (_endok0 or _floor0) # +์ƒ๋ช…์„ ๋ฐ”๋‹ฅ ์ธ์ •
# โ˜…๊นŠ์ด(-25%) ํŠธ๋ฆฌ๊ฑฐ๋Š” ์ƒ๋ช…์„ ๋ฐ”๋‹ฅ๊นŒ์ง€ ์ธ์ •ํ•ด์„œ ๋ง‰๊ณ (030530 ๋ณดํ˜ธ: ์ €๊ฐ€>์ฒซ์ƒ๋ช…์„ ์ด๋ฉด ์‹œํ€€์Šค ์ƒ์กด),
# ์žฅ๊ธฐ(600์ผ) ํŠธ๋ฆฌ๊ฑฐ๋Š” ์›๋ž˜ ๊ธฐ์ค€๋งŒ์œผ๋กœ ๋ง‰์Œ(236200 ์œ ์ง€: ์‹œ๊ฐ„์ด ์ƒ๋ช…์„ ๋ฐ”๋‹ฅ๋ณด๋‹ค ์šฐ์„ ).
_blk = _dbl_floor if (_deep_hit and not _long_hit) else _dbl_strict
if _deep_hit and (not _long_hit) and _floor0 and (not _endok0) and _blk and (not dip_floor_logged):
dip_floor_logged=True
ev.append((d,'์กฐ๊ธฐ์žฌ๋ฒ ์ด์Šค ๋ณด๋ฅ˜(์ƒ๋ช…์„ ๋ฐ”๋‹ฅ)',
f'๋‹ค์‹œ ์•ˆ ๊ทธ๋ฆผ โ€” ๋งŽ์ด({dip_gap*100:.1f}%) ๋น ์กŒ์ง€๋งŒ ์ตœ์ €๊ฐ€ {dip_px:,.0f}๊ฐ€ ์ฒซ ์ƒ๋ช…์„  {life:,.0f}๋ณด๋‹ค ์œ„. ์•„์ง ๊ตฌ์กฐ ์•ˆ ๋ฌด๋„ˆ์ง โ†’ ๋”๋ธ”๋™์‚ฐ ๊ฐ€๋Šฅ์„ฑ ์‚ด๋ ค๋‘ '))
if not _blk:
_why0 = ('๊ณ„๋‹จ์‹ํ•˜๋ฝ' if (_pg0 is not None and _pg0 < DOUBLE_MTN_GAP_MIN)
else ('๋์ง€์  ๋‚ฎ์•„์ง' if not _endok0
else ('ํ”ผํฌ์ „์ง„' if _pg0 is not None else 'ํ”ผํฌ๋ฐ์ดํ„ฐ ์—†์Œ')))
mtn_s60 = dip_ma60; mtn_sdt = dip_dt
life = min(dip_bb60, dip_bbpx)*(1-LIFELINE_MARGIN)
_dur0 = _elapsed # ์ผ๋ด‰=์ผ์ˆ˜ / ๋ถ„๋ด‰=๋ด‰์ˆ˜
if _deep_hit:
_tag0 = '์†์Œโ†’์žฌ๋ฒ ์ด์Šค(์กฐ๊ธฐํŒ์ •)'
_rsn0 = f'60์„ ์ด 240์„  ๋ฐ‘์œผ๋กœ {dip_gap*100:.1f}% ๊นŠ์ด ๋น ์ง(๊ธฐ์ค€ -{REBASE_DEEP_PCT*100:.0f}% ์ดํ•˜). ๋ˆŒ๋ฆผ ์‹œ์ž‘ {str(dip_start_dt)[:10]}๋ถ€ํ„ฐ {_dur0}์ผ์งธ'
else:
_tag0 = '์†์Œโ†’์žฌ๋ฒ ์ด์Šค(์žฅ๊ธฐDIP)'
_rsn0 = (f'๋ˆŒ๋ฆผ์ด {_dur0}์ผ(์•ฝ {_dur0//365}๋…„)์ด๋‚˜ ์ง€์†({str(dip_start_dt)[:10]}~{str(d)[:10]}, ๊ธฐ์ค€ {REBASE_DIP_DAYS}์ผ). '
f'๊นŠ์ด๋Š” {dip_gap*100:.1f}%๋กœ ์–•์•„๋„ ์ด๋ ‡๊ฒŒ ์˜ค๋ž˜ ๋Œ๋ฉด ๋ˆŒ๋ฆผ ์•„๋‹ˆ๋ผ ๋ฌด๋„ˆ์ง')
ev.append((d,_tag0,
f'์†์•˜์Œ โ†’ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ โ€” {_rsn0} / ์ด๋ฒˆ ๋ด‰์šฐ๋ฆฌ๋Š” ์ง์ „ ์‚ฐ๋ณด๋‹ค {(_pg0*100 if _pg0 is not None else 0):+.1f}%({_why0}) / 240์„  ๋ณต๊ท€ ์•ˆ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์œ„์•„๋ž˜ ์ ‘์Œ / ์ƒˆ ์‹œ์ž‘์  {str(dip_dt)[:10]}(60์„  {dip_ma60:,.0f}) / ์ƒˆ ์ƒ๋ช…์„  {life:,.0f}'))
phase='PEAK'; peak_gap=gap; peak_m60=m60; vlife=None; vbreak_dt=None; vseg_i=len(buys) # โ˜…์ƒˆ ์‚ฐ ๊ทธ๋ฆฌ๊ธฐ ์‹œ์ž‘ โ†’ ์˜› ๊ฐ€์ƒ์ƒ๋ช…์„  ํ๊ธฐ
endlow_gap=0.0; endlow_ma60=HI; endlow_px=HI
hold=False; wave=0; armed=False; b240={}; flag240up=False
daily_open=False; pend=[]; up_peak=0.0; up_days=0
mtn_count=0; life_broken=False
wave_confirmed=False; conf_logged=False; touch_logged=False; cross_up_dt=None; wave_hat=0.0
prev_mtn_peak=_p0; wave_peak_ma60=0.0
else:
# 60์„ ์ด 240 ์œ„๋กœ ๋ณต๊ท€ โ†’ ๋”๋ธ”๋™์‚ฐ์ธ์ง€ ํŒ์ • (ํ”ผํฌ ๊ฐญ ๊ธฐ์ค€)
# ์ง์ „ ์‚ฐ ํ”ผํฌ(prev_mtn_peak) vs ์ด๋ฒˆ ์‚ฐ ํ”ผํฌ(dip_peak, DIP ์ง์ „๊นŒ์ง€์˜ ์ตœ๊ณ )
# dip_dt(๋์ง€์ 2) ์ด์ „์˜ ๋งˆ์ง€๋ง‰ ํ”ผํฌ = ์ด๋ฒˆ ์‚ฐ ํ”ผํฌ
# ๊ทธ ํ”ผํฌ ์ด์ „์˜ ํ”ผํฌ = ์ง์ „ ์‚ฐ ํ”ผํฌ (์‚ฌ์ „๊ณ„์‚ฐ ๋ฆฌ์ŠคํŠธ์—์„œ ์กฐํšŒ โ€” ์ƒํƒœ์ถ”์  ์•ˆ ํ•จ)
# โ˜… ๋™์‚ฐ ๋‹จ์œ„ ํ”ผํฌ ๋น„๊ต (์„ธ๊ทธ๋จผํŠธ ์กฐ๊ฐ ์•„๋‹˜):
# ์ด๋ฒˆ ์‚ฐ ํ”ผํฌ = dip_peak (DIP ์ง„์ž… ์ง์ „๊นŒ์ง€ WAVE์—์„œ ์ถ”์ ํ•œ 60์„  ์ตœ๊ณ )
# ์ง์ „ ์‚ฐ ํ”ผํฌ = prev_mtn_peak (์ง์ „ ๋™์‚ฐ์„ฑ๊ณต/๋”๋ธ”๋™์‚ฐ์˜ ํ”ผํฌ)
p_now = dip_peak if dip_peak > 0 else None
p_prev = prev_mtn_peak
_pg = (p_now/p_prev - 1) if (p_prev and p_now) else None
_deep = dip_gap <= -emin # ๋์ง€์ (2)์ด ์ง„์งœ ๋์ง€์ ๊ธ‰ ๊นŠ์ด(-7%)์ธ๊ฐ€
_eg = (dip_ma60/prev_end60 - 1) if prev_end60 else None # ๋์ง€์ (2) vs ๋์ง€์ (1)
_end_ok = (_eg is None) or (_eg >= -DOUBLE_MTN_END_TOL) # ๋์ง€์  ์œ ์ง€/์ƒ์Šน?
_floor_ok = bool(DOUBLE_MTN_LIFELINE_FLOOR and life and dip_px > life) # โ˜…์ €๊ฐ€๊ฐ€ ์ฒซ ์ƒ๋ช…์„  ์œ„ = ์‹œํ€€์Šค ์ƒ์กด
ev.append((d,' โ””DIPํŒ์ •๋ฐ์ดํ„ฐ',
f'ํ”ผํฌ๊ฐญ {(_pg*100 if _pg is not None else 0):+.1f}% / ๋๊นŠ์ด {dip_gap*100:.2f}% / ๋์ง€์ ๊ฐญ {(_eg*100 if _eg is not None else 0):+.1f}%'))
# โ€ป _floor_ok(์ €๊ฐ€>์ฒซ์ƒ๋ช…์„ )๋Š” '๋”๋ธ”๋™์‚ฐ ํ™•์ •'์—๋Š” ์•ˆ ์”€ โ€” ๋”๋ธ”๋กœ ์น˜๋ฉด ์นด์šดํŠธ๊ฐ€ ์˜ฌ๋ผ
# ํŠธ๋ฆฌํ”Œ์บก์— ๊ฑธ๋ ค ์ดํ›„ ๋งค์ˆ˜๊ฐ€ ์ฃฝ๋Š” ๋ถ€์ž‘์šฉ(ํ•˜์ด๋‹‰์Šค2023 ์žฌ๊ฐœ ์ผ€์ด์Šค ๋’ค์ง‘ํž˜, ์ „์ˆ˜ -129M ํ™•์ธ).
# ๋Œ€์‹  ์กฐ๊ธฐ์žฌ๋ฒ ์ด์Šค๋ฅผ ๋ง‰๋Š” ๋ฐ๋งŒ ์จ์„œ, ๋์ง€์  ๋‚ฎ์•„์ง+์ƒ๋ช…์„  ์œ„ ์ผ€์ด์Šค๋Š” '์œ„์•„๋ž˜ ์žฌ๊ฐœ'(์นด์šดํŠธ ์œ ์ง€)๋กœ ํ˜๋ฆผ.
if DOUBLE_MTN_GAP > 0 and _pg is not None and DOUBLE_MTN_GAP_MIN <= _pg < DOUBLE_MTN_GAP and _deep and _end_ok:
# โ˜… ๋”๋ธ”๋™์‚ฐ ํ™•์ •: ํ”ผํฌ๊ฐ€ ๋ณ„๋กœ ์•ˆ ์˜ฌ๋ž์Œ(์ œ์ž๋ฆฌ) โ†’ ์œ„์•„๋ž˜ ๋ฆฌ์…‹, ์ƒˆ ๋™์‚ฐ
# โ€ป ์ƒ๋ช…์„ (life)์€ ๋ฆฌ์…‹ํ•˜์ง€ ์•Š์Œ โ€” ์ฒซ ๋™์‚ฐ ์‹œ์ž‘์  ์ƒ๋ช…์„ ์„ ์‹œํ€€์Šค ๋‚ด๋‚ด ๊ทธ๋Œ€๋กœ ์œ ์ง€.
# (๋”๋ธ”/ํŠธ๋ฆฌํ”Œ ๋ˆŒ๋ฆผ์—์„œ ์–•๊ฒŒ ํŒ”๋ ค๋‚˜๊ฐ€์ง€ ์•Š๋„๋ก. ์žฌ๋ฒ ์ด์Šค๋Š” ์ƒˆ ์‹œํ€€์Šค๋ผ ๋ณ„๋„)
gap_pct = (p_now/p_prev - 1)*100
# โ˜… ์ƒ๋ช…์„  ์˜ฌ๋ ค์žก๊ธฐ: ๋ฐฉ๊ธˆ ์ง€๋‚˜์˜จ DIP ๊ตฌ๊ฐ„ ์ตœ์ € BBํ•˜๋‹จ ๊ธฐ์ค€ (๋‚ด๋ฆฌ์ง€๋Š” ์•Š์Œ)
_oldlife = life
if daily and VIRTUAL_LIFELINE and dip_bbmin < HI:
_v = dip_bbmin*(1-LIFELINE_MARGIN)
vlife = _v if (life is None or _v > life) else None
vseg_i = len(buys); vbreak_dt=None
one_wave = bool(DEEP_DIP_ONE_WAVE and dip_gap <= -DEEP_DIP_ONE_WAVE_PCT)
if DOUBLE_MTN_RELIFE and dip_bbmin < HI:
_cand = dip_bbmin*(1-LIFELINE_MARGIN)
if life is None or _cand > life: life = _cand
mtn_s60 = dip_ma60; mtn_sdt = dip_dt
mts.append({'start':d,'s60':mtn_s60,'e60':dip_ma60,'life':life,
'endpx':dip_px,'sdt':mtn_sdt,'edt':dip_dt})
mtn_count += 1 # โ˜… ๋™์‚ฐ ์ˆœ๋ฒˆ ์ฆ๊ฐ€ (2=๋”๋ธ”, 3=ํŠธ๋ฆฌํ”Œโ€ฆ)
wave_confirmed=False; conf_logged=False; touch_logged=False; cross_up_dt=None; wave_hat=0.0 # ๋ณต๊ท€ํ™•์ธ ๋ฆฌ์…‹
_over = mtn_count > MAX_MTN_WAVE # ํŠธ๋ฆฌํ”Œ(3)+ ์ด๋ฉด ์œ„์•„๋ž˜ ๋งค์ˆ˜ ์•ˆ ํ•จ
_nm = {2:'๋”๋ธ”',3:'ํŠธ๋ฆฌํ”Œ',4:'์ฟผ๋“œ๋Ÿฌํ”Œ'}.get(mtn_count, f'{mtn_count}์ค‘')
ev.append((d,f'{_nm}๋™์‚ฐ ํ™•์ •โ†’์œ„์•„๋ž˜ ๋ฆฌ์…‹',
f'ํ”ผํฌ๊ฐญ {gap_pct:+.1f}% < {DOUBLE_MTN_GAP*100:.0f}% (์ œ์ž๋ฆฌ) โ€” {mtn_count}๋ฒˆ์งธ ๋™์‚ฐ / ์ƒ๋ช…์„  {life:,.0f}'
+ (f'(์ง์ „ ๋ˆŒ๋ฆผ ์ตœ์ €BB๋กœ ์˜ฌ๋ฆผ, ๊ธฐ์กด {_oldlife:,.0f})' if (_oldlife and life>_oldlife) else '(์ฒซ๋™์‚ฐ ์œ ์ง€)')
+ (' | โ˜…๋์ง€์  ๋‚ฎ์•„์กŒ์ง€๋งŒ ์ €๊ฐ€>์ฒซ์ƒ๋ช…์„  โ†’ ๋”๋ธ” ์ธ์ •(์ƒ๋ช…์„ ๋ฐ”๋‹ฅ)' if (_floor_ok and not _end_ok) else '')
+ (f' | โ˜…{MAX_MTN_WAVE}๊ฐœ ์ดˆ๊ณผ โ†’ ์œ„์•„๋ž˜ ๋งค์ˆ˜ ์•ˆ ํ•จ(๊ด€์ฐฐ๋งŒ)' if _over else '')))
wave_fresh=False # ์‹œํ€€์Šค ์ง„ํ–‰ ์ค‘(๋”๋ธ”๋™์‚ฐ) โ†’ ๋ชจ์ž ๋ฌด์‹œ
phase='WAVE'; wave=0; armed=False; b240={}; flag240up=_over # โ˜… ํŠธ๋ฆฌํ”Œ+ = ๋งค์ˆ˜ ์–ต์ œ
lwl=None; lwl_c=None; daily_open=False; pend=[]; up_peak=0.0; up_days=0
prev_end60=dip_ma60 # โ˜… ๋์ง€์ (2)๊ฐ€ ์ƒˆ ๋น„๊ต ๊ธฐ์ค€
prev_mtn_peak=p_now; wave_peak_ma60=0.0
elif _deep and _pg is not None and _pg >= DOUBLE_MTN_GAP:
# โ˜… ํ”ผํฌ๊ฐ€ ํฌ๊ฒŒ ์ „์ง„(+42%โ†‘)ํ•œ ๋’ค์˜ ๊นŠ์€ ๋ˆŒ๋ฆผ = ํ•œ ์‚ฌ์ดํด ์™„์ฃผ ํ›„ ๋ถ•๊ดด โ†’ ์žฌ๋ฒ ์ด์Šค (์ƒˆ ์‹œํ€€์Šค)
# dip ์ตœ์ €์ ์„ ์ƒˆ ๋™์‚ฐ ์‹œ์ž‘์ ์œผ๋กœ ์‚ผ๊ณ  PEAK๋ถ€ํ„ฐ ๋‹ค์‹œ (์‚ผ์„ฑ 2013, ํ•˜์ด๋‹‰์Šค 2016 ์œ ํ˜•)
mtn_s60 = dip_ma60; mtn_sdt = dip_dt
life = min(dip_bb60, dip_bbpx)*(1-LIFELINE_MARGIN)
ev.append((d,'๊นŠ์€๋ˆŒ๋ฆผโ†’์ƒˆ๋™์‚ฐ(์žฌ๋ฒ ์ด์Šค)',
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}'))
phase='PEAK'; peak_gap=gap; peak_m60=m60; vlife=None; vbreak_dt=None; vseg_i=len(buys) # โ˜…์ƒˆ ์‚ฐ ๊ทธ๋ฆฌ๊ธฐ ์‹œ์ž‘ โ†’ ์˜› ๊ฐ€์ƒ์ƒ๋ช…์„  ํ๊ธฐ
endlow_gap=0.0; endlow_ma60=HI; endlow_px=HI
hold=False; wave=0; armed=False; b240={}; flag240up=False
daily_open=False; pend=[]; up_peak=0.0; up_days=0
mtn_count=0; life_broken=False # ์‚ฌ์ดํด ์™„์ฃผ ํ›„ ์ƒˆ ์‹œํ€€์Šค (๋‹ค์Œ ๋™์‚ฐ์„ฑ๊ณต์ด 1๋ฒˆ์งธ)
prev_mtn_peak=p_now; wave_peak_ma60=0.0
elif _deep and REBASE_DEEP_PCT and dip_gap <= -REBASE_DEEP_PCT \
and not (_floor_ok and _pg is not None and DOUBLE_MTN_GAP_MIN <= _pg < DOUBLE_MTN_GAP):
# โ˜… ์†์Œโ†’์žฌ๋ฒ ์ด์Šค: ์œ„์•„๋ž˜์ธ ์ค„ ์•Œ๊ณ  ์ƒ€๋Š”๋ฐ 240์˜ฌ ๋ชป ๊ฐ€๊ณ  ๊นŠ์ด(-REBASE_DEEP_PCTโ†“) ๋ถ•๊ดด.
# ํ”ผํฌ๊ฐ€ ๊ณ„๋‹จ์‹ํ•˜๋ฝ(์ œ์ž๋ฆฌ/ํ•˜๋ฝ)์ด๋ผ ๋”๋ธ”๋™์‚ฐ๋„ ์•„๋‹˜ โ†’ '์œ„์•„๋ž˜ ์žฌ๊ฐœ' ๋Œ€์‹  ๋™์‚ฐ๋ถ€ํ„ฐ ๋‹ค์‹œ.
# ๋‹จ(์ƒ๋ช…์„ ๋ฐ”๋‹ฅ, 030530): ์ €๊ฐ€>์ฒซ์ƒ๋ช…์„  + ํ”ผํฌ๊ฐญ ๋ฐด๋“œ ์•ˆ์ด๋ฉด ์‹œํ€€์Šค ์ƒ์กด โ†’ ์žฌ๋ฒ ์ด์Šค ๋Œ€์‹  ์•„๋ž˜ '์žฌ๊ฐœ'๋กœ.
# dip ์ตœ์ €์ ์„ ์ƒˆ ๋™์‚ฐ ์‹œ์ž‘์ ์œผ๋กœ, ์ƒ๋ช…์„ ๋„ ์ƒˆ๋กœ. (John: "์œ„์•„๋ž˜ ์•„๋‹ˆ์—ˆ์œผ๋‹ˆ ๋‹ค์‹œ ๋™์‚ฐ ๋ณด์ž")
mtn_s60 = dip_ma60; mtn_sdt = dip_dt
life = min(dip_bb60, dip_bbpx)*(1-LIFELINE_MARGIN)
_whyR = ('๊ณ„๋‹จ์‹ํ•˜๋ฝ' if (_pg is not None and _pg < DOUBLE_MTN_GAP_MIN)
else ('๋์ง€์  ๋‚ฎ์•„์ง' if not _end_ok else 'ํ”ผํฌ๋ฐ์ดํ„ฐ ์—†์Œ'))
ev.append((d,'์†์Œโ†’์žฌ๋ฒ ์ด์Šค(๊นŠ์€๋ถ•๊ดด)',
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}'))
phase='PEAK'; peak_gap=gap; peak_m60=m60; vlife=None; vbreak_dt=None; vseg_i=len(buys) # โ˜…์ƒˆ ์‚ฐ ๊ทธ๋ฆฌ๊ธฐ ์‹œ์ž‘ โ†’ ์˜› ๊ฐ€์ƒ์ƒ๋ช…์„  ํ๊ธฐ
endlow_gap=0.0; endlow_ma60=HI; endlow_px=HI
hold=False; wave=0; armed=False; b240={}; flag240up=False
daily_open=False; pend=[]; up_peak=0.0; up_days=0
mtn_count=0; life_broken=False # ๊ตฌ์กฐ ์ƒˆ ์‹œ์ž‘ โ†’ ๋‹ค์Œ ๋™์‚ฐ์„ฑ๊ณต์ด 1๋ฒˆ์งธ
wave_confirmed=False; conf_logged=False; touch_logged=False; cross_up_dt=None; wave_hat=0.0
prev_mtn_peak=p_now; wave_peak_ma60=0.0
elif _deep:
# โ˜… ๊นŠ์€ ๋ˆŒ๋ฆผ์ธ๋ฐ ํ”ผํฌ๋Š” ์ œ์ž๋ฆฌ/ํ•˜๋ฝ(์ž‘์€ ๋ด‰์šฐ๋ฆฌ) โ†’ ๋™์‚ฐ์œผ๋กœ ์•ˆ ์นจ(ํŠธ๋ฆฌํ”Œ ์•„๋‹˜).
# ํ•˜์ง€๋งŒ ์ฒซ ๋™์‚ฐ ์ƒ๋ช…์„ ์„ ์•ˆ ๊นผ์œผ๋‹ˆ ์‹œํ€€์Šค(๋™์‚ฐ+๋”๋ธ”๋™์‚ฐ)๋Š” ์‚ด์•„์žˆ์Œ (M์ž๋„ ๋”๋ธ”๋™์‚ฐ).
# 60>240 ๋ณต๊ท€ = ์ƒˆ ๋งค์ˆ˜๊ตฌ๊ฐ„ โ†’ 1๋ฒˆ์งธ ์œ„์•„๋ž˜๋ถ€ํ„ฐ ์žฌ๊ฐœ. ์ƒ๋ช…์„ ์€ ๊ณ„์† ์ฒซ ๋™์‚ฐ ๊ฒƒ.
# (์ƒ๋ช…์„ ์ด ์œ ์ผํ•œ ์‚ฌ๋ง์กฐ๊ฑด โ€” ๊นจ์ง€๋ฉด DIP ์ค‘์— ์ด๋ฏธ ์ „๋Ÿ‰๋งค๋„๋์„ ๊ฒƒ)
_why3 = ('๊ณ„๋‹จ์‹ ํ•˜๋ฝ' if (_pg is not None and _pg < DOUBLE_MTN_GAP_MIN)
else ('๋์ง€์ ์ด ์ง์ „๋ณด๋‹ค ๋‚ฎ์•„์ง' if not _end_ok else 'ํ”ผํฌ๋ฐ์ดํ„ฐ ์—†์Œ'))
wave_fresh=False # ์‹œํ€€์Šค ์ง„ํ–‰ ์ค‘(์žฌ๊ฐœ) โ†’ ๋ชจ์ž ๋ฌด์‹œ
# โ˜… ์žฌ๊ฐœ ๋•Œ๋„ ์ƒ๋ช…์„  ์˜ฌ๋ ค์žก๊ธฐ (์ง์ „ ๋ˆŒ๋ฆผ ์ตœ์ € BBํ•˜๋‹จ). ๋‚ด๋ฆฌ์ง€๋Š” ์•Š์Œ.
_oldlife3 = life
if daily and VIRTUAL_LIFELINE and dip_bbmin < HI:
_v3 = dip_bbmin*(1-LIFELINE_MARGIN)
vlife = _v3 if (life is None or _v3 > life) else None
vseg_i = len(buys); vbreak_dt=None
one_wave = bool(DEEP_DIP_ONE_WAVE and dip_gap <= -DEEP_DIP_ONE_WAVE_PCT)
if DOUBLE_MTN_RELIFE and dip_bbmin < HI:
_c3 = dip_bbmin*(1-LIFELINE_MARGIN)
if life is None or _c3 > life: life = _c3
ev.append((d,'์ž‘์€๋ด‰์šฐ๋ฆฌ(๋™์‚ฐ์•„๋‹˜)โ†’์œ„์•„๋ž˜ ์žฌ๊ฐœ',
f'์ด๋ฒˆ 60์„  ๊ผญ๋Œ€๊ธฐ {(p_now if p_now else 0):,.0f} vs ์ง์ „ ์‚ฐ ๊ผญ๋Œ€๊ธฐ {(p_prev if p_prev else 0):,.0f} = {(_pg*100 if _pg is not None else 0):+.1f}% '
f'(๊ผญ๋Œ€๊ธฐ๊ฐ€ ์ง์ „๊ณผ ๋น„์Šทํ•ด์•ผ[-{abs(DOUBLE_MTN_GAP_MIN)*100:.0f}%~+{DOUBLE_MTN_GAP*100:.0f}%] ๋”๋ธ”๋™์‚ฐ์ธ๋ฐ ๋ฒ—์–ด๋‚จ โ†’ {_why3}) / '
f'60์„ ์ด 240์„  ๋ฐ‘์œผ๋กœ {dip_gap*100:.1f}% ๋น ์ง(์žฌ๋ฒ ์ด์Šค ๊ธฐ์ค€ -{REBASE_DEEP_PCT*100:.0f}% ๋ฏธ๋‹ฌ์ด๋ผ ๋™์‚ฐ ๋‹ค์‹œ ์•ˆ ๊ทธ๋ฆผ) '
f'โ†’ ์ƒˆ ์‚ฐ ์•„๋‹˜(๋™์‚ฐ ๊ฐœ์ˆ˜ ์•ˆ ์˜ฌ๋ฆผ). ์ƒ๋ช…์„  {life:,.0f}'
+ (f'(์ง์ „ ๋ˆŒ๋ฆผ ์ตœ์ €BB๋กœ ์˜ฌ๋ฆผ, ๊ธฐ์กด {_oldlife3:,.0f})' if (_oldlife3 and life>_oldlife3) else '')
+ f' โ†’ ์œ„์•„๋ž˜ ๋งค์ˆ˜๋งŒ 1๋ฒˆ์งธ๋ถ€ํ„ฐ ๋‹ค์‹œ'))
phase='WAVE'; wave=0; armed=False; b240={}; flag240up=False
lwl=None; lwl_c=None; daily_open=False; pend=[]; up_peak=0.0; up_days=0
wave_confirmed=False; conf_logged=False; touch_logged=False; cross_up_dt=None; wave_hat=0.0 # ๋ณต๊ท€ํ™•์ธ ๋ฆฌ์…‹
prev_end60=dip_ma60 # ๋‚ฎ์•„์ง„ ๋์ง€์ ์ด ์ƒˆ ๋น„๊ต ๊ธฐ์ค€ (๋™์‚ฐ ์นด์šดํŠธ๋Š” ๊ทธ๋Œ€๋กœ)
prev_mtn_peak=p_now; wave_peak_ma60=0.0
else:
# ์—ฌ๊ธฐ ๋„๋‹ฌ = _deep์ด False (๋๊นŠ์ด -7% ๋ฏธ๋‹ฌ). ์ฆ‰ ์ง„์งœ ๋์ง€์ ์ด ์•„๋‹ˆ๋ผ '์–•์€ ๋ˆŒ๋ฆผ'.
# ํ”ผํฌ๊ฐญ์ด ๋ฐด๋“œ ์•ˆ์ด์–ด๋„ ๋์ง€์ ์ด ์–•์œผ๋ฉด ๋™์‚ฐ ์ž๊ฒฉ ๋ฏธ๋‹ฌ โ†’ ๊ทธ๋ƒฅ ๋ˆŒ๋ฆผ(์œ„์•„๋ž˜ ๊ณ„์†)
gp = _pg*100 if _pg is not None else 0.0
wave_fresh=False # ์‹œํ€€์Šค ์ง„ํ–‰ ์ค‘(์–•์€๋ˆŒ๋ฆผ, ์œ„์•„๋ž˜ ๊ณ„์†) โ†’ ๋ชจ์ž ๋ฌด์‹œ
ev.append((d,'๋”๋ธ”๋™์‚ฐ ์•„๋‹˜โ†’์œ„์•„๋ž˜ ๊ณ„์†',
f'๋๊นŠ์ด {dip_gap*100:.2f}% > -{emin*100:.0f}% (์–•์€ ๋ˆŒ๋ฆผ, ์ง„์งœ ๋์ง€์  ์•„๋‹˜) / ํ”ผํฌ๊ฐญ {gp:+.1f}% โ€” ์œ„์•„๋ž˜ ๊ณ„์†'))
phase='WAVE'
prev_mtn_peak=p_now; wave_peak_ma60=0.0
up_peak=0.0; up_days=0
prev_above=above
if sf:
ev=[e for e in ev if str(e[0])>=sf]; buys=[x for x in buys if str(x[0])>=sf]
mts=[m for m in mts if str(m['start'])>=sf]
# โ˜… ๋งˆ์ง€๋ง‰ ๋ด‰ ๊ธฐ์ค€ '์ง€๊ธˆ ์ƒํƒœ' ํ•œ ์ค„ ์š”์•ฝ (full_debug/app์ด be.LAST_STATUS๋กœ ์ฝ์Œ). ๋ฐ˜ํ™˜๊ฐ’์€ ์•ˆ ๋ฐ”๊ฟˆ.
# LAST_STATUS_KIND: ์ƒ‰์น ์šฉ ์ข…๋ฅ˜ โ€” 'buy'(๋งค์ˆ˜๊ตญ๋ฉด,๋นจ๊ฐ•) / 'danger'(๋ฌด๋„ˆ์ง€๋Š”์ค‘,์ฃผํ™ฉ) / 'dip'(๋ˆŒ๋ฆผํŒ์ •)
# / 'done'(๋งค์ˆ˜๋) / 'wait'(๊ด€๋ง)
global LAST_STATUS, LAST_STATUS_KIND, LAST_STATUS_SHORT, LAST_STATUS_DETAIL
try:
_g = f"{gap*100:+.1f}%" if 'gap' in dir() else ''
_gap_txt = f"60์„ ์ด 240์„ ๋ณด๋‹ค {_g}" if _g else ''
# ๋™์‚ฐ ์ด๋ฆ„(๋ช‡ ๋ฒˆ์งธ ์‚ฐ์ธ์ง€)์„ ์‰ฝ๊ฒŒ
_mtn_nm = {1:'์ฒซ ์‚ฐ', 2:'๋”๋ธ”์‚ฐ(2๋ฒˆ์งธ)', 3:'ํŠธ๋ฆฌํ”Œ์‚ฐ(3๋ฒˆ์งธ)'}.get(mtn_count, f'{mtn_count}๋ฒˆ์งธ ์‚ฐ')
# โ˜… '์–ด๋–ค ๋™์‚ฐ โ†’ ์œ„์•„๋ž˜ ๋ช‡ ๋ฒˆ' ์Šคํ† ๋ฆฌ ์•ž๋จธ๋ฆฌ (ํ˜„์žฌ ์‚ด์•„์žˆ๋Š” ์‚ฐ = mts[-1])
_mtn = mts[-1] if mts else None
if _mtn:
_sd = str(_mtn['sdt'])[:10]; _ed = str(_mtn['edt'])[:10]
if _sd == _ed:
# ๋”๋ธ”/ํŠธ๋ฆฌํ”Œ๋™์‚ฐ: ์‹œ์ž‘๋ฐ”๋‹ฅ=๋์ง€์ (2) ๊ฐ™์€ ๋‚ (๋ณ„๋„ ์‹œ์ž‘๋ฐ”๋‹ฅ ์—†์Œ) โ†’ ํ™•์ • ์ž๋ฆฌ๋กœ ํ‘œํ˜„
_sanhead = f"{_ed} ์ž๋ฆฌ์—์„œ ํ™•์ •๋œ {_mtn_nm}"
else:
_sanhead = f"{_sd} ์‹œ์ž‘ํ•œ {_mtn_nm}(๋์ง€์  {_ed})"
else:
_sanhead = _mtn_nm
_wtxt = f"์œ„์•„๋ž˜ {wave}๋ฒˆ์งธ๊นŒ์ง€ ๋งค์ˆ˜ํ•จ" if wave else "์•„์ง ์œ„์•„๋ž˜ ๋งค์ˆ˜ ์ „"
if phase=='WAIT':
LAST_STATUS_SHORT = '๊ด€๋ง'
LAST_STATUS_DETAIL = '์•„์ง ์‚ฐ ์‹œ์ž‘ ์•ˆ ํ•จ (60์„ ์ด 240์„  ์•„๋ž˜์—์„œ ๋ฐ”๋‹ฅ ๋‹ค์ง€๋Š” ์ค‘)'
LAST_STATUS_KIND = 'wait'
elif phase=='PEAK':
_pk_detail = f"์‹œ์ž‘ ๋ฐ”๋‹ฅ {str(mtn_sdt)[:10]}(60์„  {mtn_s60:,.0f}), ๋ด‰์šฐ๋ฆฌ ์ตœ๊ณ  +{peak_gap*100:.1f}% / ์ƒ๋ช…์„  {life:,.0f}"
if life_broken:
LAST_STATUS_SHORT = '๋ฌด๋„ˆ์ง€๋Š” ์ค‘'
LAST_STATUS_DETAIL = f"์ƒ๋ช…์„  {life:,.0f} ์•„๋ž˜๋กœ ๋‚ด๋ ค๊ฐ(์†์ ˆ๋จ). 60์„ ์ด ์‹œ์ž‘๋ณด๋‹ค ๋†’๊ฒŒ ๋๋‚˜๋ฉด ๊ทธ๋•Œ ๋‹ค์‹œ ์„ฑ๊ณต ์ธ์ •"
LAST_STATUS_KIND = 'danger'
elif above:
LAST_STATUS_SHORT = f'{str(mtn_sdt)[:10]} ์‹œ์ž‘ํ•˜๋Š” ๋™์‚ฐ ๊ทธ๋ฆฌ๋Š” ์ค‘'
LAST_STATUS_DETAIL = _pk_detail
LAST_STATUS_KIND = 'wait'
else:
LAST_STATUS_SHORT = f'{str(mtn_sdt)[:10]} ์‹œ์ž‘ํ•˜๋Š” ๋™์‚ฐ ํ›„ ๋์ง€์  ๊ฐ€๋Š” ์ค‘'
LAST_STATUS_DETAIL = _pk_detail
LAST_STATUS_KIND = 'wait'
elif phase=='DIP':
_durd = (idx[-1]-dip_start_dt).days if dip_start_dt is not None else 0
LAST_STATUS_SHORT = '๋ˆŒ๋ฆผ ํŒ์ •(๋งค์ˆ˜๋ฉˆ์ถค)'
LAST_STATUS_DETAIL = (f"{_sanhead}์—์„œ {_wtxt}. ๊ทธ ๋’ค 60์„ ์ด 240์„  ์•„๋ž˜๋กœ ๊บพ์—ฌ {str(dip_start_dt)[:10]}๋ถ€ํ„ฐ "
f"{_durd}์ผ์งธ ๋ˆŒ๋ฆผ ํŒ์ • ์ค‘(์ง€๊ธˆ 60์„ ์ด 240์„ ๋ณด๋‹ค {dip_gap*100:.1f}%) โ€” ๋งค์ˆ˜ ๋ฉˆ์ถ”๊ณ  "
f"๋”๋ธ”๋™์‚ฐ์ธ์ง€ ๋ฌด๋„ˆ์ง„ ๊ฑด์ง€ ์ง€์ผœ๋ณด๋Š” ์ค‘")
LAST_STATUS_KIND = 'dip'
elif phase=='WAVE':
if flag240up:
LAST_STATUS_SHORT = '๋งค์ˆ˜ ๋(์‰ฌ๋Š” ์ค‘)'
LAST_STATUS_DETAIL = (f"{_sanhead}์—์„œ ์œ„์•„๋ž˜ 2๋ฒˆ ๋‹ค ๋งˆ์นจ โ†’ ๋งค์ˆ˜ ๋. "
f"๋‹ค์‹œ 240์„  ์•„๋ž˜๋กœ ๊บพ์ผ ๋•Œ๊นŒ์ง€ ์‰ฌ๋Š” ์ค‘ ({_gap_txt})")
LAST_STATUS_KIND = 'done'
elif daily_open:
_ud = f"์ง€๊ธˆ์€ 60์„  ์œ„๋กœ ํА({up_days}๋ด‰์งธ) โ€” ์ž ๊น ๋งค์ˆ˜ ์‰ฌ๋Š” ์ค‘" if up_days else "์ง€๊ธˆ 60์„  ๋ฐ‘ โ€” ๋งค์ˆ˜ ์ค‘"
LAST_STATUS_SHORT = '๋งค์ˆ˜ ๊ตญ๋ฉด'
LAST_STATUS_DETAIL = f"{_sanhead}์—์„œ {wave}๋ฒˆ์งธ ์œ„์•„๋ž˜ ๋งค์ˆ˜ ์ค‘. {_ud} ({_gap_txt})"
LAST_STATUS_KIND = 'buy'
elif wave==0:
if wave_confirmed:
LAST_STATUS_SHORT = '๋งค์ˆ˜ ๊ตญ๋ฉด(๋Œ€๊ธฐ)'
LAST_STATUS_DETAIL = f"{_mtn_nm} ์œ„์•„๋ž˜ ์—ด๋ฆผ, 1๋ฒˆ์งธ ๋ˆŒ๋ฆผ ๋Œ€๊ธฐ(๋ณต๊ท€ ํ™•์ธ๋จ, 60์„  ๋ฐ‘์œผ๋กœ ์˜ค๋ฉด ๋งค์ˆ˜) ({_gap_txt})"
LAST_STATUS_KIND = 'buy'
else:
LAST_STATUS_SHORT = '๋ณต๊ท€ ํ™•์ธ ๋Œ€๊ธฐ'
LAST_STATUS_DETAIL = f"{_mtn_nm} ์œ„์•„๋ž˜ ์—ด๋ ธ์ง€๋งŒ ๊ฐ€์งœ(๋ชจ์ž)์ผ ์ˆ˜ ์žˆ์–ด ์•„์ง ๋งค์ˆ˜ ์ „ ({_gap_txt})"
LAST_STATUS_KIND = 'wait'
else:
LAST_STATUS_SHORT = '์œ„ ๊ตฌ๊ฐ„ ๋Œ€๊ธฐ'
LAST_STATUS_DETAIL = f"{_mtn_nm} ยท {wave}๋ฒˆ์งธ ๋ˆŒ๋ฆผ ํ›„ ์œ„ ๊ตฌ๊ฐ„ ๋Œ€๊ธฐ ์ค‘ ({_gap_txt})"
LAST_STATUS_KIND = 'wait'
else:
LAST_STATUS_SHORT = phase; LAST_STATUS_DETAIL = ''; LAST_STATUS_KIND = 'wait'
LAST_STATUS = f"{LAST_STATUS_SHORT} โ€” {LAST_STATUS_DETAIL}" if LAST_STATUS_DETAIL else LAST_STATUS_SHORT
except Exception as _e:
LAST_STATUS = f"(์ƒํƒœ ์š”์•ฝ ์‹คํŒจ: {_e})"; LAST_STATUS_KIND = 'wait'; LAST_STATUS_SHORT=''; LAST_STATUS_DETAIL=''
return ev,buys,mts
if __name__=='__main__':
path=sys.argv[1] if len(sys.argv)>1 else '007340_5min.csv'
# ํƒ€์ž„ํ”„๋ ˆ์ž„: ์ธ์ž๋กœ ์ฃผ๋ฉด ๊ทธ๊ฑฐ, ์—†์œผ๋ฉด ํŒŒ์ผ๋ช…์—์„œ ์ž๋™๊ฐ์ง€
if len(sys.argv)>2:
tf=sys.argv[2]
else:
pl = path.lower()
if '1day' in pl or '_day' in pl or 'daily' in pl: tf='1day'
elif '1hour' in pl or '60min' in pl or '1h' in pl: tf='1hour'
elif '15min' in pl: tf='15min'
else: tf='5min'
print(f"(ํƒ€์ž„ํ”„๋ ˆ์ž„ ์ž๋™๊ฐ์ง€: {tf})")
sf=sys.argv[3] if len(sys.argv)>3 else None
df=pd.read_csv(path,parse_dates=['datetime'],index_col='datetime')
ev,buys,mts=run(df,tf,sf)
print(f"=== {path} ({tf}) | {df.index[0]} ~ {df.index[-1]} ===\n")
print(f"[๋™์‚ฐ ์„ฑ๊ณต {len(mts)}๊ฐœ]")
for m in mts:
print(f" {m['start']} | ์‹œ์ž‘์ 60์„  {m['s60']:,.0f} | ๋์ง€์ 60์„  {m['e60']:,.0f} | ์ƒ๋ช…์„  {m['life']:,.0f}")
print(f"\n[์ด๋ฒคํŠธ]")
for d,t,dt in ev: print(f" {str(d):<20}{t:<24}{dt}")
print(f"\n[๋งค์ˆ˜ {len(buys)}๊ฐœ]")
tot=0
for d,p,a2,lb in buys: print(f" {str(d):<20}{p:>8,.0f}์› {a2:>9,}์› {lb}"); tot+=a2
print(f" ์ด {tot:,}์›")