glorifiedjx commited on
Commit
c56af2b
ยท
verified ยท
1 Parent(s): 8b7a5fd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +118 -129
app.py CHANGED
@@ -1,145 +1,134 @@
1
  # -*- coding: utf-8 -*-
2
  """
3
- ์ฃผ์‹ ๋””๋ฒ„๊ฑฐ (๋ชจ๋ฐ”์ผ ์›น์•ฑ) โ€” 3๊ฐœ ๋ถ„์„ / Hugging Face Spaces ๋ฐฐํฌ์šฉ
4
-
5
- ํƒญ(๋ถ„์„):
6
- 1) VCP ๋ŒํŒŒ ๊ฐ์ง€ (vcp_debug.py) โ† ๋‚ ์งœ ์ž๋™: ์ตœ๊ทผ 1๋…„
7
- 2) ๋ณผ๋ฆฐ์ € ์นด์šดํŠธ (bollinger_count_debug.py) โ† ๋‚ ์งœ ์ž๋™: ์ตœ๊ทผ 1๋…„
8
- 3) ์ „๋Ÿ‰๋งค๋„ ์ตœํ›„๋ณด๋ฃจ (final_sell_monitor.py) โ† ์ตœ์ข… ํ™•์ • ๋ฒ„์ „
9
-
10
- ์›์น™: ๋ฐ์Šคํฌํƒ‘ ์Šคํฌ๋ฆฝํŠธ์˜ ๋กœ์ง/์ถœ๋ ฅ์„ ๊ทธ๋Œ€๋กœ ์žฌํ˜„.
11
- - ํ•จ์ˆ˜ํ˜•(analyze_one) ์Šคํฌ๋ฆฝํŠธ: import ํ›„ ํ•จ์ˆ˜ ํ˜ธ์ถœ
12
- - ์ตœ์ƒ์œ„ ์‹คํ–‰ํ˜•(VCPยท๋ณผ๋ฆฐ์ €): ์†Œ์Šค๋ฅผ ์ฝ์–ด TICKER๋งŒ ๋ฐ”๊ฟ” exec (sys.exit ๊ฐ€๋“œ)
13
- ๋ชจ๋“  ์ถœ๋ ฅ์€ contextlib.redirect_stdout ๋กœ ๊ทธ๋Œ€๋กœ ์บก์ฒ˜ํ•ด ํ™”๋ฉด์— ํ‘œ์‹œ.
14
-
15
- HF Spaces(ํ•œ๊ตญ ์™ธ IP)์—์„œ๋„ ๋„ค์ด๋ฒ„ ์ฃผ๊ฐ€/์ง€์ˆ˜ ๋ฐ์ดํ„ฐ๋Š” ์ •์ƒ ๋กœ๋“œ๋จ.
16
- KRX(StockListing)๋งŒ ๋ง‰ํžˆ๋ฏ€๋กœ ์‹œ์žฅ(KOSPI/KOSDAQ)์€ ์ง์ ‘ ์„ ํƒ.
17
  """
18
 
19
- import io
20
  import os
21
- import re
22
- import contextlib
23
- from datetime import datetime, timedelta
24
-
25
  import gradio as gr
26
 
27
- # ํ•จ์ˆ˜ํ˜• ์Šคํฌ๋ฆฝํŠธ๋Š” import ํ•ด์„œ ํ˜ธ์ถœ (์ตœ์ƒ์œ„์—์„œ ๋ถ„์„์ด ์‹คํ–‰๋˜์ง€ ์•Š์Œ)
28
- import final_sell_monitor
29
 
30
  HERE = os.path.dirname(os.path.abspath(__file__))
31
- BOLL_PATH = os.path.join(HERE, "bollinger_count_debug.py")
32
- VCP_PATH = os.path.join(HERE, "vcp_debug.py")
33
 
34
- _TICKER_RE = re.compile(r"TICKER\s*=\s*['\"][0-9A-Za-z]+['\"]")
35
 
36
 
37
- def _safe(fn, *args):
38
- """์–ด๋–ค ๋ถ„์„์ด๋“  ์˜ˆ์™ธ๊ฐ€ ๋‚˜๋„ ๋นจ๊ฐ„ '์˜ค๋ฅ˜' ๋Œ€์‹  ์ด์œ ๋ฅผ ๊ธ€๋กœ ๋ฐ˜ํ™˜."""
 
39
  try:
40
- return fn(*args)
 
 
 
 
 
 
 
 
41
  except Exception as e:
42
- msg = str(e).strip().splitlines()[0] if str(e).strip() else type(e).__name__
43
- return (f"[์˜ค๋ฅ˜] {msg[:200]}\n"
44
- f"์ข…๋ชฉ์ฝ”๋“œ๊ฐ€ ๋งž๋Š”์ง€, ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด ๋ณด์„ธ์š”.\n"
45
- f"(๋ฐ์ดํ„ฐ ์ œ๊ณต์ฒ˜(๋„ค์ด๋ฒ„) ์ผ์‹œ ์˜ค๋ฅ˜์ด๊ฑฐ๋‚˜, ์‹ ๊ทœ/์ •์ง€/ํ์ง€ ์ข…๋ชฉ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)")
46
-
47
-
48
- def _exec_script(path, ticker, extra_subs=None):
49
- """์ตœ์ƒ์œ„ ์‹คํ–‰ํ˜• ์Šคํฌ๋ฆฝํŠธ: TICKER(๋ฐ ์ถ”๊ฐ€ ์น˜ํ™˜)๋งŒ ๋ฐ”๊ฟ” exec, ์ถœ๋ ฅ ์บก์ฒ˜."""
50
- buf = io.StringIO()
51
- ns = {"__name__": "__sandbox__"} # __main__ ๊ฐ€๋“œ ๋น„ํ™œ์„ฑ
52
- with contextlib.redirect_stdout(buf):
53
- try:
54
- with open(path, encoding="utf-8") as f:
55
- src = f.read()
56
- src = _TICKER_RE.sub(f"TICKER = '{ticker}'", src, count=1)
57
- if extra_subs:
58
- for pat, repl in extra_subs:
59
- src = re.sub(pat, repl, src, count=1)
60
- exec(compile(src, path, "exec"), ns)
61
- except SystemExit:
62
- pass # ์Šคํฌ๋ฆฝํŠธ์˜ sys.exit() ๊ฐ€ ์•ฑ์„ ์ฃฝ์ด์ง€ ์•Š๊ฒŒ
63
- except Exception as e:
64
- msg = str(e).strip().splitlines()[0] if str(e).strip() else type(e).__name__
65
- print(f"\n[์˜ค๋ฅ˜] {msg[:200]}")
66
- return buf.getvalue() or "์ถœ๋ ฅ ์—†์Œ"
67
-
68
-
69
- def _call_func(fn, *args):
70
- """ํ•จ์ˆ˜ํ˜• ์Šคํฌ๋ฆฝํŠธ: ํ•จ์ˆ˜ ํ˜ธ์ถœํ•˜๋ฉฐ ์ถœ๋ ฅ ์บก์ฒ˜."""
71
- buf = io.StringIO()
72
- with contextlib.redirect_stdout(buf):
73
- try:
74
- fn(*args)
75
- except SystemExit:
76
- pass
77
- except Exception as e:
78
- msg = str(e).strip().splitlines()[0] if str(e).strip() else type(e).__name__
79
- print(f"\n[์˜ค๋ฅ˜] {msg[:200]}")
80
- print("์ข…๋ชฉ์ฝ”๋“œ์™€ ์‹œ์žฅ(KOSDAQ/KOSPI)์ด ๋งž๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.")
81
- return buf.getvalue() or "์ถœ๋ ฅ ์—†์Œ"
82
-
83
-
84
- # ---------- ๋ถ„์„๋ณ„ ์‹คํ–‰ ----------
85
- def run_bollinger(ticker):
86
- ticker = (ticker or "").strip()
87
- if not ticker:
88
- return "์ข…๋ชฉ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š” (์˜ˆ: 420770)"
89
- today = datetime.today()
90
- frm = (today - timedelta(days=365)).strftime("%Y-%m-%d")
91
- to = today.strftime("%Y-%m-%d")
92
- subs = [
93
- (r"DEBUG_FROM\s*=\s*['\"][0-9-]+['\"]", f"DEBUG_FROM = '{frm}'"),
94
- (r"DEBUG_TO\s*=\s*['\"][0-9-]+['\"]", f"DEBUG_TO = '{to}'"),
95
- ]
96
- return _exec_script(BOLL_PATH, ticker, subs)
97
-
98
-
99
- def run_vcp(ticker):
100
- ticker = (ticker or "").strip()
101
- if not ticker:
102
- return "์ข…๋ชฉ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š” (์˜ˆ: 007340)"
103
- return _exec_script(VCP_PATH, ticker)
104
-
105
-
106
- def run_final(ticker, name):
107
- ticker = (ticker or "").strip()
108
- if not ticker:
109
- return "์ข…๋ชฉ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š” (์˜ˆ: 195940)"
110
- # ์‹œ์žฅ์€ ์ž๋™ํŒ๋ณ„ (์ˆ˜๋™ ์„ ํƒ ์—†์Œ -> ์ž˜๋ชป ๊ณ ๋ฅผ ์ผ ์—†์Œ)
111
- h = {"ticker": ticker, "name": (name or "").strip() or None}
112
- return _call_func(final_sell_monitor.analyze_one, h)
113
-
114
-
115
- # ---------- UI ----------
116
- with gr.Blocks(title="์ฃผ์‹ ๋””๋ฒ„๊ฑฐ") as demo:
117
- gr.Markdown("## ์ฃผ์‹ ๋””๋ฒ„๊ฑฐ (๋ชจ๋ฐ”์ผ)\n๋ถ„์„์„ ๊ณ ๋ฅด๊ณ  ์ข…๋ชฉ์ฝ”๋“œ๋งŒ ๋„ฃ์œผ๋ฉด ๋ฐ์Šคํฌํƒ‘๊ณผ ๋™์ผํ•œ ์ถœ๋ ฅ์ด ๋‚˜์˜ต๋‹ˆ๋‹ค.")
118
-
119
- with gr.Tab("๋ณผ๋ฆฐ์ € ์นด์šดํŠธ"):
120
- b_tk = gr.Textbox(label="์ข…๋ชฉ์ฝ”๋“œ", placeholder="์˜ˆ: 420770")
121
- b_btn = gr.Button("์‹คํ–‰", variant="primary")
122
- b_out = gr.Textbox(label="๊ฒฐ๊ณผ (์ตœ๊ทผ 1๋…„ ์ž๋™)", lines=20)
123
- b_btn.click(lambda tk: _safe(run_bollinger, tk), inputs=b_tk, outputs=b_out)
124
-
125
- with gr.Tab("VCP ๋ŒํŒŒ"):
126
- v_tk = gr.Textbox(label="์ข…๋ชฉ์ฝ”๋“œ", placeholder="์˜ˆ: 007340")
127
- v_btn = gr.Button("์‹คํ–‰", variant="primary")
128
- v_out = gr.Textbox(label="๊ฒฐ๊ณผ (์ตœ๊ทผ 1๋…„ ์ž๋™)", lines=20)
129
- v_btn.click(lambda tk: _safe(run_vcp, tk), inputs=v_tk, outputs=v_out)
130
-
131
- with gr.Tab("์ „๋Ÿ‰๋งค๋„ ์ตœํ›„๋ณด๋ฃจ"):
132
- f_tk = gr.Textbox(label="์ข…๋ชฉ์ฝ”๋“œ", placeholder="์˜ˆ: 195940")
133
- f_nm = gr.Textbox(label="์ข…๋ชฉ๋ช…(์„ ํƒ)", placeholder="์˜ˆ: HK์ด๋…ธ์—”")
134
- gr.Markdown("โ€ป ์‹œ์žฅ(KOSPI/KOSDAQ)์€ ์ข…๋ชฉ์ฝ”๋“œ๋กœ ์ž๋™ ํŒ๋ณ„๋ฉ๋‹ˆ๋‹ค.")
135
- f_btn = gr.Button("์‹คํ–‰", variant="primary")
136
- f_out = gr.Textbox(label="๊ฒฐ๊ณผ", lines=16)
137
- f_btn.click(lambda a, c: _safe(run_final, a, c), inputs=[f_tk, f_nm], outputs=f_out)
138
-
139
 
140
  if __name__ == "__main__":
141
- # HF Spaces ํ‘œ์ค€ ์‹คํ–‰ (ํฌํŠธ 7860 ๊ณ ์ •) โ€” ๋กœ๊ทธ์ธ ์—†์Œ
142
- demo.launch(
143
- server_name="0.0.0.0",
144
- server_port=7860,
145
- )
 
1
  # -*- coding: utf-8 -*-
2
  """
3
+ ์ „๋Ÿ‰๋งค๋„ ์ตœํ›„๋ณด๋ฃจ - ๋ชจ๋ฐ”์ผ ์›น ๋ชจ๋‹ˆํ„ฐ (Hugging Face Spaces / Gradio)
4
+ ==================================================================
5
+ confirm_sell_backtest.py ์˜ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ทธ๋Œ€๋กœ import (ํŒŒ๋ฆฌํ‹ฐ 100%).
6
+ ์‹œ์žฅ(KOSPI/KOSDAQ)์€ market_map.csv ๋กœ ์ž๋™ํŒ๋ณ„ (KRX StockListing ํ˜ธ์ถœ ์•ˆ ํ•จ).
7
+
8
+ HF Space ์— ๊ฐ™์ด ์˜ฌ๋ฆด ํŒŒ์ผ:
9
+ app.py
10
+ confirm_sell_backtest.py
11
+ market_map.csv (code,market ๋‘ ์ปฌ๋Ÿผ)
12
+ requirements.txt
13
+
14
+ ์ฐธ๊ณ : HF(ํ•ด์™ธ IP)์—์„œ๋„ ๋„ค์ด๋ฒ„ ์ฃผ๊ฐ€/์ง€์ˆ˜(fdr.DataReader)๋Š” ์ •์ƒ.
15
+ KRX StockListing ๋งŒ ๋ง‰ํžˆ๋ฏ€๋กœ ์‹œ์žฅ์€ csv ๋กœ ํ•ด๊ฒฐ.
 
16
  """
17
 
 
18
  import os
 
 
 
 
19
  import gradio as gr
20
 
21
+ import confirm_sell_backtest as BT # ํ•ต์‹ฌ ๋กœ์ง/์„ค์ • ๊ทธ๋Œ€๋กœ
 
22
 
23
  HERE = os.path.dirname(os.path.abspath(__file__))
24
+ MAP_PATH = os.path.join(HERE, "market_map.csv")
 
25
 
26
+ ALERT_DAYS = 5 # ์ตœ๊ทผ ์ด ๊ฑฐ๋ž˜์ผ ์•ˆ์˜ ๋งค๋„์‹ ํ˜ธ๋ฅผ [๋งค๋„]๋กœ ๋ณธ๋‹ค
27
 
28
 
29
+ # ---------- market_map.csv ๋กœ๋“œ (code -> market) ----------
30
+ def load_market_map():
31
+ m = {}
32
  try:
33
+ with open(MAP_PATH, encoding="utf-8") as f:
34
+ for i, line in enumerate(f):
35
+ parts = line.strip().split(",")
36
+ if len(parts) < 2:
37
+ continue
38
+ code, mkt = parts[0].strip(), parts[1].strip()
39
+ if code.lower() == "code": # ํ—ค๋” ์Šคํ‚ต
40
+ continue
41
+ m[code.zfill(6)] = mkt.upper()
42
  except Exception as e:
43
+ print(f"[๊ฒฝ๊ณ ] market_map.csv ๋กœ๋“œ ์‹คํŒจ: {e}")
44
+ return m
45
+
46
+ MARKET_MAP = load_market_map()
47
+
48
+
49
+ def market_of(code):
50
+ return MARKET_MAP.get(code.zfill(6), "KOSDAQ") # ๋ชป ์ฐพ์œผ๋ฉด ์ฝ”์Šค๋‹ฅ ๊ฐ€์ •
51
+
52
+
53
+ # ---------- ํ•œ ์ข…๋ชฉ ์ ๊ฒ€ (๋ฐฑํ…Œ์Šคํ„ฐ ๋กœ์ง ๊ทธ๋Œ€๋กœ) ----------
54
+ def check_one(code):
55
+ code = code.zfill(6)
56
+ market = market_of(code)
57
+ r = BT.load_one(code, market)
58
+ if r is None:
59
+ return {"code": code, "market": market, "ok": False, "msg": "๋ฐ์ดํ„ฐ ์—†์Œ"}
60
+ stock = r["stock"]; index = r["index"]
61
+ sells, jumps, steps = BT.find_confirmed_sells(
62
+ stock, index, r["dates"], r["start_pos"],
63
+ ma60=r.get("ma60"), ma240=r.get("ma240"),
64
+ low=r.get("low"), bbl=r.get("bbl"),
65
+ ma240_est=r.get("ma240_est"), ma120=r.get("ma120"))
66
+ last_i = len(stock) - 1
67
+ recent = [s for s in sells if s["i"] >= last_i - ALERT_DAYS]
68
+ return {
69
+ "code": code, "market": market, "ok": True,
70
+ "last_date": r["dates"][last_i],
71
+ "price_now": float(stock[last_i]),
72
+ "recent": recent,
73
+ "last_sell": sells[-1] if sells else None,
74
+ }
75
+
76
+
77
+ def _band(lo, hi):
78
+ return f"โ‰ค{hi*100:.0f}%" if lo <= -1 else f"({lo*100:.0f},{hi*100:.0f}]%"
79
+
80
+
81
+ def run_monitor(text):
82
+ # ์ž…๋ ฅ ํŒŒ์‹ฑ: ์ค„๋ฐ”๊ฟˆ/์‰ผํ‘œ/๊ณต๋ฐฑ ๋‹ค ํ—ˆ์šฉ
83
+ codes = [c.strip() for c in text.replace(",", " ").replace("\n", " ").split()]
84
+ codes = [c for c in codes if c]
85
+ if not codes:
86
+ return "์ข…๋ชฉ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•ด์ค˜ (์˜ˆ: 277810 373220)"
87
+
88
+ head = (f"**์ „๋Ÿ‰๋งค๋„ ์ตœํ›„๋ณด๋ฃจ ๋ชจ๋‹ˆํ„ฐ** ยท ์ตœ๊ทผ {ALERT_DAYS}๊ฑฐ๋ž˜์ผ ๊ธฐ์ค€\n\n"
89
+ f"๊ธฐ์ค€: ๋ŒํŒŒํ›„{BT.RUN_MIN_DAYS}์ผ AND A-4ํ‰ํ‰ "
90
+ f"60{_band(BT.FLAT_MIN_60, BT.FLAT_MAX_60)} {BT.FLAT_COMBINE} "
91
+ f"120{_band(BT.FLAT_MIN_120, BT.FLAT_MAX_120)} {BT.FLAT_COMBINE} "
92
+ f"240{_band(BT.FLAT_MIN_240, BT.FLAT_MAX_240)}\n\n---\n")
93
+
94
+ out = [head]
95
+ alerts = []
96
+ for code in codes:
97
+ res = check_one(code)
98
+ if not res["ok"]:
99
+ out.append(f"- `{code}` ({res['market']}) โ€” {res['msg']}")
100
+ continue
101
+ if res["recent"]:
102
+ s = res["recent"][-1]
103
+ alerts.append(code)
104
+ est = " `[240์ถ”์ •]`" if s.get("est") else ""
105
+ out.append(
106
+ f"### ๐Ÿ”ด [๋งค๋„] {code} ({res['market']}){est}\n"
107
+ f"- ํ˜„์žฌ๊ฐ€ **{res['price_now']:,.0f}**\n"
108
+ f"- ์‹ ํ˜ธ์ผ {s['date']} @ {s['price']:,.0f} (์ด๊ฒฉ {s['div']*100:.0f}%)\n"
109
+ f"- ์‚ฌ์œ  `{s.get('trend','')}`\n")
110
+ else:
111
+ ls = res["last_sell"]
112
+ tail = f" ยท ๋งˆ์ง€๋ง‰ ๋งค๋„์‹ ํ˜ธ {ls['date']}" if ls else " ยท ๊ณผ๊ฑฐ ๋งค๋„์‹ ํ˜ธ ์—†์Œ"
113
+ out.append(f"### ๐ŸŸข [๋ณด์œ ] {code} ({res['market']})\n"
114
+ f"- ํ˜„์žฌ๊ฐ€ {res['price_now']:,.0f}{tail}\n")
115
+
116
+ out.append("\n---\n")
117
+ if alerts:
118
+ out.append(f"**์˜ค๋Š˜ ํ™•์ธ ํ•„์š”: {len(alerts)}์ข…๋ชฉ โ†’ {', '.join(alerts)}**")
119
+ else:
120
+ out.append("**์ตœ๊ทผ ๋งค๋„์‹ ํ˜ธ ์—†์Œ. ์ „๋ถ€ ๋ณด์œ  ์œ ์ง€.**")
121
+ return "\n".join(out)
122
+
123
+
124
+ with gr.Blocks(title="์ „๋Ÿ‰๋งค๋„ ๋ชจ๋‹ˆํ„ฐ") as demo:
125
+ gr.Markdown("# ๐Ÿ“‰ ์ „๋Ÿ‰๋งค๋„ ์ตœํ›„๋ณด๋ฃจ ๋ชจ๋‹ˆํ„ฐ")
126
+ gr.Markdown("๋ณด์œ ์ข…๋ชฉ ์ฝ”๋“œ๋ฅผ ๋„ฃ์œผ๋ฉด ์ตœ๊ทผ ๋งค๋„์‹ ํ˜ธ ์—ฌ๋ถ€๋ฅผ ์•Œ๋ ค์ค˜. (์‹œ์žฅ ์ž๋™ํŒ๋ณ„)")
127
+ inp = gr.Textbox(label="์ข…๋ชฉ์ฝ”๋“œ (์—ฌ๋Ÿฌ ๊ฐœ๋Š” ๊ณต๋ฐฑ/์ค„๋ฐ”๊ฟˆ/์‰ผํ‘œ๋กœ)",
128
+ value="277810 373220", lines=3)
129
+ btn = gr.Button("๋งค๋„์‹ ํ˜ธ ํ™•์ธ", variant="primary")
130
+ outp = gr.Markdown()
131
+ btn.click(run_monitor, inputs=inp, outputs=outp)
 
 
 
 
 
 
 
 
132
 
133
  if __name__ == "__main__":
134
+ demo.launch()