glorifiedjx commited on
Commit
c8af3b2
ยท
verified ยท
1 Parent(s): e65556f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +122 -38
app.py CHANGED
@@ -1,42 +1,54 @@
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:
@@ -47,10 +59,51 @@ 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)
@@ -65,32 +118,42 @@ def check_one(code):
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:
@@ -109,9 +172,11 @@ def run_monitor(text):
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:
@@ -121,14 +186,33 @@ def run_monitor(text):
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()
 
1
  # -*- coding: utf-8 -*-
2
  """
3
+ ์ฃผ์‹ ๋””๋ฒ„๊ฑฐ (๋ชจ๋ฐ”์ผ ์›น์•ฑ) โ€” Hugging Face Spaces / Gradio
4
+ =========================================================
5
+ ํƒญ:
6
+ 1) VCP ๋ŒํŒŒ ๊ฐ์ง€ (vcp_debug.py) โ€” ์ตœ๊ทผ 1๋…„ ์ž๋™
7
+ 2) ๋ณผ๋ฆฐ์ € ์นด์šดํŠธ (bollinger_count_debug.py) โ€” ์ตœ๊ทผ 1๋…„ ์ž๋™
8
+ 3) ์ „๋Ÿ‰๋งค๋„ ์ตœํ›„๋ณด๋ฃจ(๋‹ค์ข…๋ชฉ) โ€” confirm_sell_backtest.py ๋กœ์ง ๊ทธ๋Œ€๋กœ
9
+
10
+ ์›์น™:
11
+ - ์ „๋Ÿ‰๋งค๋„: confirm_sell_backtest.py ๋ฅผ import (๋ฐฑํ…Œ์Šคํ„ฐ์™€ 100% ๋™์ผ ๋กœ์ง)
12
+ - VCPยท๋ณผ๋ฆฐ์ €: ๋ฐ์Šคํฌํƒ‘ ์Šคํฌ๋ฆฝํŠธ ์†Œ์Šค๋ฅผ ์ฝ์–ด TICKER๋งŒ ๋ฐ”๊ฟ” exec, ์ถœ๋ ฅ ๊ทธ๋Œ€๋กœ ์บก์ฒ˜
13
+ - ์‹œ์žฅ(KOSPI/KOSDAQ): market_map.csv ๋กœ ์ž๋™ํŒ๋ณ„ (KRX StockListing ์•ˆ ๋ถ€๋ฆ„)
14
+
15
+ HF(ํ•ด์™ธ IP)์—์„œ๋„ ๋„ค์ด๋ฒ„ ์ฃผ๊ฐ€/์ง€์ˆ˜(fdr.DataReader)๋Š” ์ •์ƒ.
16
+ ๊ฐ™์ด ์˜ฌ๋ฆด ํŒŒ์ผ: app.py, confirm_sell_backtest.py, vcp_debug.py,
17
+ bollinger_count_debug.py, market_map.csv, requirements.txt
18
  """
19
 
20
+ import io
21
  import os
22
+ import re
23
+ import contextlib
24
+
25
  import gradio as gr
26
 
27
+ import confirm_sell_backtest as BT # ์ „๋Ÿ‰๋งค๋„ ํ•ต์‹ฌ ๋กœ์ง/์„ค์ • ๊ทธ๋Œ€๋กœ
28
 
29
  HERE = os.path.dirname(os.path.abspath(__file__))
30
+ MAP_PATH = os.path.join(HERE, "market_map.csv")
31
+ BOLL_PATH = os.path.join(HERE, "bollinger_count_debug.py")
32
+ VCP_PATH = os.path.join(HERE, "vcp_debug.py")
33
+
34
+ ALERT_DAYS = 5 # ์ „๋Ÿ‰๋งค๋„: ์ตœ๊ทผ ์ด ๊ฑฐ๋ž˜์ผ ์•ˆ์˜ ๋งค๋„์‹ ํ˜ธ๋ฅผ [๋งค๋„]๋กœ ๋ณธ๋‹ค
35
 
36
+ _TICKER_RE = re.compile(r"TICKER\s*=\s*['\"][0-9A-Za-z]+['\"]")
37
 
38
 
39
+ # ====================================================================
40
+ # ๊ณตํ†ต: market_map.csv (code -> market)
41
+ # ====================================================================
42
  def load_market_map():
43
  m = {}
44
  try:
45
  with open(MAP_PATH, encoding="utf-8") as f:
46
+ for line in f:
47
  parts = line.strip().split(",")
48
  if len(parts) < 2:
49
  continue
50
  code, mkt = parts[0].strip(), parts[1].strip()
51
+ if code.lower() == "code":
52
  continue
53
  m[code.zfill(6)] = mkt.upper()
54
  except Exception as e:
 
59
 
60
 
61
  def market_of(code):
62
+ return MARKET_MAP.get(code.zfill(6), "KOSDAQ")
63
+
64
+
65
+ # ====================================================================
66
+ # ํƒญ 1ยท2: VCP / ๋ณผ๋ฆฐ์ € โ€” ์Šคํฌ๋ฆฝํŠธ ์†Œ์Šค๋ฅผ ์ฝ์–ด TICKER ๋ฐ”๊ฟ” exec
67
+ # ====================================================================
68
+ def run_script(path, ticker):
69
+ ticker = ticker.strip().zfill(6)
70
+ if not ticker.isdigit():
71
+ return "์ข…๋ชฉ์ฝ”๋“œ(์ˆซ์ž 6์ž๋ฆฌ)๋ฅผ ๋„ฃ์–ด์ค˜. ์˜ˆ: 222080"
72
+ try:
73
+ src = open(path, encoding="utf-8").read()
74
+ except Exception as e:
75
+ return f"์Šคํฌ๋ฆฝํŠธ ๋กœ๋“œ ์‹คํŒจ: {e}"
76
+ src, n = _TICKER_RE.subn(f"TICKER = '{ticker}'", src, count=1)
77
+ if n == 0:
78
+ return "์Šคํฌ๋ฆฝํŠธ์—์„œ TICKER ๋ผ์ธ์„ ๋ชป ์ฐพ์Œ."
79
+ buf = io.StringIO()
80
+ g = {"__name__": "__main__"}
81
+ try:
82
+ with contextlib.redirect_stdout(buf):
83
+ exec(compile(src, path, "exec"), g)
84
+ except SystemExit:
85
+ pass
86
+ except Exception as e:
87
+ return f"{buf.getvalue()}\n\n[์˜ค๋ฅ˜] {e}"
88
+ out = buf.getvalue().strip()
89
+ return out if out else "์ถœ๋ ฅ ์—†์Œ (๋ฐ์ดํ„ฐ๊ฐ€ ์—†๊ฑฐ๋‚˜ ์กฐ๊ฑด ๋ฏธ์ถฉ์กฑ)"
90
+
91
+
92
+ def run_vcp(ticker):
93
+ return run_script(VCP_PATH, ticker)
94
+
95
+
96
+ def run_boll(ticker):
97
+ return run_script(BOLL_PATH, ticker)
98
+
99
+
100
+ # ====================================================================
101
+ # ํƒญ 3: ์ „๋Ÿ‰๋งค๋„ ์ตœํ›„๋ณด๋ฃจ (๋‹ค์ข…๋ชฉ) โ€” ํ˜„์žฌ ๋‹จ๊ณ„ status ํฌํ•จ
102
+ # ====================================================================
103
+ def _band(lo, hi):
104
+ return f"โ‰ค{hi*100:.0f}%" if lo <= -1 else f"({lo*100:.0f},{hi*100:.0f}]%"
105
 
106
 
 
107
  def check_one(code):
108
  code = code.zfill(6)
109
  market = market_of(code)
 
118
  ma240_est=r.get("ma240_est"), ma120=r.get("ma120"))
119
  last_i = len(stock) - 1
120
  recent = [s for s in sells if s["i"] >= last_i - ALERT_DAYS]
121
+
122
+ # ํ˜„์žฌ ๋‹จ๊ณ„ status: steps ๋งˆ์ง€๋ง‰์˜ '[ํ˜„์žฌ] ...' (์—†์œผ๋ฉด ๊ด€๋ง)
123
+ cur = None
124
+ for stp in steps:
125
+ if stp["name"].startswith("[ํ˜„์žฌ]"):
126
+ cur = stp
127
  return {
128
  "code": code, "market": market, "ok": True,
 
129
  "price_now": float(stock[last_i]),
130
  "recent": recent,
131
  "last_sell": sells[-1] if sells else None,
132
+ "cur": cur,
133
  }
134
 
135
 
136
+ def _status_line(res):
137
+ """ํ˜„์žฌ ๋‹จ๊ณ„๋ฅผ ์งง๊ฒŒ: ๊ด€๋ง / flagON ๋Œ€๊ธฐ / 1์ฐจ / 2์ฐจflagON ๋Œ€๊ธฐ + ์ด๊ฒฉ."""
138
+ cur = res["cur"]
139
+ if cur is None:
140
+ return "๊ด€๋ง (์‹ ํ˜ธ ๋Œ€๊ธฐ)"
141
+ name = cur["name"].replace("[ํ˜„์žฌ] ", "")
142
+ note = cur.get("note", "")
143
+ return f"{name} ยท ์ด๊ฒฉ {cur['div']*100:.1f}%" + (f" ยท {note}" if note else "")
144
 
145
 
146
  def run_monitor(text):
 
147
  codes = [c.strip() for c in text.replace(",", " ").replace("\n", " ").split()]
148
  codes = [c for c in codes if c]
149
  if not codes:
150
  return "์ข…๋ชฉ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•ด์ค˜ (์˜ˆ: 277810 373220)"
151
 
152
+ head = (f"**์ „๋Ÿ‰๋งค๋„ ์ตœํ›„๋ณด๋ฃจ** ยท ์ตœ๊ทผ {ALERT_DAYS}๊ฑฐ๋ž˜์ผ ๊ธฐ์ค€\n\n"
153
  f"๊ธฐ์ค€: ๋ŒํŒŒํ›„{BT.RUN_MIN_DAYS}์ผ AND A-4ํ‰ํ‰ "
154
  f"60{_band(BT.FLAT_MIN_60, BT.FLAT_MAX_60)} {BT.FLAT_COMBINE} "
155
  f"120{_band(BT.FLAT_MIN_120, BT.FLAT_MAX_120)} {BT.FLAT_COMBINE} "
156
  f"240{_band(BT.FLAT_MIN_240, BT.FLAT_MAX_240)}\n\n---\n")
 
157
  out = [head]
158
  alerts = []
159
  for code in codes:
 
172
  f"- ์‚ฌ์œ  `{s.get('trend','')}`\n")
173
  else:
174
  ls = res["last_sell"]
175
+ tail = f" ยท ๋งˆ์ง€๋ง‰ ๋งค๋„์‹ ํ˜ธ {ls['date']}" if ls else ""
176
+ out.append(
177
+ f"### ๐ŸŸข [๋ณด์œ ] {code} ({res['market']})\n"
178
+ f"- ํ˜„์žฌ๊ฐ€ {res['price_now']:,.0f}{tail}\n"
179
+ f"- ์ƒํƒœ: {_status_line(res)}\n")
180
 
181
  out.append("\n---\n")
182
  if alerts:
 
186
  return "\n".join(out)
187
 
188
 
189
+ # ====================================================================
190
+ # UI
191
+ # ====================================================================
192
+ with gr.Blocks(title="์ฃผ์‹ ๋””๋ฒ„๊ฑฐ") as demo:
193
+ gr.Markdown("# ๐Ÿ“Š ์ฃผ์‹ ๋””๋ฒ„๊ฑฐ")
194
+
195
+ with gr.Tab("VCP ๋ŒํŒŒ"):
196
+ gr.Markdown("๋ฏธ๋„ˆ๋น„๋‹ˆ VCP ๋ŒํŒŒ ๊ฐ์ง€ (์ตœ๊ทผ 1๋…„ ์ž๋™)")
197
+ v_in = gr.Textbox(label="์ข…๋ชฉ์ฝ”๋“œ", value="222080")
198
+ v_btn = gr.Button("๋ถ„์„", variant="primary")
199
+ v_out = gr.Code(label="๊ฒฐ๊ณผ")
200
+ v_btn.click(run_vcp, inputs=v_in, outputs=v_out)
201
+
202
+ with gr.Tab("๋ณผ๋ฆฐ์ € ์นด์šดํŠธ"):
203
+ gr.Markdown("๋ณผ๋ฆฐ์ €๋ฐด๋“œ ์ƒ๋‹จ ๋ŒํŒŒ ์นด์šดํŒ… (์ตœ๊ทผ 1๋…„ ์ž๋™)")
204
+ b_in = gr.Textbox(label="์ข…๋ชฉ์ฝ”๋“œ", value="420770")
205
+ b_btn = gr.Button("๋ถ„์„", variant="primary")
206
+ b_out = gr.Code(label="๊ฒฐ๊ณผ")
207
+ b_btn.click(run_boll, inputs=b_in, outputs=b_out)
208
+
209
+ with gr.Tab("์ „๋Ÿ‰๋งค๋„ ์ตœํ›„๋ณด๋ฃจ"):
210
+ gr.Markdown("๋ณด์œ ์ข…๋ชฉ ์ฝ”๋“œ๋ฅผ ๋„ฃ์œผ๋ฉด ๋งค๋„์‹ ํ˜ธ/ํ˜„์žฌ๋‹จ๊ณ„๋ฅผ ์•Œ๋ ค์ค˜. (์‹œ์žฅ ์ž๋™ํŒ๋ณ„, ์—ฌ๋Ÿฌ ์ข…๋ชฉ ๊ฐ€๋Šฅ)")
211
+ m_in = gr.Textbox(label="์ข…๋ชฉ์ฝ”๋“œ (์—ฌ๋Ÿฌ ๊ฐœ๋Š” ๊ณต๋ฐฑ/์ค„๋ฐ”๊ฟˆ/์‰ผํ‘œ๋กœ)",
212
+ value="277810 373220", lines=3)
213
+ m_btn = gr.Button("๋งค๋„์‹ ํ˜ธ ํ™•์ธ", variant="primary")
214
+ m_out = gr.Markdown()
215
+ m_btn.click(run_monitor, inputs=m_in, outputs=m_out)
216
 
217
  if __name__ == "__main__":
218
  demo.launch()