@jellyos/agent 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/README.npm.md +212 -0
- package/bin/jellyos-mcp +26 -0
- package/dist/api/ExtensionAPI.d.ts +6 -0
- package/dist/api/Registry.js +3 -1
- package/dist/cli.js +117 -42
- package/dist/index.d.ts +24 -1
- package/dist/index.js +19 -2
- package/dist/mcp/entry.d.ts +2 -0
- package/dist/mcp/entry.js +71 -0
- package/dist/mcp/server.d.ts +31 -0
- package/dist/mcp/server.js +128 -0
- package/dist/models/CostTracker.d.ts +66 -0
- package/dist/models/CostTracker.js +148 -0
- package/dist/models/ModelRegistry.d.ts +157 -0
- package/dist/models/ModelRegistry.js +496 -0
- package/dist/models/index.d.ts +5 -0
- package/dist/models/index.js +3 -0
- package/dist/runner/AgentRunner.d.ts +23 -2
- package/dist/runner/AgentRunner.js +264 -24
- package/dist/runner/ModelClient.d.ts +26 -6
- package/dist/runner/ModelClient.js +147 -28
- package/dist/runner/SwarmRouter.d.ts +10 -7
- package/dist/runner/SwarmRouter.js +85 -28
- package/dist/runner/ToolDispatcher.d.ts +10 -0
- package/dist/runner/ToolDispatcher.js +106 -2
- package/dist/scheduler/AgentScheduler.d.ts +118 -0
- package/dist/scheduler/AgentScheduler.js +253 -0
- package/dist/session/ContextStore.d.ts +96 -0
- package/dist/session/ContextStore.js +207 -0
- package/dist/session/GoalManager.d.ts +101 -0
- package/dist/session/GoalManager.js +167 -0
- package/dist/session/MemoryStore.d.ts +48 -0
- package/dist/session/MemoryStore.js +166 -0
- package/dist/session/SessionManager.d.ts +45 -4
- package/dist/session/SessionManager.js +151 -8
- package/dist/telemetry/Tracer.d.ts +48 -0
- package/dist/telemetry/Tracer.js +102 -0
- package/dist/tests/ContextStore.test.d.ts +2 -0
- package/dist/tests/ContextStore.test.js +74 -0
- package/dist/tests/ModelRegistry.test.d.ts +2 -0
- package/dist/tests/ModelRegistry.test.js +69 -0
- package/dist/tests/SessionManager.test.d.ts +2 -0
- package/dist/tests/SessionManager.test.js +108 -0
- package/dist/tests/TechnicalAnalysis.test.d.ts +2 -0
- package/dist/tests/TechnicalAnalysis.test.js +109 -0
- package/dist/tools/MarketSentiment.d.ts +166 -0
- package/dist/tools/MarketSentiment.js +209 -0
- package/dist/tools/NewsSentiment.d.ts +67 -0
- package/dist/tools/NewsSentiment.js +226 -0
- package/dist/tools/PriceFeed.d.ts +105 -0
- package/dist/tools/PriceFeed.js +282 -0
- package/dist/tools/TechnicalAnalysis.d.ts +110 -0
- package/dist/tools/TechnicalAnalysis.js +357 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.js +4 -0
- package/dist/tui/App.d.ts +7 -5
- package/dist/tui/App.js +350 -65
- package/dist/tui/REPL.d.ts +2 -1
- package/dist/tui/REPL.js +11 -6
- package/dist/tui/StatusBar.js +1 -1
- package/package.json +9 -4
- package/dist/api/ExtensionAPI.d.ts.map +0 -1
- package/dist/api/ExtensionAPI.js.map +0 -1
- package/dist/api/Registry.d.ts.map +0 -1
- package/dist/api/Registry.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/loader.d.ts.map +0 -1
- package/dist/loader.js.map +0 -1
- package/dist/runner/AgentRunner.d.ts.map +0 -1
- package/dist/runner/AgentRunner.js.map +0 -1
- package/dist/runner/ModelClient.d.ts.map +0 -1
- package/dist/runner/ModelClient.js.map +0 -1
- package/dist/runner/SwarmRouter.d.ts.map +0 -1
- package/dist/runner/SwarmRouter.js.map +0 -1
- package/dist/runner/ToolDispatcher.d.ts.map +0 -1
- package/dist/runner/ToolDispatcher.js.map +0 -1
- package/dist/session/SessionManager.d.ts.map +0 -1
- package/dist/session/SessionManager.js.map +0 -1
- package/dist/tui/App.d.ts.map +0 -1
- package/dist/tui/App.js.map +0 -1
- package/dist/tui/REPL.d.ts.map +0 -1
- package/dist/tui/REPL.js.map +0 -1
- package/dist/tui/StatusBar.d.ts.map +0 -1
- package/dist/tui/StatusBar.js.map +0 -1
- package/dist/tui/theme.d.ts.map +0 -1
- package/dist/tui/theme.js.map +0 -1
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Technical Analysis Toolkit — RSI, MACD, EMA, Bollinger Bands,
|
|
3
|
+
* moving averages, volume profile, and signal detection.
|
|
4
|
+
*
|
|
5
|
+
* All calculations are pure math on price arrays — no external API needed.
|
|
6
|
+
* Designed to be exposed as tools the AI can call autonomously.
|
|
7
|
+
*/
|
|
8
|
+
import { type Static } from "@sinclair/typebox";
|
|
9
|
+
export interface OHLCV {
|
|
10
|
+
timestamp: number;
|
|
11
|
+
open: number;
|
|
12
|
+
high: number;
|
|
13
|
+
low: number;
|
|
14
|
+
close: number;
|
|
15
|
+
volume: number;
|
|
16
|
+
}
|
|
17
|
+
export interface AnalysisResult {
|
|
18
|
+
indicator: string;
|
|
19
|
+
value: number | number[];
|
|
20
|
+
signal?: "bullish" | "bearish" | "neutral";
|
|
21
|
+
metadata?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
export declare function sma(prices: number[], period: number): number[];
|
|
24
|
+
export declare function ema(prices: number[], period: number): number[];
|
|
25
|
+
export declare function rsi(prices: number[], period?: number): number[];
|
|
26
|
+
export declare function macd(prices: number[], fast?: number, slow?: number, signal?: number): {
|
|
27
|
+
macd: number[];
|
|
28
|
+
signal: number[];
|
|
29
|
+
histogram: number[];
|
|
30
|
+
};
|
|
31
|
+
export declare function bollingerBands(prices: number[], period?: number, deviations?: number): {
|
|
32
|
+
upper: number[];
|
|
33
|
+
middle: number[];
|
|
34
|
+
lower: number[];
|
|
35
|
+
width: number[];
|
|
36
|
+
};
|
|
37
|
+
export declare function atr(highs: number[], lows: number[], closes: number[], period?: number): number[];
|
|
38
|
+
export declare function volumeProfile(volumes: number[], prices: number[], levels?: number): {
|
|
39
|
+
priceRange: string;
|
|
40
|
+
volume: number;
|
|
41
|
+
pct: number;
|
|
42
|
+
}[];
|
|
43
|
+
export declare function detectCrossovers(fast: number[], slow: number[]): {
|
|
44
|
+
type: "golden_cross" | "death_cross" | null;
|
|
45
|
+
index: number;
|
|
46
|
+
};
|
|
47
|
+
export declare function rsiSignal(rsiValues: number[]): AnalysisResult;
|
|
48
|
+
export declare function bollingerSignal(price: number, bands: {
|
|
49
|
+
upper: number[];
|
|
50
|
+
lower: number[];
|
|
51
|
+
}): AnalysisResult;
|
|
52
|
+
export declare function macdSignal(macdData: {
|
|
53
|
+
macd: number[];
|
|
54
|
+
signal: number[];
|
|
55
|
+
histogram: number[];
|
|
56
|
+
}): AnalysisResult;
|
|
57
|
+
export declare function fullAnalysis(ohlcv: OHLCV[]): AnalysisResult[];
|
|
58
|
+
export declare const analyzeTAParams: import("@sinclair/typebox").TObject<{
|
|
59
|
+
prices: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TNumber>;
|
|
60
|
+
highs: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TNumber>>;
|
|
61
|
+
lows: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TNumber>>;
|
|
62
|
+
volumes: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TNumber>>;
|
|
63
|
+
}>;
|
|
64
|
+
export declare const rsiParams: import("@sinclair/typebox").TObject<{
|
|
65
|
+
prices: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TNumber>;
|
|
66
|
+
period: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
67
|
+
}>;
|
|
68
|
+
export declare const macdParams: import("@sinclair/typebox").TObject<{
|
|
69
|
+
prices: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TNumber>;
|
|
70
|
+
fast: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
71
|
+
slow: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
72
|
+
signal: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
73
|
+
}>;
|
|
74
|
+
export declare const getCandlesParams: import("@sinclair/typebox").TObject<{
|
|
75
|
+
symbol: import("@sinclair/typebox").TString;
|
|
76
|
+
interval: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
77
|
+
limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
78
|
+
analyze: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
79
|
+
}>;
|
|
80
|
+
export declare function getCandlesTool(_id: string, params: Static<typeof getCandlesParams>): Promise<{
|
|
81
|
+
content: {
|
|
82
|
+
type: "text";
|
|
83
|
+
text: string;
|
|
84
|
+
}[];
|
|
85
|
+
details: {
|
|
86
|
+
symbol?: undefined;
|
|
87
|
+
interval?: undefined;
|
|
88
|
+
candleCount?: undefined;
|
|
89
|
+
latest?: undefined;
|
|
90
|
+
priceChangePercent?: undefined;
|
|
91
|
+
};
|
|
92
|
+
} | {
|
|
93
|
+
content: {
|
|
94
|
+
type: "text";
|
|
95
|
+
text: string;
|
|
96
|
+
}[];
|
|
97
|
+
details: {
|
|
98
|
+
symbol: string;
|
|
99
|
+
interval: string;
|
|
100
|
+
candleCount: number;
|
|
101
|
+
latest: {
|
|
102
|
+
close: number;
|
|
103
|
+
high: number;
|
|
104
|
+
low: number;
|
|
105
|
+
volume: number;
|
|
106
|
+
};
|
|
107
|
+
priceChangePercent: number;
|
|
108
|
+
};
|
|
109
|
+
}>;
|
|
110
|
+
//# sourceMappingURL=TechnicalAnalysis.d.ts.map
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Technical Analysis Toolkit — RSI, MACD, EMA, Bollinger Bands,
|
|
3
|
+
* moving averages, volume profile, and signal detection.
|
|
4
|
+
*
|
|
5
|
+
* All calculations are pure math on price arrays — no external API needed.
|
|
6
|
+
* Designed to be exposed as tools the AI can call autonomously.
|
|
7
|
+
*/
|
|
8
|
+
import { Type } from "@sinclair/typebox";
|
|
9
|
+
// ── Core calculations ────────────────────────────────────────────────────────
|
|
10
|
+
export function sma(prices, period) {
|
|
11
|
+
if (prices.length < period)
|
|
12
|
+
return [];
|
|
13
|
+
const result = [];
|
|
14
|
+
let sum = 0;
|
|
15
|
+
for (let i = 0; i < prices.length; i++) {
|
|
16
|
+
sum += prices[i];
|
|
17
|
+
if (i >= period)
|
|
18
|
+
sum -= prices[i - period];
|
|
19
|
+
if (i >= period - 1)
|
|
20
|
+
result.push(sum / period);
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
export function ema(prices, period) {
|
|
25
|
+
if (prices.length < 2)
|
|
26
|
+
return [];
|
|
27
|
+
const k = 2 / (period + 1);
|
|
28
|
+
const result = [prices[0]];
|
|
29
|
+
for (let i = 1; i < prices.length; i++) {
|
|
30
|
+
result.push(prices[i] * k + result[i - 1] * (1 - k));
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
export function rsi(prices, period = 14) {
|
|
35
|
+
if (prices.length < period + 1)
|
|
36
|
+
return [];
|
|
37
|
+
const result = [];
|
|
38
|
+
let avgGain = 0, avgLoss = 0;
|
|
39
|
+
for (let i = 1; i <= period; i++) {
|
|
40
|
+
const diff = prices[i] - prices[i - 1];
|
|
41
|
+
if (diff > 0)
|
|
42
|
+
avgGain += diff;
|
|
43
|
+
else
|
|
44
|
+
avgLoss += Math.abs(diff);
|
|
45
|
+
}
|
|
46
|
+
avgGain /= period;
|
|
47
|
+
avgLoss /= period;
|
|
48
|
+
for (let i = period + 1; i < prices.length; i++) {
|
|
49
|
+
const diff = prices[i] - prices[i - 1];
|
|
50
|
+
const gain = diff > 0 ? diff : 0;
|
|
51
|
+
const loss = diff < 0 ? Math.abs(diff) : 0;
|
|
52
|
+
avgGain = (avgGain * (period - 1) + gain) / period;
|
|
53
|
+
avgLoss = (avgLoss * (period - 1) + loss) / period;
|
|
54
|
+
const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
|
|
55
|
+
result.push(100 - (100 / (1 + rs)));
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
export function macd(prices, fast = 12, slow = 26, signal = 9) {
|
|
60
|
+
const fastEMA = ema(prices, fast);
|
|
61
|
+
const slowEMA = ema(prices, slow);
|
|
62
|
+
const start = slowEMA.length - fastEMA.length;
|
|
63
|
+
const macdLine = [];
|
|
64
|
+
for (let i = 0; i < fastEMA.length; i++) {
|
|
65
|
+
const slowIdx = start + i;
|
|
66
|
+
if (slowIdx >= 0)
|
|
67
|
+
macdLine.push(fastEMA[i] - slowEMA[slowIdx]);
|
|
68
|
+
}
|
|
69
|
+
if (macdLine.length < signal)
|
|
70
|
+
return { macd: macdLine, signal: [], histogram: [] };
|
|
71
|
+
const signalLine = ema(macdLine, signal);
|
|
72
|
+
const hist = macdLine.slice(signal - 1).map((v, i) => v - (signalLine[i] ?? 0));
|
|
73
|
+
// Align signal line with histogram
|
|
74
|
+
const alignedSignal = signalLine;
|
|
75
|
+
return { macd: macdLine, signal: alignedSignal, histogram: hist };
|
|
76
|
+
}
|
|
77
|
+
export function bollingerBands(prices, period = 20, deviations = 2) {
|
|
78
|
+
const middle = sma(prices, period);
|
|
79
|
+
const upper = [];
|
|
80
|
+
const lower = [];
|
|
81
|
+
const width = [];
|
|
82
|
+
for (let i = period - 1; i < prices.length; i++) {
|
|
83
|
+
const slice = prices.slice(i - period + 1, i + 1);
|
|
84
|
+
const avg = slice.reduce((a, b) => a + b, 0) / period;
|
|
85
|
+
const variance = slice.reduce((sum, v) => sum + (v - avg) ** 2, 0) / period;
|
|
86
|
+
const std = Math.sqrt(variance);
|
|
87
|
+
upper.push(avg + deviations * std);
|
|
88
|
+
lower.push(avg - deviations * std);
|
|
89
|
+
width.push((2 * deviations * std) / avg * 100); // bandwidth %
|
|
90
|
+
}
|
|
91
|
+
return { upper, middle, lower, width };
|
|
92
|
+
}
|
|
93
|
+
export function atr(highs, lows, closes, period = 14) {
|
|
94
|
+
if (highs.length < 2)
|
|
95
|
+
return [];
|
|
96
|
+
const tr = [];
|
|
97
|
+
for (let i = 1; i < highs.length; i++) {
|
|
98
|
+
tr.push(Math.max(highs[i] - lows[i], Math.abs(highs[i] - closes[i - 1]), Math.abs(lows[i] - closes[i - 1])));
|
|
99
|
+
}
|
|
100
|
+
return ema(tr, period);
|
|
101
|
+
}
|
|
102
|
+
export function volumeProfile(volumes, prices, levels = 10) {
|
|
103
|
+
if (volumes.length === 0)
|
|
104
|
+
return [];
|
|
105
|
+
const min = Math.min(...prices);
|
|
106
|
+
const max = Math.max(...prices);
|
|
107
|
+
const step = (max - min) / levels;
|
|
108
|
+
const profile = [];
|
|
109
|
+
for (let i = 0; i < levels; i++) {
|
|
110
|
+
profile.push({ volume: 0, low: min + i * step, high: min + (i + 1) * step });
|
|
111
|
+
}
|
|
112
|
+
for (let i = 0; i < volumes.length; i++) {
|
|
113
|
+
const price = prices[i];
|
|
114
|
+
for (let j = 0; j < levels; j++) {
|
|
115
|
+
if (price >= profile[j].low && (j === levels - 1 || price < profile[j].high)) {
|
|
116
|
+
profile[j].volume += volumes[i];
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const totalVol = profile.reduce((s, p) => s + p.volume, 0);
|
|
122
|
+
return profile.map(p => ({
|
|
123
|
+
priceRange: `${p.low.toFixed(2)}-${p.high.toFixed(2)}`,
|
|
124
|
+
volume: Math.round(p.volume),
|
|
125
|
+
pct: totalVol > 0 ? Math.round(p.volume / totalVol * 100) : 0,
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
// ── Signal detection ─────────────────────────────────────────────────────────
|
|
129
|
+
export function detectCrossovers(fast, slow) {
|
|
130
|
+
if (fast.length < 2 || slow.length < 2)
|
|
131
|
+
return { type: null, index: -1 };
|
|
132
|
+
const offset = slow.length - fast.length;
|
|
133
|
+
for (let i = fast.length - 1; i >= Math.max(0, fast.length - 5); i--) {
|
|
134
|
+
const prevFast = fast[i - 1];
|
|
135
|
+
const prevSlow = slow[offset + i - 1];
|
|
136
|
+
const currFast = fast[i];
|
|
137
|
+
const currSlow = slow[offset + i];
|
|
138
|
+
if (prevFast === undefined || prevSlow === undefined || currFast === undefined || currSlow === undefined)
|
|
139
|
+
continue;
|
|
140
|
+
if (prevFast <= prevSlow && currFast > currSlow)
|
|
141
|
+
return { type: "golden_cross", index: i };
|
|
142
|
+
if (prevFast >= prevSlow && currFast < currSlow)
|
|
143
|
+
return { type: "death_cross", index: i };
|
|
144
|
+
}
|
|
145
|
+
return { type: null, index: -1 };
|
|
146
|
+
}
|
|
147
|
+
export function rsiSignal(rsiValues) {
|
|
148
|
+
const last = rsiValues[rsiValues.length - 1];
|
|
149
|
+
if (last === undefined)
|
|
150
|
+
return { indicator: "RSI", value: 0, signal: "neutral" };
|
|
151
|
+
if (last > 70)
|
|
152
|
+
return { indicator: "RSI", value: Math.round(last * 100) / 100, signal: "bearish", metadata: { condition: "overbought" } };
|
|
153
|
+
if (last < 30)
|
|
154
|
+
return { indicator: "RSI", value: Math.round(last * 100) / 100, signal: "bullish", metadata: { condition: "oversold" } };
|
|
155
|
+
return { indicator: "RSI", value: Math.round(last * 100) / 100, signal: "neutral" };
|
|
156
|
+
}
|
|
157
|
+
export function bollingerSignal(price, bands) {
|
|
158
|
+
const upper = bands.upper[bands.upper.length - 1];
|
|
159
|
+
const lower = bands.lower[bands.lower.length - 1];
|
|
160
|
+
if (upper === undefined || lower === undefined)
|
|
161
|
+
return { indicator: "Bollinger", value: 0, signal: "neutral" };
|
|
162
|
+
const position = (price - lower) / (upper - lower);
|
|
163
|
+
if (position > 0.95)
|
|
164
|
+
return { indicator: "Bollinger", value: Math.round(position * 100), signal: "bearish", metadata: { condition: "upper band touch" } };
|
|
165
|
+
if (position < 0.05)
|
|
166
|
+
return { indicator: "Bollinger", value: Math.round(position * 100), signal: "bullish", metadata: { condition: "lower band touch" } };
|
|
167
|
+
return { indicator: "Bollinger", value: Math.round(position * 100), signal: "neutral" };
|
|
168
|
+
}
|
|
169
|
+
export function macdSignal(macdData) {
|
|
170
|
+
const hist = macdData.histogram;
|
|
171
|
+
if (hist.length < 2)
|
|
172
|
+
return { indicator: "MACD", value: 0, signal: "neutral" };
|
|
173
|
+
const last = hist[hist.length - 1];
|
|
174
|
+
const prev = hist[hist.length - 2];
|
|
175
|
+
const value = Math.round(last * 1_000_000) / 1_000_000;
|
|
176
|
+
if (last > 0 && prev <= 0)
|
|
177
|
+
return { indicator: "MACD", value, signal: "bullish", metadata: { condition: "histogram crossed above zero" } };
|
|
178
|
+
if (last < 0 && prev >= 0)
|
|
179
|
+
return { indicator: "MACD", value, signal: "bearish", metadata: { condition: "histogram crossed below zero" } };
|
|
180
|
+
if (last > prev)
|
|
181
|
+
return { indicator: "MACD", value, signal: "bullish", metadata: { condition: "histogram rising" } };
|
|
182
|
+
if (last < prev)
|
|
183
|
+
return { indicator: "MACD", value, signal: "bearish", metadata: { condition: "histogram falling" } };
|
|
184
|
+
return { indicator: "MACD", value, signal: "neutral" };
|
|
185
|
+
}
|
|
186
|
+
// ── Full analysis ────────────────────────────────────────────────────────────
|
|
187
|
+
export function fullAnalysis(ohlcv) {
|
|
188
|
+
const closes = ohlcv.map(c => c.close);
|
|
189
|
+
const highs = ohlcv.map(c => c.high);
|
|
190
|
+
const lows = ohlcv.map(c => c.low);
|
|
191
|
+
const volumes = ohlcv.map(c => c.volume);
|
|
192
|
+
if (closes.length < 30)
|
|
193
|
+
return [{ indicator: "ERROR", value: 0, signal: "neutral", metadata: { message: `Need 30+ candles, got ${closes.length}` } }];
|
|
194
|
+
const results = [];
|
|
195
|
+
// RSI
|
|
196
|
+
const rsiVals = rsi(closes, 14);
|
|
197
|
+
results.push(rsiSignal(rsiVals));
|
|
198
|
+
// MACD
|
|
199
|
+
const macdData = macd(closes, 12, 26, 9);
|
|
200
|
+
results.push(macdSignal(macdData));
|
|
201
|
+
// Bollinger
|
|
202
|
+
const bb = bollingerBands(closes, 20, 2);
|
|
203
|
+
results.push(bollingerSignal(closes[closes.length - 1], bb));
|
|
204
|
+
// EMA crossover (12/26)
|
|
205
|
+
const ema12 = ema(closes, 12);
|
|
206
|
+
const ema26 = ema(closes, 26);
|
|
207
|
+
const cross = detectCrossovers(ema12, ema26);
|
|
208
|
+
results.push({
|
|
209
|
+
indicator: "EMA Cross",
|
|
210
|
+
value: cross.type === "golden_cross" ? 1 : cross.type === "death_cross" ? -1 : 0,
|
|
211
|
+
signal: cross.type === "golden_cross" ? "bullish" : cross.type === "death_cross" ? "bearish" : "neutral",
|
|
212
|
+
metadata: { type: cross.type, index: cross.index },
|
|
213
|
+
});
|
|
214
|
+
// ATR (volatility)
|
|
215
|
+
const atrVals = atr(highs, lows, closes, 14);
|
|
216
|
+
const lastATR = atrVals[atrVals.length - 1] ?? 0;
|
|
217
|
+
results.push({
|
|
218
|
+
indicator: "ATR",
|
|
219
|
+
value: Math.round(lastATR * 100) / 100,
|
|
220
|
+
signal: lastATR > closes[closes.length - 1] * 0.05 ? "bearish" : "neutral",
|
|
221
|
+
metadata: { pct_of_price: Math.round(lastATR / closes[closes.length - 1] * 10000) / 100 },
|
|
222
|
+
});
|
|
223
|
+
// Volume profile
|
|
224
|
+
const vp = volumeProfile(volumes.slice(-50), closes.slice(-50), 5);
|
|
225
|
+
results.push({
|
|
226
|
+
indicator: "Volume Profile",
|
|
227
|
+
value: 0,
|
|
228
|
+
signal: "neutral",
|
|
229
|
+
metadata: { profile: vp },
|
|
230
|
+
});
|
|
231
|
+
// Simple moving averages
|
|
232
|
+
const sma20 = sma(closes, 20);
|
|
233
|
+
const sma50 = sma(closes, 50);
|
|
234
|
+
const lastSma20 = sma20[sma20.length - 1] ?? 0;
|
|
235
|
+
const lastSma50 = sma50[sma50.length - 1] ?? 0;
|
|
236
|
+
const price = closes[closes.length - 1];
|
|
237
|
+
results.push({
|
|
238
|
+
indicator: "SMA Position",
|
|
239
|
+
value: Math.round((price / lastSma20 - 1) * 10000) / 100,
|
|
240
|
+
signal: price > lastSma20 && price > lastSma50 ? "bullish" : price < lastSma20 && price < lastSma50 ? "bearish" : "neutral",
|
|
241
|
+
metadata: { sma20: Math.round(lastSma20 * 100) / 100, sma50: Math.round(lastSma50 * 100) / 100, price },
|
|
242
|
+
});
|
|
243
|
+
// Overall score
|
|
244
|
+
const signals = results.filter(r => r.signal === "bullish" || r.signal === "bearish");
|
|
245
|
+
const bullish = signals.filter(s => s.signal === "bullish").length;
|
|
246
|
+
const bearish = signals.filter(s => s.signal === "bearish").length;
|
|
247
|
+
results.push({
|
|
248
|
+
indicator: "SUMMARY",
|
|
249
|
+
value: bullish - bearish,
|
|
250
|
+
signal: bullish > bearish ? "bullish" : bearish > bullish ? "bearish" : "neutral",
|
|
251
|
+
metadata: { bullish_indicators: bullish, bearish_indicators: bearish, total: signals.length },
|
|
252
|
+
});
|
|
253
|
+
return results;
|
|
254
|
+
}
|
|
255
|
+
// ── Tool parameter schemas ───────────────────────────────────────────────────
|
|
256
|
+
export const analyzeTAParams = Type.Object({
|
|
257
|
+
prices: Type.Array(Type.Number(), { description: "Array of closing prices (most recent last)" }),
|
|
258
|
+
highs: Type.Optional(Type.Array(Type.Number(), { description: "High prices (optional, for ATR)" })),
|
|
259
|
+
lows: Type.Optional(Type.Array(Type.Number(), { description: "Low prices (optional, for ATR)" })),
|
|
260
|
+
volumes: Type.Optional(Type.Array(Type.Number(), { description: "Volume data (optional, for volume profile)" })),
|
|
261
|
+
});
|
|
262
|
+
export const rsiParams = Type.Object({
|
|
263
|
+
prices: Type.Array(Type.Number(), { description: "Array of closing prices" }),
|
|
264
|
+
period: Type.Optional(Type.Number({ default: 14, description: "RSI period" })),
|
|
265
|
+
});
|
|
266
|
+
export const macdParams = Type.Object({
|
|
267
|
+
prices: Type.Array(Type.Number(), { description: "Array of closing prices" }),
|
|
268
|
+
fast: Type.Optional(Type.Number({ default: 12 })),
|
|
269
|
+
slow: Type.Optional(Type.Number({ default: 26 })),
|
|
270
|
+
signal: Type.Optional(Type.Number({ default: 9 })),
|
|
271
|
+
});
|
|
272
|
+
// ── Tool: get_candles (#18 — fetch real OHLCV data from Binance) ─────────────
|
|
273
|
+
export const getCandlesParams = Type.Object({
|
|
274
|
+
symbol: Type.String({ description: "Crypto symbol e.g. BTC, ETH, SOL" }),
|
|
275
|
+
interval: Type.Optional(Type.String({ description: "Candle interval: 1m 5m 15m 1h 4h 1d (default: 1h)" })),
|
|
276
|
+
limit: Type.Optional(Type.Number({ description: "Number of candles 1-500 (default: 100)" })),
|
|
277
|
+
analyze: Type.Optional(Type.Boolean({ description: "Run full TA analysis on fetched candles (default: true)" })),
|
|
278
|
+
});
|
|
279
|
+
export async function getCandlesTool(_id, params) {
|
|
280
|
+
const symbol = params.symbol.toUpperCase().replace(/USDT$/, "") + "USDT";
|
|
281
|
+
const interval = params.interval ?? "1h";
|
|
282
|
+
const limit = Math.min(Math.max(params.limit ?? 100, 10), 500);
|
|
283
|
+
const analyze = params.analyze !== false;
|
|
284
|
+
const VALID_INTERVALS = new Set(["1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "3d", "1w", "1M"]);
|
|
285
|
+
if (!VALID_INTERVALS.has(interval)) {
|
|
286
|
+
return {
|
|
287
|
+
content: [{ type: "text", text: `Invalid interval "${interval}". Valid: ${[...VALID_INTERVALS].join(", ")}` }],
|
|
288
|
+
details: {},
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
const res = await fetch(`https://api.binance.com/api/v3/klines?symbol=${symbol}&interval=${interval}&limit=${limit}`, { signal: AbortSignal.timeout(12_000), headers: { "User-Agent": "JellyOS/1.0" } });
|
|
293
|
+
if (!res.ok) {
|
|
294
|
+
return {
|
|
295
|
+
content: [{ type: "text", text: `Binance API error ${res.status} for ${symbol}. Check symbol.` }],
|
|
296
|
+
details: {},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const raw = await res.json();
|
|
300
|
+
const candles = raw.map(c => ({
|
|
301
|
+
timestamp: c[0],
|
|
302
|
+
open: parseFloat(c[1]),
|
|
303
|
+
high: parseFloat(c[2]),
|
|
304
|
+
low: parseFloat(c[3]),
|
|
305
|
+
close: parseFloat(c[4]),
|
|
306
|
+
volume: parseFloat(c[5]),
|
|
307
|
+
}));
|
|
308
|
+
const latest = candles[candles.length - 1];
|
|
309
|
+
const oldest = candles[0];
|
|
310
|
+
const chgPct = ((latest.close - oldest.close) / oldest.close * 100).toFixed(2);
|
|
311
|
+
const priceStr = latest.close < 1
|
|
312
|
+
? `$${latest.close.toFixed(6)}`
|
|
313
|
+
: `$${latest.close.toLocaleString(undefined, { maximumFractionDigits: 2 })}`;
|
|
314
|
+
const lines = [
|
|
315
|
+
`${symbol} | ${interval} | ${candles.length} candles`,
|
|
316
|
+
`Close: ${priceStr} Period change: ${parseFloat(chgPct) >= 0 ? "+" : ""}${chgPct}%`,
|
|
317
|
+
`High: $${latest.high.toLocaleString()} Low: $${latest.low.toLocaleString()}`,
|
|
318
|
+
`Volume (last bar): ${latest.volume.toLocaleString()}`,
|
|
319
|
+
];
|
|
320
|
+
if (analyze && candles.length >= 30) {
|
|
321
|
+
const results = fullAnalysis(candles);
|
|
322
|
+
const summary = results.find(r => r.indicator === "SUMMARY");
|
|
323
|
+
lines.push("", "── Technical Analysis ──");
|
|
324
|
+
for (const r of results) {
|
|
325
|
+
if (r.indicator === "SUMMARY")
|
|
326
|
+
continue;
|
|
327
|
+
const icon = r.signal === "bullish" ? "🟢" : r.signal === "bearish" ? "🔴" : "⚪";
|
|
328
|
+
const val = typeof r.value === "number" ? r.value.toFixed(2) : String(r.value);
|
|
329
|
+
const meta = r.metadata?.condition ? ` (${String(r.metadata.condition)})` : "";
|
|
330
|
+
lines.push(` ${icon} ${r.indicator.padEnd(16)} ${val}${meta}`);
|
|
331
|
+
}
|
|
332
|
+
if (summary) {
|
|
333
|
+
const icon = summary.signal === "bullish" ? "🟢" : summary.signal === "bearish" ? "🔴" : "⚪";
|
|
334
|
+
lines.push("", `${icon} OVERALL: ${String(summary.signal).toUpperCase()} — ${String(summary.metadata?.bullish_indicators ?? 0)} bull / ${String(summary.metadata?.bearish_indicators ?? 0)} bear signals`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
else if (analyze && candles.length < 30) {
|
|
338
|
+
lines.push(`(need 30+ candles for TA, got ${candles.length} — increase limit)`);
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
342
|
+
details: {
|
|
343
|
+
symbol, interval, candleCount: candles.length,
|
|
344
|
+
latest: { close: latest.close, high: latest.high, low: latest.low, volume: latest.volume },
|
|
345
|
+
priceChangePercent: parseFloat(chgPct),
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
catch (e) {
|
|
350
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
351
|
+
return {
|
|
352
|
+
content: [{ type: "text", text: `Failed to fetch candles for ${symbol}: ${msg}` }],
|
|
353
|
+
details: {},
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
//# sourceMappingURL=TechnicalAnalysis.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { fullAnalysis, rsi, macd, ema, sma, bollingerBands, atr, rsiSignal, macdSignal, bollingerSignal } from "./TechnicalAnalysis.js";
|
|
2
|
+
export type { OHLCV, AnalysisResult } from "./TechnicalAnalysis.js";
|
|
3
|
+
export { PriceFeed, priceFeed, getPricesTool, topMoversTool, marketOverviewTool } from "./PriceFeed.js";
|
|
4
|
+
export type { PriceTick } from "./PriceFeed.js";
|
|
5
|
+
export { NewsFeed, newsFeed, getNewsTool, scoreSentiment } from "./NewsSentiment.js";
|
|
6
|
+
export type { NewsItem, SentimentReport } from "./NewsSentiment.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { fullAnalysis, rsi, macd, ema, sma, bollingerBands, atr, rsiSignal, macdSignal, bollingerSignal } from "./TechnicalAnalysis.js";
|
|
2
|
+
export { PriceFeed, priceFeed, getPricesTool, topMoversTool, marketOverviewTool } from "./PriceFeed.js";
|
|
3
|
+
export { NewsFeed, newsFeed, getNewsTool, scoreSentiment } from "./NewsSentiment.js";
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
package/dist/tui/App.d.ts
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* App — root Ink component.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Multi-pane TUI with context bar, syntax-highlighted tool output,
|
|
4
|
+
* live side panel with ticker/prices, and command palette triggered via /palette.
|
|
5
5
|
*/
|
|
6
6
|
import { Registry } from "../api/Registry.js";
|
|
7
|
+
import type { ModelRegistry } from "../models/ModelRegistry.js";
|
|
8
|
+
import type { CostTracker } from "../models/CostTracker.js";
|
|
7
9
|
export interface AppProps {
|
|
8
10
|
registry: Registry;
|
|
9
11
|
systemPrompt?: string;
|
|
10
12
|
effectLevel?: string;
|
|
11
13
|
chain?: string;
|
|
12
|
-
|
|
14
|
+
modelReg?: ModelRegistry;
|
|
15
|
+
costTracker?: CostTracker;
|
|
13
16
|
onNotifyReady?: (fn: (msg: string) => void) => void;
|
|
14
|
-
/** Status updater callback injected before mount */
|
|
15
17
|
onStatusReady?: (fn: (key: string, val: string) => void) => void;
|
|
16
18
|
}
|
|
17
|
-
export declare function App({ registry, systemPrompt, effectLevel: initialEffect, chain: initialChain, onNotifyReady, onStatusReady, }: AppProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export declare function App({ registry, systemPrompt, effectLevel: initialEffect, chain: initialChain, modelReg, costTracker, onNotifyReady, onStatusReady, }: AppProps): import("react/jsx-runtime").JSX.Element;
|
|
18
20
|
//# sourceMappingURL=App.d.ts.map
|