feat: add Citations and WatchlistChat components, integrate with API
- Implemented Citations component to display citation data. - Created WatchlistDrawer and ChatSidebar components for managing watchlists and AI chat functionality. - Integrated API calls for watchlist management and chat interactions. - Updated index.tsx to include new components in the main application layout. - Added API client in lib/api.ts for structured API interactions. - Developed Alerts, Dashboard, Portfolio, Routines, Screener, and Report pages with relevant data fetching and UI components. - Introduced styles in tokens.css for consistent theming across the application. - Configured TypeScript and Vite for project setup and development.
This commit is contained in:
1 parent
0db2cf28b3
commit
8c184ccae1
111 files changed
+12349
-62
No files matched your search
@@ -0,0 +1,132 @@
|
||||
package agents
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"flowsight/internal/model"
|
||||
"flowsight/internal/sectors"
|
||||
)
|
||||
|
||||
// AnalyzeBrokerIntel (A2) classifies broker behavior and emits sector
|
||||
// rotation on week-over-week sign flips with evidence rows.
|
||||
func AnalyzeBrokerIntel(ctx context.Context, d Deps, ticker string) model.AgentResult {
|
||||
_ = ctx
|
||||
ticker = strings.ToUpper(ticker)
|
||||
res := model.AgentResult{Summary: "no broker snapshots available"}
|
||||
|
||||
var registry []sectors.BrokerRegistryRow
|
||||
regDate, regOK := payload(d.DB, "IDX", "brokers-registry", ®istry)
|
||||
var top struct {
|
||||
Date string `json:"date"`
|
||||
Results []sectors.TopBrokerRow `json:"results"`
|
||||
}
|
||||
topDate, topOK := payload(d.DB, "IDX", "brokers-top", &top)
|
||||
if !regOK && !topOK {
|
||||
return res
|
||||
}
|
||||
if regOK {
|
||||
res.Citations = append(res.Citations, model.Cite("v2/brokers/", "IDX", regDate))
|
||||
}
|
||||
if topOK {
|
||||
res.Citations = append(res.Citations, model.Cite("v2/brokers/top/", "IDX", topDate))
|
||||
}
|
||||
|
||||
byCode := map[string]sectors.BrokerRegistryRow{}
|
||||
for _, r := range registry {
|
||||
byCode[r.Code] = r
|
||||
}
|
||||
accum, distrib := 0, 0
|
||||
var lines []string
|
||||
for _, b := range top.Results {
|
||||
row := byCode[b.BrokerCode]
|
||||
origin := "domestic"
|
||||
cohort := "unknown"
|
||||
if row.IsForeign {
|
||||
origin = "foreign"
|
||||
}
|
||||
if row.Cohort != nil && *row.Cohort != "" {
|
||||
cohort = *row.Cohort
|
||||
}
|
||||
class := "neutral"
|
||||
switch {
|
||||
case b.Net > 0:
|
||||
class, accum = "accumulating", accum+1
|
||||
case b.Net < 0:
|
||||
class, distrib = "distributing", distrib+1
|
||||
}
|
||||
if len(lines) < 5 {
|
||||
lines = append(lines, fmt.Sprintf("%s (%s/%s) %s %s",
|
||||
b.BrokerCode, origin, cohort, class, fmtIDR(float64(b.Net))))
|
||||
}
|
||||
}
|
||||
total := accum + distrib
|
||||
score := 0.0
|
||||
if total > 0 {
|
||||
score = float64(accum-distrib) / float64(total) * 100
|
||||
}
|
||||
res.Score = clampScore(score, -100, 100)
|
||||
|
||||
// Rotation: week-over-week sign flip on stored sector nets.
|
||||
var flow struct {
|
||||
Week string `json:"week"`
|
||||
Current map[string]float64 `json:"current"`
|
||||
Previous map[string]float64 `json:"previous"`
|
||||
}
|
||||
flowDate, flowOK := payload(d.DB, "IDX", "sector-flow", &flow)
|
||||
rotFrom, rotTo, rotDelta := "", "", 0.0
|
||||
if flowOK {
|
||||
res.Citations = append(res.Citations, model.Cite("v2/subsector/report/", "IDX", flowDate))
|
||||
type flip struct {
|
||||
sector string
|
||||
delta float64
|
||||
}
|
||||
var flips []flip
|
||||
for s, cur := range flow.Current {
|
||||
prev := flow.Previous[s]
|
||||
if prev < 0 && cur > 0 {
|
||||
flips = append(flips, flip{s, cur - prev})
|
||||
}
|
||||
}
|
||||
var outflows []flip
|
||||
for s, cur := range flow.Current {
|
||||
prev := flow.Previous[s]
|
||||
if prev > 0 && cur < 0 {
|
||||
outflows = append(outflows, flip{s, prev - cur})
|
||||
}
|
||||
}
|
||||
sort.Slice(flips, func(i, j int) bool { return flips[i].delta > flips[j].delta })
|
||||
sort.Slice(outflows, func(i, j int) bool { return outflows[i].delta > outflows[j].delta })
|
||||
if len(flips) > 0 && len(outflows) > 0 {
|
||||
rotFrom, rotTo, rotDelta = outflows[0].sector, flips[0].sector, flips[0].delta
|
||||
res.Flags = append(res.Flags, "sector-rotation")
|
||||
}
|
||||
}
|
||||
|
||||
res.Values = []model.Value{{
|
||||
Label: "broker behavior",
|
||||
Display: fmt.Sprintf("%d accumulating vs %d distributing", accum, distrib),
|
||||
Citations: res.Citations,
|
||||
}, {
|
||||
Label: "top brokers",
|
||||
Display: strings.Join(lines, "; "),
|
||||
Citations: res.Citations,
|
||||
}}
|
||||
if rotFrom != "" {
|
||||
res.Values = append(res.Values, model.Value{
|
||||
Label: "sector rotation",
|
||||
Display: fmt.Sprintf("%s -> %s (%s swing)", rotFrom, rotTo, fmtIDR(rotDelta)),
|
||||
Citations: res.Citations,
|
||||
})
|
||||
res.Summary = fmt.Sprintf("rotation %s -> %s; score %+.0f", rotFrom, rotTo, score)
|
||||
} else {
|
||||
res.Summary = fmt.Sprintf("no rotation flip; score %+.0f (%d vs %d)", score, accum, distrib)
|
||||
}
|
||||
res.Extra = map[string]any{
|
||||
"accumulating": accum, "distributing": distrib,
|
||||
"rotation_from": rotFrom, "rotation_to": rotTo, "rotation_delta": rotDelta,
|
||||
}
|
||||
return res
|
||||
}
|
||||
Reference in new issue
Block a user