ranranrunforit commited on
Commit
05546bf
·
verified ·
1 Parent(s): e05e1db

Upload 33 files

Browse files
server.py CHANGED
@@ -99,7 +99,7 @@ async def signals_summary(ticker: str):
99
  def gen():
100
  final = ""
101
  for acc in llm_local.chat_stream(prompt, max_tokens=240, temperature=0.2,
102
- worker="translator"):
103
  final = acc
104
  yield acc
105
  try:
@@ -213,6 +213,30 @@ async def automation_publish(req: Request):
213
 
214
 
215
  # ───────────────────────── Model ─────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  @app.get("/api/model/status")
217
  async def model_status():
218
  return JSONResponse({
 
99
  def gen():
100
  final = ""
101
  for acc in llm_local.chat_stream(prompt, max_tokens=240, temperature=0.2,
102
+ worker="interpreter"):
103
  final = acc
104
  yield acc
105
  try:
 
213
 
214
 
215
  # ───────────────────────── Model ─────────────────────────
216
+ @app.get("/api/market/status")
217
+ async def market_status():
218
+ import datetime as dt
219
+ try:
220
+ from zoneinfo import ZoneInfo
221
+ now = dt.datetime.now(ZoneInfo("America/New_York"))
222
+ except Exception:
223
+ now = dt.datetime.utcnow()
224
+ wd = now.weekday() # 0=Mon … 6=Sun
225
+ minutes = now.hour * 60 + now.minute
226
+ is_weekday = wd < 5
227
+ # regular session 9:30–16:00 ET
228
+ is_open = is_weekday and (9*60+30) <= minutes < (16*60)
229
+ if is_open:
230
+ label, variant = "Market open", "positive"
231
+ elif is_weekday and minutes < (9*60+30):
232
+ label, variant = "Pre-market", "notice"
233
+ elif is_weekday and minutes >= (16*60):
234
+ label, variant = "After hours", "notice"
235
+ else:
236
+ label, variant = "Market closed · weekend", "neutral"
237
+ return JSONResponse({"open": is_open, "label": label, "variant": variant})
238
+
239
+
240
  @app.get("/api/model/status")
241
  async def model_status():
242
  return JSONResponse({
ui_kits/chan-compass/api.js CHANGED
@@ -54,6 +54,7 @@ window.CCApi = (function () {
54
  autoRun: () => jpost("/api/automation/run", {}),
55
  autoState: () => jget("/api/automation/state"),
56
  publishTraces: (repo) => jpost("/api/automation/publish-traces", { repo }),
 
57
  modelStatus: () => jget("/api/model/status"),
58
  modelTest: () => jpost("/api/model/test", {}),
59
  modelTestStatus: () => jget("/api/model/test-status"),
 
54
  autoRun: () => jpost("/api/automation/run", {}),
55
  autoState: () => jget("/api/automation/state"),
56
  publishTraces: (repo) => jpost("/api/automation/publish-traces", { repo }),
57
+ marketStatus: () => jget("/api/market/status"),
58
  modelStatus: () => jget("/api/model/status"),
59
  modelTest: () => jpost("/api/model/test", {}),
60
  modelTestStatus: () => jget("/api/model/test-status"),
ui_kits/chan-compass/app.jsx CHANGED
@@ -65,6 +65,11 @@ class Boundary extends React.Component {
65
 
66
  function App() {
67
  const [tab, setTab] = useS('signals');
 
 
 
 
 
68
  const items = [
69
  { id:'signals', label:'Signals', icon:<Glyph name="trending-up" size={16}/> },
70
  { id:'rotation', label:'Sector Rotation', icon:<Glyph name="refresh-cw" size={16}/> },
@@ -85,13 +90,13 @@ function App() {
85
  <div className="cc-brand">
86
  <div className="cc-mark"><Glyph name="compass" size={22} color="#fff"/></div>
87
  <div className="cc-brand-text">
88
- <div className="cc-wordmark">Chan Compass <span>· US Markets</span></div>
89
  <div className="cc-tagline">Multi-timeframe 缠论 signal engine · fully local</div>
90
  </div>
91
  </div>
92
  <div className="cc-header-right">
93
  <CCBadge variant="accent"><Glyph name="cpu" size={12} style={{marginRight:4}}/> Local · llama.cpp</CCBadge>
94
- <CCStatus variant="positive" live>Market open</CCStatus>
95
  </div>
96
  </header>
97
 
@@ -104,7 +109,7 @@ function App() {
104
  </main>
105
 
106
  <footer className="cc-footer">
107
- <span>Chan Compass · educational tool, not investment advice</span>
108
  <span className="cc-foot-sep">·</span>
109
  <span>data: Yahoo Finance</span>
110
  <span className="cc-foot-sep">·</span>
 
65
 
66
  function App() {
67
  const [tab, setTab] = useS('signals');
68
+ const [mkt, setMkt] = useS({label:"…", variant:"neutral"});
69
+ useE(()=>{
70
+ const tick = ()=> window.CCApi && window.CCApi.marketStatus().then(setMkt).catch(()=>{});
71
+ tick(); const id=setInterval(tick, 60000); return ()=>clearInterval(id);
72
+ },[]);
73
  const items = [
74
  { id:'signals', label:'Signals', icon:<Glyph name="trending-up" size={16}/> },
75
  { id:'rotation', label:'Sector Rotation', icon:<Glyph name="refresh-cw" size={16}/> },
 
90
  <div className="cc-brand">
91
  <div className="cc-mark"><Glyph name="compass" size={22} color="#fff"/></div>
92
  <div className="cc-brand-text">
93
+ <div className="cc-wordmark">Chan Compass <span>· US Stock Markets</span></div>
94
  <div className="cc-tagline">Multi-timeframe 缠论 signal engine · fully local</div>
95
  </div>
96
  </div>
97
  <div className="cc-header-right">
98
  <CCBadge variant="accent"><Glyph name="cpu" size={12} style={{marginRight:4}}/> Local · llama.cpp</CCBadge>
99
+ <CCStatus variant={mkt.variant} live={mkt.variant==="positive"}>{mkt.label}</CCStatus>
100
  </div>
101
  </header>
102
 
 
109
  </main>
110
 
111
  <footer className="cc-footer">
112
+ <span>Chan Compass · built for my family, not investment advice</span>
113
  <span className="cc-foot-sep">·</span>
114
  <span>data: Yahoo Finance</span>
115
  <span className="cc-foot-sep">·</span>
ui_kits/chan-compass/views.jsx CHANGED
@@ -157,16 +157,17 @@ function SignalsView() {
157
  )}
158
  {summary && <div className="dim" style={{marginTop:8, fontSize:13}}>{summary}</div>}
159
  </Card>
160
- <Card title={"AI Summary" + (sel?" - "+sel:"")} subtitle="Summary sub-agent - Chan-Tuned Qwen3-1.7B - references the multi-timeframe ruling chain">
161
- <div style={{display:"flex", gap:"var(--space-150)", marginBottom:"var(--space-200)"}}>
162
  <Button variant="accent" size="sm" onClick={explain} disabled={!sel || aiBusy}>
163
- <Icon name="sparkles" size={14}/> {aiBusy ? "Summarizing..." : "AI summary (local LLM)"}
164
  </Button>
 
165
  </div>
166
  <div className="cc-llm-out">
167
- {aiText ? <Markdown text={aiText}/> : <span className="dim">AI output appears here. Pick a ticker, then AI summary.</span>}
168
  </div>
169
- <EmailRow getContent={()=>aiText} tag="Signals summary"/>
170
  </Card>
171
  </div>
172
  );
@@ -206,10 +207,9 @@ function RotationView() {
206
 
207
  return (
208
  <div style={{display:"flex", flexDirection:"column", gap:"var(--space-300)"}}>
209
- <InlineAlert variant="informative" title="Where capital is flowing">
210
- 11 SPDR sector ETFs (full S&amp;P 500). Flow proxy = change % x dollar volume; RS = return minus SPY.
211
- True per-sector fund-flow feeds are paid data - this is the standard free proxy.
212
- </InlineAlert>
213
  <div style={{display:"flex", gap:"var(--space-150)"}}>
214
  <Button variant="accent" onClick={refresh} disabled={loading}>
215
  <Icon name="refresh-cw" size={15}/> {loading?"Refreshing...":"Refresh rotation (instant)"}
@@ -235,8 +235,15 @@ function NewsView() {
235
  const [hold, setHold] = useState("");
236
  const [out, setOut] = useState("");
237
  const [busy, setBusy] = useState(false);
 
238
  useEffect(()=>{ API.holdings().then(r=>setHold((r.holdings||[]).join(", "))).catch(()=>{}); },[]);
239
- const save = async () => { await API.saveHoldings(hold); };
 
 
 
 
 
 
240
  const check = async () => { setBusy(true); setOut(""); await API.checkNews(m=>setOut(m.text||"")); setBusy(false); };
241
  return (
242
  <div style={{display:"flex", flexDirection:"column", gap:"var(--space-300)"}}>
@@ -248,6 +255,7 @@ function NewsView() {
248
  <Icon name="search" size={15}/> {busy?"Checking...":"Check today's news"}
249
  </Button>
250
  </div>
 
251
  <div className="cc-llm-out" style={{minHeight:200}}>
252
  {out ? <Markdown text={out}/> : <span className="dim">News briefs appear here.</span>}
253
  </div>
@@ -377,9 +385,9 @@ function ModelView() {
377
  };
378
  return (
379
  <div style={{display:"flex", flexDirection:"column", gap:"var(--space-300)"}}>
380
- <InlineAlert variant="informative" title="Everything runs locally through llama.cpp">
381
- Qwen3 GGUF weights, all below the 32B cap. The Summary sub-agent uses a published fine-tune (Chan-Tuned Qwen3-1.7B).
382
- </InlineAlert>
383
  <Card title="Sub-agent pool" subtitle="Each sub-agent has its own lock - they run in parallel"
384
  action={<Button variant="secondary" size="sm" onClick={runTest}><Icon name="zap" size={14}/> Test sub-agents now</Button>}>
385
  <div className="cc-radios">
 
157
  )}
158
  {summary && <div className="dim" style={{marginTop:8, fontSize:13}}>{summary}</div>}
159
  </Card>
160
+ <Card title={"AI Interpret" + (sel?" - "+sel:"")} subtitle="Interpreter sub-agent - Chan-Tuned Qwen3-1.7B - references the multi-timeframe ruling chain">
161
+ <div style={{display:"flex", gap:"var(--space-150)", marginBottom:"var(--space-200)", alignItems:"center"}}>
162
  <Button variant="accent" size="sm" onClick={explain} disabled={!sel || aiBusy}>
163
+ <Icon name="sparkles" size={14}/> {aiBusy ? "Interpreting..." : "AI interpret (local LLM)"}
164
  </Button>
165
+ {sel && <span className="dim" style={{fontSize:13}}>Selected: <b className="mono">{sel}</b> — click another row to change.</span>}
166
  </div>
167
  <div className="cc-llm-out">
168
+ {aiText ? <Markdown text={aiText}/> : <span className="dim">AI output appears here. Click a ticker row above, then AI interpret.</span>}
169
  </div>
170
+ <EmailRow getContent={()=>aiText} tag="Signals interpret"/>
171
  </Card>
172
  </div>
173
  );
 
207
 
208
  return (
209
  <div style={{display:"flex", flexDirection:"column", gap:"var(--space-300)"}}>
210
+ <Card title="Where capital is flowing" subtitle="11 SPDR sector ETFs (full S&P 500) · flow proxy = change % × dollar volume · RS = return minus SPY">
211
+ <div className="dim" style={{fontSize:13}}>True per-sector fund-flow feeds are paid data this is the standard free proxy.</div>
212
+ </Card>
 
213
  <div style={{display:"flex", gap:"var(--space-150)"}}>
214
  <Button variant="accent" onClick={refresh} disabled={loading}>
215
  <Icon name="refresh-cw" size={15}/> {loading?"Refreshing...":"Refresh rotation (instant)"}
 
235
  const [hold, setHold] = useState("");
236
  const [out, setOut] = useState("");
237
  const [busy, setBusy] = useState(false);
238
+ const [saved, setSaved] = useState("");
239
  useEffect(()=>{ API.holdings().then(r=>setHold((r.holdings||[]).join(", "))).catch(()=>{}); },[]);
240
+ const save = async () => {
241
+ setSaved("Saving…");
242
+ try { const r = await API.saveHoldings(hold);
243
+ setSaved("✅ Saved " + ((r.saved&&r.saved.length)||0) + " holdings.");
244
+ setTimeout(()=>setSaved(""), 3000);
245
+ } catch(e){ setSaved("❌ " + e.message); }
246
+ };
247
  const check = async () => { setBusy(true); setOut(""); await API.checkNews(m=>setOut(m.text||"")); setBusy(false); };
248
  return (
249
  <div style={{display:"flex", flexDirection:"column", gap:"var(--space-300)"}}>
 
255
  <Icon name="search" size={15}/> {busy?"Checking...":"Check today's news"}
256
  </Button>
257
  </div>
258
+ {saved && <div className="dim" style={{marginBottom:8, fontSize:13}}>{saved}</div>}
259
  <div className="cc-llm-out" style={{minHeight:200}}>
260
  {out ? <Markdown text={out}/> : <span className="dim">News briefs appear here.</span>}
261
  </div>
 
385
  };
386
  return (
387
  <div style={{display:"flex", flexDirection:"column", gap:"var(--space-300)"}}>
388
+ <Card title="Everything runs locally through llama.cpp" subtitle="Qwen3 GGUF weights, all below the 32B cap">
389
+ <div className="dim" style={{fontSize:13}}>The Interpreter sub-agent uses a published fine-tune (Chan-Tuned Qwen3-1.7B).</div>
390
+ </Card>
391
  <Card title="Sub-agent pool" subtitle="Each sub-agent has its own lock - they run in parallel"
392
  action={<Button variant="secondary" size="sm" onClick={runTest}><Icon name="zap" size={14}/> Test sub-agents now</Button>}>
393
  <div className="cc-radios">