ranranrunforit commited on
Commit
433e48f
·
verified ·
1 Parent(s): be366d0

Upload 18 files

Browse files
Files changed (2) hide show
  1. app.py +13 -77
  2. llm_local.py +6 -9
app.py CHANGED
@@ -11,7 +11,6 @@ github.com/adobe/react-spectrum), approximated in Gradio CSS:
11
  from __future__ import annotations
12
 
13
  import os
14
- import re
15
  import threading
16
 
17
  import gradio as gr
@@ -45,11 +44,6 @@ def ui_run_signals(tickers_text, force):
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,50 +52,6 @@ def ui_run_signals(tickers_text, force):
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,42 +66,28 @@ def ui_explain_detail(ticker):
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
 
11
  from __future__ import annotations
12
 
13
  import os
 
14
  import threading
15
 
16
  import gradio as gr
 
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
  )
53
 
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  def ui_show_detail(ticker):
56
  if not ticker:
57
  return "Select a ticker after running the analysis."
 
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
llm_local.py CHANGED
@@ -36,11 +36,10 @@ MODEL_ZOO = {
36
  # 🎯 Well-Tuned: after you publish your LoRA-merged GGUF, uncomment and edit
37
  # this line (repo = your HF id, filename = the .gguf you uploaded). It will
38
  # then appear in the Model tab as the fast Translator sub-agent.
39
- "Chan-Tuned Qwen3-1.7B · my fine-tune": (
40
- "ranranrunforit/chan-compass-qwen3-1.7b-gguf", "chan-qwen3-1.7b-q8_0.gguf"),
41
  }
42
- # FAST_MODEL = "Qwen3-1.7B · Tiny Titan (≤4B award class)"
43
- FAST_MODEL = "Chan-Tuned Qwen3-1.7B · my fine-tune"
44
  DEFAULT_MODEL = "Qwen3-4B · default — fast + smart, still ≤4B"
45
 
46
  _THINK_RE = re.compile(r"<think>.*?</think>", re.S)
@@ -52,7 +51,6 @@ _NCPU = max(2, (os.cpu_count() or 4))
52
  # the 4B Analyst writes reports. Total ≈ 9 GB on a 32 GB Space.
53
  WORKER_LABEL = {
54
  "translator": "Translator sub-agent (Signals · Explain)",
55
- "rchain": "Ruling-Chain Translator sub-agent (中文→EN)",
56
  "narrator": "Narrator sub-agent (Sector Rotation)",
57
  "reporter": "Reporter sub-agent (News · Research support)",
58
  "analyst": "Analyst sub-agent (Auto Research)",
@@ -66,7 +64,6 @@ def _mk(model):
66
 
67
  WORKERS = {
68
  "translator": _mk(FAST_MODEL),
69
- "rchain": _mk(FAST_MODEL),
70
  "narrator": _mk(FAST_MODEL),
71
  "reporter": _mk(FAST_MODEL),
72
  "analyst": _mk(DEFAULT_MODEL),
@@ -209,7 +206,7 @@ def load_model(name: str, worker: str = "analyst") -> str:
209
  def auto_load_all():
210
  """Startup: tiny agents first (one small GGUF download serves all three),
211
  then the Analyst. Runs in a background thread."""
212
- for key in ("translator", "rchain", "narrator", "reporter", "analyst"):
213
  load_model(WORKERS[key]["model"], worker=key)
214
 
215
 
@@ -222,7 +219,7 @@ def is_loaded(worker: str = None) -> bool:
222
 
223
  def status() -> str:
224
  lines = []
225
- for key in ("translator", "rchain", "narrator", "reporter", "analyst"):
226
  w = WORKERS[key]
227
  label = WORKER_LABEL[key]
228
  if w["llm"] is not None:
@@ -300,7 +297,7 @@ def quick_test() -> str:
300
  """Sanity check both sub-agents."""
301
  import time
302
  outs = []
303
- for key in ("translator", "rchain", "narrator", "reporter", "analyst"):
304
  if WORKERS[key]["llm"] is None:
305
  outs.append(f"{WORKER_LABEL[key]}: not loaded ({WORKERS[key]['stage']})")
306
  continue
 
36
  # 🎯 Well-Tuned: after you publish your LoRA-merged GGUF, uncomment and edit
37
  # this line (repo = your HF id, filename = the .gguf you uploaded). It will
38
  # then appear in the Model tab as the fast Translator sub-agent.
39
+ # "Chan-Tuned Qwen3-1.7B · my fine-tune": (
40
+ # "ranranrunforit/chan-compass-qwen3-1.7b-gguf", "chan-qwen3-1.7b-q8_0.gguf"),
41
  }
42
+ FAST_MODEL = "Qwen3-1.7B · Tiny Titan (≤4B award class)"
 
43
  DEFAULT_MODEL = "Qwen3-4B · default — fast + smart, still ≤4B"
44
 
45
  _THINK_RE = re.compile(r"<think>.*?</think>", re.S)
 
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
 
65
  WORKERS = {
66
  "translator": _mk(FAST_MODEL),
 
67
  "narrator": _mk(FAST_MODEL),
68
  "reporter": _mk(FAST_MODEL),
69
  "analyst": _mk(DEFAULT_MODEL),
 
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
 
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
  """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