ranranrunforit commited on
Commit
fa5538a
·
verified ·
1 Parent(s): d29721a

Upload 18 files

Browse files
Files changed (2) hide show
  1. app.py +77 -13
  2. llm_local.py +5 -3
app.py CHANGED
@@ -11,6 +11,7 @@ github.com/adobe/react-spectrum), approximated in Gradio CSS:
11
  from __future__ import annotations
12
 
13
  import os
 
14
  import threading
15
 
16
  import gradio as gr
@@ -44,6 +45,11 @@ def ui_run_signals(tickers_text, force):
44
  automation.STATE["signals_df"] = df
45
  automation.STATE["signals_details"] = details
46
  automation.STATE["signals_summary"] = summary
 
 
 
 
 
47
  choices = sorted(details.keys())
48
  return (
49
  df if df is not None else pd.DataFrame(),
@@ -52,6 +58,50 @@ def ui_run_signals(tickers_text, force):
52
  )
53
 
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  def ui_show_detail(ticker):
56
  if not ticker:
57
  return "Select a ticker after running the analysis."
@@ -66,28 +116,42 @@ def ui_explain_detail(ticker):
66
  if not raw:
67
  yield "Run the analysis and select a ticker first."
68
  return
69
- # The full Chan ruling chain (Chinese) is kept BACKSTAGE in STATE and fed to
70
- # the model alongside the English raw read, so the summary reflects the real
71
- # multi-timeframe reasoning but the chain is never shown and output is
72
- # English only. (This restores the merged raw-read + ruling-chain logic.)
73
- chain = automation.STATE.get("signals_details", {}).get(ticker or "", "")
74
- chain_core = chain.split("日线买卖点逐项诊断")[0].strip()[:2000] if chain else ""
 
 
75
  prompt = ("You are an equity analyst. Write a SHORT plain-English summary "
76
  "(≤100 words) for a long-term holder of a US stock: the situation "
77
  "today, whether to act or wait, and the key price levels.\n"
78
- "Use the FACT LINE for the numbers, and the RULING CHAIN (a "
79
- "Chinese multi-timeframe Chan-theory decision log) for the reasoning "
80
- " translate and synthesize it; output ENGLISH ONLY, no Chinese "
81
- "characters, do not quote the log, no disclaimers.\n\n"
82
  f"FACT LINE:\n{raw}")
83
- if chain_core:
84
- prompt += f"\n\nRULING CHAIN (translate & synthesize, don't quote):\n{chain_core}"
85
  yield "🤖 _Translator sub-agent (Qwen3-1.7B · llama.cpp) is summarizing…_"
86
  final = ""
87
- for acc in llm_local.chat_stream(prompt, max_tokens=260, temperature=0.2,
88
  worker="translator"):
89
  final = acc
90
  yield "🤖 **AI narrative (Translator sub-agent · Qwen3-1.7B):**\n\n" + acc
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  # capture (raw read → narrative) as a fine-tuning pair (🎯 Well-Tuned)
92
  try:
93
  import finetune_data
 
11
  from __future__ import annotations
12
 
13
  import os
14
+ import re
15
  import threading
16
 
17
  import gradio as gr
 
45
  automation.STATE["signals_df"] = df
46
  automation.STATE["signals_details"] = details
47
  automation.STATE["signals_summary"] = summary
48
+ # Pre-translate every ruling chain to English in the BACKGROUND on the
49
+ # dedicated rchain sub-agent, so when the user clicks AI summary the English
50
+ # chain is already cached → faster, and the merge step gets English-only
51
+ # input (no Chinese can leak into the output).
52
+ _kick_chain_translation(details)
53
  choices = sorted(details.keys())
54
  return (
55
  df if df is not None else pd.DataFrame(),
 
58
  )
59
 
60
 
61
+ def _chain_core(ticker: str) -> str:
62
+ """The ruling chain minus the huge per-signal diagnostics block."""
63
+ chain = automation.STATE.get("signals_details", {}).get(ticker, "")
64
+ return chain.split("日线买卖点逐项诊断")[0].strip()[:2000] if chain else ""
65
+
66
+
67
+ def _translate_chain(ticker: str) -> str:
68
+ """Translate one ruling chain to English on the dedicated rchain sub-agent;
69
+ cache the result. Returns the English text (or '' if no model/chain)."""
70
+ cache = automation.STATE.setdefault("chain_en", {})
71
+ if ticker in cache:
72
+ return cache[ticker]
73
+ core = _chain_core(ticker)
74
+ if not core:
75
+ return ""
76
+ if not (llm_local.is_loaded("rchain") or llm_local.is_loaded("translator")):
77
+ return ""
78
+ wk = "rchain" if llm_local.is_loaded("rchain") else "translator"
79
+ prompt = ("Translate this Chinese multi-timeframe Chan-theory decision log "
80
+ "into concise English. Keep the structure (one line per level: "
81
+ "yearly / monthly / weekly / daily / 60m / 30m …). Output ENGLISH "
82
+ "ONLY, no Chinese characters, no commentary.\n\n" + core)
83
+ en = llm_local.chat(prompt, max_tokens=380, temperature=0.1, worker=wk)
84
+ if en and not en.startswith(("(", "⏳")):
85
+ cache[ticker] = en
86
+ return en
87
+ return ""
88
+
89
+
90
+ def _kick_chain_translation(details: dict):
91
+ """Background: translate all ruling chains to English right after analysis,
92
+ so AI summary is instant and English-only later."""
93
+ import threading
94
+ automation.STATE["chain_en"] = {} # reset for the new run
95
+
96
+ def _run():
97
+ for tk in list(details.keys()):
98
+ try:
99
+ _translate_chain(tk)
100
+ except Exception:
101
+ pass
102
+ threading.Thread(target=_run, daemon=True).start()
103
+
104
+
105
  def ui_show_detail(ticker):
106
  if not ticker:
107
  return "Select a ticker after running the analysis."
 
116
  if not raw:
117
  yield "Run the analysis and select a ticker first."
118
  return
119
+ # Use the pre-translated English ruling chain (translated in the background
120
+ # right after Run analysis). If it isn't ready yet, translate it now on the
121
+ # rchain sub-agent. Both the fact line AND the chain are now English, so the
122
+ # merge step cannot leak Chinese into the output.
123
+ chain_en = automation.STATE.get("chain_en", {}).get(ticker or "")
124
+ if not chain_en:
125
+ yield "🤖 _Translating the multi-timeframe ruling chain to English…_"
126
+ chain_en = _translate_chain(ticker or "")
127
  prompt = ("You are an equity analyst. Write a SHORT plain-English summary "
128
  "(≤100 words) for a long-term holder of a US stock: the situation "
129
  "today, whether to act or wait, and the key price levels.\n"
130
+ "Base it on the FACT LINE (numbers) and the REASONING (an English "
131
+ "translation of the multi-timeframe Chan-theory verdict). Output "
132
+ "ENGLISH ONLY, do not quote the inputs, no disclaimers.\n\n"
 
133
  f"FACT LINE:\n{raw}")
134
+ if chain_en:
135
+ prompt += f"\n\nREASONING:\n{chain_en}"
136
  yield "🤖 _Translator sub-agent (Qwen3-1.7B · llama.cpp) is summarizing…_"
137
  final = ""
138
+ for acc in llm_local.chat_stream(prompt, max_tokens=240, temperature=0.2,
139
  worker="translator"):
140
  final = acc
141
  yield "🤖 **AI narrative (Translator sub-agent · Qwen3-1.7B):**\n\n" + acc
142
+ # English-only guarantee: if the model still slipped in Chinese, re-run once
143
+ # asking for a strict English rewrite.
144
+ if re.search(r"[\u4e00-\u9fff]", final):
145
+ fix = ("Rewrite the following as an English-only equity summary "
146
+ "(≤100 words). Remove ALL Chinese characters, keep the meaning "
147
+ "and numbers, no disclaimers:\n\n" + final)
148
+ fixed = ""
149
+ for acc in llm_local.chat_stream(fix, max_tokens=240, temperature=0.1,
150
+ worker="translator"):
151
+ fixed = acc
152
+ yield "🤖 **AI narrative (Translator sub-agent · Qwen3-1.7B):**\n\n" + acc
153
+ if fixed:
154
+ final = fixed
155
  # capture (raw read → narrative) as a fine-tuning pair (🎯 Well-Tuned)
156
  try:
157
  import finetune_data
llm_local.py CHANGED
@@ -51,6 +51,7 @@ _NCPU = max(2, (os.cpu_count() or 4))
51
  # the 4B Analyst writes reports. Total ≈ 9 GB on a 32 GB Space.
52
  WORKER_LABEL = {
53
  "translator": "Translator sub-agent (Signals · Explain)",
 
54
  "narrator": "Narrator sub-agent (Sector Rotation)",
55
  "reporter": "Reporter sub-agent (News · Research support)",
56
  "analyst": "Analyst sub-agent (Auto Research)",
@@ -64,6 +65,7 @@ def _mk(model):
64
 
65
  WORKERS = {
66
  "translator": _mk(FAST_MODEL),
 
67
  "narrator": _mk(FAST_MODEL),
68
  "reporter": _mk(FAST_MODEL),
69
  "analyst": _mk(DEFAULT_MODEL),
@@ -206,7 +208,7 @@ def load_model(name: str, worker: str = "analyst") -> str:
206
  def auto_load_all():
207
  """Startup: tiny agents first (one small GGUF download serves all three),
208
  then the Analyst. Runs in a background thread."""
209
- for key in ("translator", "narrator", "reporter", "analyst"):
210
  load_model(WORKERS[key]["model"], worker=key)
211
 
212
 
@@ -219,7 +221,7 @@ def is_loaded(worker: str = None) -> bool:
219
 
220
  def status() -> str:
221
  lines = []
222
- for key in ("translator", "narrator", "reporter", "analyst"):
223
  w = WORKERS[key]
224
  label = WORKER_LABEL[key]
225
  if w["llm"] is not None:
@@ -297,7 +299,7 @@ def quick_test() -> str:
297
  """Sanity check both sub-agents."""
298
  import time
299
  outs = []
300
- for key in ("translator", "narrator", "reporter", "analyst"):
301
  if WORKERS[key]["llm"] is None:
302
  outs.append(f"{WORKER_LABEL[key]}: not loaded ({WORKERS[key]['stage']})")
303
  continue
 
51
  # the 4B Analyst writes reports. Total ≈ 9 GB on a 32 GB Space.
52
  WORKER_LABEL = {
53
  "translator": "Translator sub-agent (Signals · Explain)",
54
+ "rchain": "Ruling-Chain Translator sub-agent (中文→EN)",
55
  "narrator": "Narrator sub-agent (Sector Rotation)",
56
  "reporter": "Reporter sub-agent (News · Research support)",
57
  "analyst": "Analyst sub-agent (Auto Research)",
 
65
 
66
  WORKERS = {
67
  "translator": _mk(FAST_MODEL),
68
+ "rchain": _mk(FAST_MODEL),
69
  "narrator": _mk(FAST_MODEL),
70
  "reporter": _mk(FAST_MODEL),
71
  "analyst": _mk(DEFAULT_MODEL),
 
208
  def auto_load_all():
209
  """Startup: tiny agents first (one small GGUF download serves all three),
210
  then the Analyst. Runs in a background thread."""
211
+ for key in ("translator", "rchain", "narrator", "reporter", "analyst"):
212
  load_model(WORKERS[key]["model"], worker=key)
213
 
214
 
 
221
 
222
  def status() -> str:
223
  lines = []
224
+ for key in ("translator", "rchain", "narrator", "reporter", "analyst"):
225
  w = WORKERS[key]
226
  label = WORKER_LABEL[key]
227
  if w["llm"] is not None:
 
299
  """Sanity check both sub-agents."""
300
  import time
301
  outs = []
302
+ for key in ("translator", "rchain", "narrator", "reporter", "analyst"):
303
  if WORKERS[key]["llm"] is None:
304
  outs.append(f"{WORKER_LABEL[key]}: not loaded ({WORKERS[key]['stage']})")
305
  continue