Spaces:
Runtime error
Runtime error
Upload 33 files
Browse files- server.py +25 -1
- ui_kits/chan-compass/api.js +1 -0
- ui_kits/chan-compass/app.jsx +8 -3
- ui_kits/chan-compass/views.jsx +21 -13
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="
|
| 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"
|
| 95 |
</div>
|
| 96 |
</header>
|
| 97 |
|
|
@@ -104,7 +109,7 @@ function App() {
|
|
| 104 |
</main>
|
| 105 |
|
| 106 |
<footer className="cc-footer">
|
| 107 |
-
<span>Chan Compass ·
|
| 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
|
| 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 ? "
|
| 164 |
</Button>
|
|
|
|
| 165 |
</div>
|
| 166 |
<div className="cc-llm-out">
|
| 167 |
-
{aiText ? <Markdown text={aiText}/> : <span className="dim">AI output appears here.
|
| 168 |
</div>
|
| 169 |
-
<EmailRow getContent={()=>aiText} tag="Signals
|
| 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 |
-
<
|
| 210 |
-
|
| 211 |
-
|
| 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 () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
<
|
| 381 |
-
|
| 382 |
-
</
|
| 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">
|