Spaces:
Running
Running
Update app.py
Browse files์ต์ข ๋ณด๋ฃจ ์ ๋๋งค๋ ์ต์ ์ถ๊ฐ
app.py
CHANGED
|
@@ -434,27 +434,73 @@ def run_ma240(ticker):
|
|
| 434 |
return buf.getvalue()
|
| 435 |
|
| 436 |
|
|
|
|
| 437 |
# ============================================================
|
| 438 |
-
# 3) ์ ๋๋งค๋ (
|
|
|
|
|
|
|
| 439 |
# ============================================================
|
| 440 |
def run_sell(ticker, market_override="์๋"):
|
| 441 |
buf = io.StringIO()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 442 |
try:
|
| 443 |
with contextlib.redirect_stdout(buf):
|
| 444 |
TICKER = ticker
|
|
|
|
| 445 |
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
|
| 452 |
-
|
| 453 |
-
print(
|
| 454 |
-
print("=" * 70)
|
| 455 |
-
print("\n[1/3] ์์ฅ ํ๋ณ ์ค...")
|
| 456 |
-
market = None; name = TICKER
|
| 457 |
mo = (market_override or "์๋").strip()
|
|
|
|
| 458 |
if mo in ('KOSPI', 'KOSDAQ'):
|
| 459 |
market = mo
|
| 460 |
print(f" (์๋ ์ง์ ) {market}")
|
|
@@ -475,166 +521,108 @@ def run_sell(ticker, market_override="์๋"):
|
|
| 475 |
break
|
| 476 |
if market is None:
|
| 477 |
market = 'KOSDAQ'
|
| 478 |
-
print(f" ๋ชฉ๋ก์์ ๋ชป ์ฐพ์ -> KOSDAQ ๊ฐ์ (์ '์์ฅ' ๋ฉ๋ด์์ ์ง์ ์ ํ
|
| 479 |
else:
|
| 480 |
print(f" {name} ({TICKER}) / {market}")
|
| 481 |
except Exception as e:
|
| 482 |
market = 'KOSDAQ'
|
| 483 |
-
print(f" ํ๋ณ ์คํจ({type(e).__name__}) -> KOSDAQ ๊ฐ์ . "
|
| 484 |
-
f"์ '์์ฅ' ๋ฉ๋ด์์ KOSPI/KOSDAQ
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
if
|
| 489 |
-
print("
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
index_peak_value = float(recent_idx.max())
|
| 514 |
-
stock_at_peak = float(merged.loc[index_peak_date, 'stock']) if index_peak_date in merged.index else None
|
| 515 |
-
|
| 516 |
-
latest_date = merged.index[-1]
|
| 517 |
-
is_ath = (index_peak_date == latest_date)
|
| 518 |
-
|
| 519 |
-
days_from_peak = (latest_date.to_pydatetime().replace(tzinfo=None) -
|
| 520 |
-
index_peak_date.to_pydatetime().replace(tzinfo=None)).days
|
| 521 |
-
|
| 522 |
-
print(f" ์ง์ ๊ณ ์ : {index_peak_value:,.2f} ({str(index_peak_date)[:10]})")
|
| 523 |
-
print(f" ์ ์ ๋ ์ข
๋ชฉ: {stock_at_peak:,.0f}" if stock_at_peak else " ์ ์ ๋ ์ข
๋ชฉ: ์์")
|
| 524 |
-
print(f" ์ค๋ ์ง์: {float(merged['index'].iloc[-1]):,.2f} ({str(latest_date)[:10]})")
|
| 525 |
-
print(f" ATH ์ฌ๋ถ: {'์ โ RS Line ๋ถ์' if is_ath else f'์๋์ค (๊ณ ์ {days_from_peak}์ผ ๊ฒฝ๊ณผ) โ ์ด๊ฒฉ ๋ถ์'}")
|
| 526 |
-
|
| 527 |
-
FLAG_START = str(index_peak_date)[:10]
|
| 528 |
-
|
| 529 |
-
if not is_ath:
|
| 530 |
-
flag_data = merged[merged.index >= FLAG_START].copy()
|
| 531 |
-
if len(flag_data) == 0:
|
| 532 |
-
print("๋ฐ์ดํฐ ์์"); sys.exit()
|
| 533 |
-
|
| 534 |
-
print("\n" + "=" * 70)
|
| 535 |
-
print("[์ด๊ฒฉ ๋ถ์] ์ง์ ๊ณ ์ ๊ธฐ์ค")
|
| 536 |
-
print(f"์ง์ ์ ์ : {FLAG_START} ({index_peak_value:,.2f})")
|
| 537 |
-
print("=" * 70)
|
| 538 |
-
print(f"\n{'๋ ์ง':<12} {'์ข
๋ชฉ๊ฐ':>8} {'์ง์':>8} {'์ง์์์ต':>9} {'์ข
๋ชฉ์์ต':>9} {'์ด๊ฒฉ':>8} {'flag':>5} ํ์ ")
|
| 539 |
-
print("-" * 78)
|
| 540 |
-
|
| 541 |
-
div_flag = False; div_flag_value = None
|
| 542 |
-
sell_signal = False; sell_date = None; sell_reason = None
|
| 543 |
-
|
| 544 |
-
for date, row in flag_data.iterrows():
|
| 545 |
-
s = float(row['stock']); idx = float(row['index'])
|
| 546 |
-
if stock_at_peak is None:
|
| 547 |
-
continue
|
| 548 |
-
ir = (idx / index_peak_value) - 1
|
| 549 |
-
sr = (s / stock_at_peak) - 1
|
| 550 |
div = sr - ir
|
| 551 |
-
verdict = ''
|
| 552 |
if div <= DIV_THRESHOLD:
|
| 553 |
if not div_flag:
|
| 554 |
-
div_flag = True; div_flag_value = div
|
| 555 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 556 |
else:
|
| 557 |
-
|
| 558 |
-
verdict = '!!! ์ ๋๋งค๋!'
|
| 559 |
-
if not sell_signal:
|
| 560 |
-
sell_signal = True; sell_date = str(date)[:10]
|
| 561 |
-
sell_reason = f'์ด๊ฒฉ {div*100:.1f}%'
|
| 562 |
-
div_flag_value = div
|
| 563 |
-
else:
|
| 564 |
-
verdict = 'Watch'; div_flag_value = div
|
| 565 |
else:
|
| 566 |
-
|
| 567 |
-
div_flag = False; div_flag_value = None; verdict = 'FLAG OFF'
|
| 568 |
-
|
| 569 |
-
print(f"{str(date)[:10]:<12} {s:>8,.0f} {idx:>8,.0f} "
|
| 570 |
-
f"{ir*100:>+8.1f}% {sr*100:>+8.1f}% {div*100:>+7.1f}% "
|
| 571 |
-
f"{'ON' if div_flag else '':>5} {verdict}")
|
| 572 |
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
if
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
print(f" ์ง์ ์ ์ : {index_peak_value:,.2f} ({FLAG_START})")
|
| 584 |
-
print(f" ์ ์ ๋ ์ข
๋ชฉ : {stock_at_peak:,.0f}")
|
| 585 |
-
print(f" ํ์ฌ ์ข
๋ชฉ : {float(last['stock']):,.0f}")
|
| 586 |
-
print(f" ์ด๊ฒฉ : {dv_now*100:+.1f}% (๊ธฐ์ค {DIV_THRESHOLD*100:.0f}%)")
|
| 587 |
-
print(f" div_flag : {'ON' if div_flag else 'off'}")
|
| 588 |
-
|
| 589 |
-
print("\n" + "=" * 70)
|
| 590 |
-
print("์ต์ข
์์ฝ")
|
| 591 |
-
print("=" * 70)
|
| 592 |
-
print(f"์ข
๋ชฉ : {name} ({TICKER}) / {market}")
|
| 593 |
-
print(f"์ง์ ์ ์ : {FLAG_START} ({index_peak_value:,.2f}) | {days_from_peak}์ผ ๊ฒฝ๊ณผ")
|
| 594 |
-
print(f"์ด๊ฒฉ ์ ํธ : {('>>> ' + sell_date) if sell_signal else 'None'}")
|
| 595 |
-
print()
|
| 596 |
-
if sell_signal:
|
| 597 |
-
print(f"==> ๋งค๋ ์ ํธ: {sell_date}")
|
| 598 |
-
else:
|
| 599 |
-
print("==> ์์ง ๋งค๋ ์ ํธ ์์. ๊ณ์ ๋ณด์ .")
|
| 600 |
-
|
| 601 |
-
else:
|
| 602 |
-
print("\n" + "=" * 70)
|
| 603 |
-
print(f"[RS Line ๋ถ์] MA{RS_MA_PERIOD} - ์ค๋์ด ์ง์ ๊ณ ์ (ATH)")
|
| 604 |
-
print("=" * 70)
|
| 605 |
-
|
| 606 |
-
rs_all = merged.copy()
|
| 607 |
-
rs_all['rs'] = rs_all['stock'] / rs_all['index']
|
| 608 |
-
rs_all['rs_ma'] = rs_all['rs'].rolling(RS_MA_PERIOD).mean()
|
| 609 |
-
rs_flag = rs_all[rs_all.index >= FLAG_START].dropna(subset=['rs_ma'])
|
| 610 |
-
|
| 611 |
-
rs_sell_signal = False; rs_sell_date = None
|
| 612 |
-
|
| 613 |
-
if len(rs_flag) == 0:
|
| 614 |
-
print("RS MA ๊ณ์ฐ ๋ถ๊ฐ (๋ฐ์ดํฐ ๋ถ์กฑ)")
|
| 615 |
-
else:
|
| 616 |
-
print(f"\n{'๋ ์ง':<12} {'์ข
๋ชฉ๊ฐ':>8} {'RS':>10} {'RS MA20':>10} {'vs MA':>8} ํ์ ")
|
| 617 |
-
print("-" * 58)
|
| 618 |
-
for date, row in rs_flag.iterrows():
|
| 619 |
-
rs = float(row['rs']); rma = float(row['rs_ma'])
|
| 620 |
-
pct = (rs / rma - 1) * 100
|
| 621 |
-
v = '>>> ๋ฐ๋ํฌ๋ก์ค!' if rs < rma else 'Golden'
|
| 622 |
-
if rs < rma and not rs_sell_signal:
|
| 623 |
-
rs_sell_signal = True; rs_sell_date = str(date)[:10]
|
| 624 |
-
print(f"{str(date)[:10]:<12} {float(row['stock']):>8,.0f} "
|
| 625 |
-
f"{rs:>10.4f} {rma:>10.4f} {pct:>+7.1f}% {v}")
|
| 626 |
-
|
| 627 |
-
print("\n" + "=" * 70)
|
| 628 |
-
print("์ต์ข
์์ฝ")
|
| 629 |
-
print("=" * 70)
|
| 630 |
-
print(f"์ข
๋ชฉ : {name} ({TICKER}) / {market}")
|
| 631 |
-
print(f"์ง์ ์ ์ : {FLAG_START} ({index_peak_value:,.2f}) | ATH")
|
| 632 |
-
print(f"RS ์ ํธ : {('>>> ' + rs_sell_date) if rs_sell_signal else 'None'}")
|
| 633 |
-
print()
|
| 634 |
-
if rs_sell_signal:
|
| 635 |
-
print(f"==> RS Line ๋งค๋ ์ ํธ: {rs_sell_date}")
|
| 636 |
else:
|
| 637 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 638 |
except SystemExit:
|
| 639 |
pass
|
| 640 |
except Exception as e:
|
|
@@ -928,7 +916,7 @@ VCP_DETECT = "VCP ๋ํ ๊ฐ์ง"
|
|
| 928 |
VCP_SELL = "VCP ์คํจ ๋ถ๋ถ๋งค๋"
|
| 929 |
BOLL = "๋ณผ๋ฆฐ์ ์นด์ดํธ"
|
| 930 |
MA240 = "240์ ์ถ์ธ์ฌ๋ง ์ปท"
|
| 931 |
-
SELL = "์ ๋๋งค๋ (
|
| 932 |
|
| 933 |
CHOICES = [VCP_DETECT, VCP_SELL, BOLL, MA240, SELL]
|
| 934 |
|
|
|
|
| 434 |
return buf.getvalue()
|
| 435 |
|
| 436 |
|
| 437 |
+
|
| 438 |
# ============================================================
|
| 439 |
+
# 3) ์ ๋๋งค๋ (์ตํ๋ณด๋ฃจ) (final_sell_monitor.py ๋ก์ง ์ด์ ยท ๋จ์ผ ํฐ์ปค)
|
| 440 |
+
# ์ฃผ ์ ํธ : ์๊ธฐ๊ณ ์ ์ด๊ฒฉ -10% -> 22์ผ๋ด lower-low ์ฌํ์ธ (240์ฌ ๊ตฌ๊ฐ)
|
| 441 |
+
# ๋ฐฑ์
: ์ข
๊ฐ๊ฐ 240์ BREAK_CONFIRM์ผ ์ฐ์ ์ดํ
|
| 442 |
# ============================================================
|
| 443 |
def run_sell(ticker, market_override="์๋"):
|
| 444 |
buf = io.StringIO()
|
| 445 |
+
|
| 446 |
+
DIV_THRESHOLD = -0.10 # ์ด๊ฒฉ ๊ธฐ์ค
|
| 447 |
+
CONFIRM_WINDOW = 22 # lower-low ์ฌํ์ธ ์๋์ฐ(๊ฑฐ๋์ผ)
|
| 448 |
+
TREND_MIN_DAYS = 60 # '240์ฌ': ์ต๊ทผ N๊ฑฐ๋์ผ ์ฐ์ 240์ ์
|
| 449 |
+
TREND_MIN_GAP = 0.05 # '240์ฌ': 240์ +5% ์ด์
|
| 450 |
+
BREAK_PCT = 0.00 # ๋ฐฑ์
240์ ์ดํ ๊ธฐ์ค
|
| 451 |
+
BREAK_CONFIRM = 2 # ๋ฐฑ์
: ์ฐ์ N์ผ ์ดํ
|
| 452 |
+
DATA_BACK_DAYS = 900 # 240MA ์๋ฐ์
ํฌํจ ๋ก๋(โ2.5๋
)
|
| 453 |
+
|
| 454 |
+
def in_240_run(stock, ma240, i):
|
| 455 |
+
if i - TREND_MIN_DAYS + 1 < 0:
|
| 456 |
+
return False
|
| 457 |
+
m = ma240[i]
|
| 458 |
+
if np.isnan(m) or stock[i] <= m * (1 + TREND_MIN_GAP):
|
| 459 |
+
return False
|
| 460 |
+
for k in range(i - TREND_MIN_DAYS + 1, i + 1):
|
| 461 |
+
if np.isnan(ma240[k]) or stock[k] <= ma240[k]:
|
| 462 |
+
return False
|
| 463 |
+
return True
|
| 464 |
+
|
| 465 |
+
def load(tk, market):
|
| 466 |
+
end = datetime.today().strftime('%Y-%m-%d')
|
| 467 |
+
start = (datetime.today() - timedelta(days=DATA_BACK_DAYS)).strftime('%Y-%m-%d')
|
| 468 |
+
sdf = fdr.DataReader(tk, start, end)
|
| 469 |
+
if sdf is None or len(sdf) == 0:
|
| 470 |
+
return None
|
| 471 |
+
idx_code = 'KS11' if market == 'KOSPI' else 'KQ11'
|
| 472 |
+
idf = None
|
| 473 |
+
for c in (idx_code, '^' + idx_code):
|
| 474 |
+
try:
|
| 475 |
+
t = fdr.DataReader(c, start, end)
|
| 476 |
+
if t is not None and len(t) > 0:
|
| 477 |
+
idf = t; break
|
| 478 |
+
except Exception:
|
| 479 |
+
continue
|
| 480 |
+
if idf is None:
|
| 481 |
+
return None
|
| 482 |
+
merged = pd.concat([sdf['Close'].rename('stock'), idf['Close'].rename('index')],
|
| 483 |
+
axis=1, sort=True)
|
| 484 |
+
merged['index'] = merged['index'].ffill()
|
| 485 |
+
merged = merged.dropna()
|
| 486 |
+
merged['ma240'] = merged['stock'].rolling(240).mean()
|
| 487 |
+
return merged
|
| 488 |
+
|
| 489 |
try:
|
| 490 |
with contextlib.redirect_stdout(buf):
|
| 491 |
TICKER = ticker
|
| 492 |
+
name = TICKER
|
| 493 |
|
| 494 |
+
print("=" * 72)
|
| 495 |
+
print(f"์ ๋๋งค๋ ์ตํ๋ณด๋ฃจ ๋ชจ๋ํฐ | {datetime.today().strftime('%Y-%m-%d %H:%M')}")
|
| 496 |
+
print(f"์ฃผ ์ ํธ: 240์ฌ + ์ด๊ฒฉ {DIV_THRESHOLD*100:.0f}% lower-low ์ฌํ์ธ({CONFIRM_WINDOW}์ผ)"
|
| 497 |
+
f" | ๋ฐฑ์
: 240์ {BREAK_CONFIRM}์ผ ์ฐ์ ์ดํ")
|
| 498 |
+
print("=" * 72)
|
| 499 |
|
| 500 |
+
# --- ์์ฅ ๊ฒฐ์ (์๋ ์ฐ์ , ์๋์ KRX ๊ฐ๋ฅํ ๋๋ง) ---
|
| 501 |
+
print("\n[์์ฅ ํ๋ณ]")
|
|
|
|
|
|
|
|
|
|
| 502 |
mo = (market_override or "์๋").strip()
|
| 503 |
+
market = None
|
| 504 |
if mo in ('KOSPI', 'KOSDAQ'):
|
| 505 |
market = mo
|
| 506 |
print(f" (์๋ ์ง์ ) {market}")
|
|
|
|
| 521 |
break
|
| 522 |
if market is None:
|
| 523 |
market = 'KOSDAQ'
|
| 524 |
+
print(f" ๋ชฉ๋ก์์ ๋ชป ์ฐพ์ -> KOSDAQ ๊ฐ์ (์ '์์ฅ' ๋ฉ๋ด์์ ์ง์ ์ ํ)")
|
| 525 |
else:
|
| 526 |
print(f" {name} ({TICKER}) / {market}")
|
| 527 |
except Exception as e:
|
| 528 |
market = 'KOSDAQ'
|
| 529 |
+
print(f" ์๋ํ๋ณ ์คํจ({type(e).__name__}) -> KOSDAQ ๊ฐ์ . "
|
| 530 |
+
f"์ '์์ฅ' ๋ฉ๋ด์์ KOSPI/KOSDAQ ์ง์ ์ ํ ๊ถ์ฅ.")
|
| 531 |
+
|
| 532 |
+
# --- ๋ฐ์ดํฐ ๋ก๋ ---
|
| 533 |
+
df = load(TICKER, market)
|
| 534 |
+
if df is None:
|
| 535 |
+
print(f"\n[{TICKER} {name}] ๋ฐ์ดํฐ ๋ก๋ ์คํจ (ํฐ์ปค/์์ฅ ํ์ธ)"); sys.exit()
|
| 536 |
+
|
| 537 |
+
dates = [str(d)[:10] for d in df.index]
|
| 538 |
+
stock = df['stock'].values.astype(float)
|
| 539 |
+
index = df['index'].values.astype(float)
|
| 540 |
+
ma240 = df['ma240'].values.astype(float)
|
| 541 |
+
n = len(stock)
|
| 542 |
+
|
| 543 |
+
# --- ์ํฌํฌ์๋: ์๊ธฐ๊ณ ์ + ์ด๊ฒฉ ์ด๋ฒคํธ + lower-low ์ฌํ์ธ (๊ฐ์ฅ ์ต๊ทผ ๊ฒ ์ถ์ ) ---
|
| 544 |
+
running_peak = -1.0; index_at_peak = None; peak_date = None
|
| 545 |
+
div_flag = False; div_flag_value = None; div_sold = False
|
| 546 |
+
last_event = None
|
| 547 |
+
confirmed = None
|
| 548 |
+
|
| 549 |
+
for t in range(n):
|
| 550 |
+
st = stock[t]; it = index[t]
|
| 551 |
+
if running_peak < 0 or st >= running_peak:
|
| 552 |
+
running_peak = st; index_at_peak = it; peak_date = dates[t]
|
| 553 |
+
div_flag = False; div_flag_value = None; div_sold = False
|
| 554 |
+
last_event = None
|
| 555 |
+
confirmed = None
|
| 556 |
+
else:
|
| 557 |
+
sr = st / running_peak - 1
|
| 558 |
+
ir = (it / index_at_peak - 1) if index_at_peak else 0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 559 |
div = sr - ir
|
|
|
|
| 560 |
if div <= DIV_THRESHOLD:
|
| 561 |
if not div_flag:
|
| 562 |
+
div_flag = True; div_flag_value = div; div_sold = False
|
| 563 |
+
elif div < div_flag_value:
|
| 564 |
+
if not div_sold:
|
| 565 |
+
if (last_event is not None
|
| 566 |
+
and t - last_event['i'] <= CONFIRM_WINDOW
|
| 567 |
+
and st < last_event['price']):
|
| 568 |
+
confirmed = {'i': t, 'date': dates[t], 'price': st,
|
| 569 |
+
'prev_date': last_event['date'],
|
| 570 |
+
'run': in_240_run(stock, ma240, t)}
|
| 571 |
+
last_event = {'i': t, 'price': st, 'date': dates[t]}
|
| 572 |
+
div_sold = True
|
| 573 |
+
div_flag_value = div
|
| 574 |
else:
|
| 575 |
+
div_flag_value = div
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 576 |
else:
|
| 577 |
+
div_flag = False; div_flag_value = None; div_sold = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 578 |
|
| 579 |
+
# --- ๋ฐฑ์
: 240์ ์ดํ (์ฐ์ BREAK_CONFIRM์ผ) ---
|
| 580 |
+
break_240 = None
|
| 581 |
+
streak = 0
|
| 582 |
+
for t in range(n):
|
| 583 |
+
if np.isnan(ma240[t]):
|
| 584 |
+
continue
|
| 585 |
+
if stock[t] < ma240[t] * (1 - BREAK_PCT):
|
| 586 |
+
streak += 1
|
| 587 |
+
if streak >= BREAK_CONFIRM and break_240 is None:
|
| 588 |
+
break_240 = dates[t]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 589 |
else:
|
| 590 |
+
streak = 0
|
| 591 |
+
break_240 = None
|
| 592 |
+
|
| 593 |
+
# --- ์ค๋ ์ํ ---
|
| 594 |
+
t = n - 1
|
| 595 |
+
today = dates[t]
|
| 596 |
+
m = ma240[t]
|
| 597 |
+
run_now = in_240_run(stock, ma240, t)
|
| 598 |
+
sr_now = stock[t] / running_peak - 1 if running_peak > 0 else 0.0
|
| 599 |
+
ir_now = (index[t] / index_at_peak - 1) if index_at_peak else 0.0
|
| 600 |
+
div_now = sr_now - ir_now
|
| 601 |
+
|
| 602 |
+
print("\n" + "=" * 72)
|
| 603 |
+
print(f"[{TICKER} {name}] {market} | {today} ์ข
๊ฐ {stock[t]:,.0f}"
|
| 604 |
+
+ (f" | 240์ {m:,.0f} ({(stock[t]/m-1)*100:+.1f}%)" if not np.isnan(m) else " | 240์ ๋ฐ์ดํฐ๋ถ์กฑ"))
|
| 605 |
+
print(f" 240์ฌ ์ฌ๋ถ : {'์ (60์ผ ์ฐ์ 240์ + 5%โ)' if run_now else '์๋์ค'}")
|
| 606 |
+
print(f" ์ข
๋ชฉ ๊ณ ์ : {running_peak:,.0f} ({peak_date}) | ํ์ฌ ์ด๊ฒฉ {div_now*100:+.1f}%"
|
| 607 |
+
f" (๊ธฐ์ค {DIV_THRESHOLD*100:.0f}%) | flag {'ON' if div_flag else 'off'}")
|
| 608 |
+
if last_event and not confirmed:
|
| 609 |
+
remain = CONFIRM_WINDOW - (t - last_event['i'])
|
| 610 |
+
if remain > 0:
|
| 611 |
+
print(f" 1์ฐจ ์ด๋ฒคํธ : {last_event['date']} @ {last_event['price']:,.0f}"
|
| 612 |
+
f" -> ์ฌํ์ธ ๋๊ธฐ {remain}๊ฑฐ๋์ผ ๋จ์ (์ด๋ณด๋ค ๋ฎ์ 2์ฐจ ์ด๋ฒคํธ๋ฉด ์ ๋๋งค๋)")
|
| 613 |
+
|
| 614 |
+
# --- ํ์ ---
|
| 615 |
+
if confirmed:
|
| 616 |
+
tag = '240์ฌ' if confirmed['run'] else '๋น240์ฌ(์ฐธ๊ณ )'
|
| 617 |
+
ago = (n - 1) - confirmed['i']
|
| 618 |
+
print(f"\n >>> [์ฃผ ์ ํธ] ์ ๋๋งค๋: {confirmed['date']} @ {confirmed['price']:,.0f}"
|
| 619 |
+
f" (1์ฐจ {confirmed['prev_date']} -> lower-low ์ฌํ์ธ, {tag}) | {ago}๊ฑฐ๋์ผ ์ ")
|
| 620 |
+
if not confirmed['run']:
|
| 621 |
+
print(" (240์ฌ ๊ตฌ๊ฐ์ด ์๋์ด์ ์ ๋ขฐ๋๋ ํ ๋จ๊ณ ๋ฎ์ โ ์ฐจํธ๋ก ํ์ธ)")
|
| 622 |
+
if break_240:
|
| 623 |
+
print(f"\n >>> [๋ฐฑ์
] 240์ ์ดํ ๋งค๋: {break_240} (์ฐ์ {BREAK_CONFIRM}์ผ ์ดํ, ํ์ฌ๋ ์ดํ ์ค)")
|
| 624 |
+
if not confirmed and not break_240:
|
| 625 |
+
print(f"\n ==> ๋งค๋ ์ ํธ ์์. ๋ณด์ ์ ์ง.")
|
| 626 |
except SystemExit:
|
| 627 |
pass
|
| 628 |
except Exception as e:
|
|
|
|
| 916 |
VCP_SELL = "VCP ์คํจ ๋ถ๋ถ๋งค๋"
|
| 917 |
BOLL = "๋ณผ๋ฆฐ์ ์นด์ดํธ"
|
| 918 |
MA240 = "240์ ์ถ์ธ์ฌ๋ง ์ปท"
|
| 919 |
+
SELL = "์ ๋๋งค๋ (์ตํ๋ณด๋ฃจ)"
|
| 920 |
|
| 921 |
CHOICES = [VCP_DETECT, VCP_SELL, BOLL, MA240, SELL]
|
| 922 |
|