@blockrun/franklin 3.15.12 → 3.15.13
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/dist/agent/context.js +6 -0
- package/dist/tools/trading.js +83 -22
- package/package.json +1 -1
package/dist/agent/context.js
CHANGED
|
@@ -326,6 +326,12 @@ Your training data is frozen in the past. Live-world questions MUST be answered
|
|
|
326
326
|
|
|
327
327
|
If you find yourself about to emit one of these, stop and call the tool instead. If you don't know which ticker the user means, call ExaSearch or AskUser — never deflect.
|
|
328
328
|
|
|
329
|
+
**Trading verdicts (TradingSignal).** When the user asks "how does $TICKER look" / "should I buy X" / "is BTC overbought":
|
|
330
|
+
- Run **TradingSignal** with default lookback (90d). Lower values leave MACD undefined.
|
|
331
|
+
- The tool returns a **Verdict** section with \`Direction\`, \`Bull signals\`, \`Bear signals\`. Echo it directly. Do not soften "bullish" to "leaning slightly positive" — say what the data says.
|
|
332
|
+
- If \`Data Notes\` lists an indicator as "insufficient data", state that explicitly to the user and suggest re-running with more days. Do NOT pretend that indicator is "neutral".
|
|
333
|
+
- **Forbidden default**: "持有观望", "wait and see", "hold for clearer signals" — these are bugs when ≥2 indicators voted in a clear direction. Bail out to those phrases ONLY when (a) the Verdict says \`neutral\` AND (b) the bull/bear signal lists are both genuinely empty or one of each. Otherwise commit to a direction with the reasoning the tool already gave you.
|
|
334
|
+
|
|
329
335
|
**Media generation (ImageGen / VideoGen).** Pass just the user's descriptive prompt and the output path — do NOT pass \`model\`. The harness picks the right model for the requested style + budget, refines loose prompts using a 5-slot template (scene / subject / details / use case / constraints), and surfaces both the refinement and a cost proposal through AskUser before spending. If the user wants their prompt left exactly as written, prefix it with \`///\` to skip refinement. Only pass \`model\` explicitly if the user named one specifically.`;
|
|
330
336
|
}
|
|
331
337
|
function getTokenEfficiencySection() {
|
package/dist/tools/trading.js
CHANGED
|
@@ -16,8 +16,19 @@ function formatUsd(n) {
|
|
|
16
16
|
return `$${(n / 1e3).toFixed(1)}K`;
|
|
17
17
|
return `$${n.toFixed(2)}`;
|
|
18
18
|
}
|
|
19
|
+
// MACD needs slow EMA (26) + signal EMA (9) = 35 closes minimum for the
|
|
20
|
+
// signal/histogram to be defined. Default was 30, which left signal=NaN
|
|
21
|
+
// and trend stuck at 'neutral' on every call — see the 2026-05-03 BTC
|
|
22
|
+
// report where the agent had to write "MACD signal can't be computed
|
|
23
|
+
// due to insufficient data". 90d gives stable MACD plus enough room for
|
|
24
|
+
// reasonable Bollinger bandwidth and annualized volatility readings.
|
|
25
|
+
const DEFAULT_LOOKBACK_DAYS = 90;
|
|
26
|
+
const MIN_DAYS_FOR_MACD = 35;
|
|
27
|
+
function fmtNumber(n, digits) {
|
|
28
|
+
return Number.isFinite(n) ? n.toFixed(digits) : 'n/a';
|
|
29
|
+
}
|
|
19
30
|
async function executeSignal(input, _ctx) {
|
|
20
|
-
const { ticker, days =
|
|
31
|
+
const { ticker, days = DEFAULT_LOOKBACK_DAYS } = input;
|
|
21
32
|
if (!ticker) {
|
|
22
33
|
return { output: 'Error: ticker is required', isError: true };
|
|
23
34
|
}
|
|
@@ -37,23 +48,45 @@ async function executeSignal(input, _ctx) {
|
|
|
37
48
|
const macdResult = macd(closes);
|
|
38
49
|
const bbResult = bollingerBands(closes);
|
|
39
50
|
const volResult = volatility(closes);
|
|
40
|
-
//
|
|
51
|
+
// Per-indicator validity. Each has its own minimum sample requirement
|
|
52
|
+
// and we surface the gap rather than silently defaulting to 'neutral'.
|
|
53
|
+
const macdValid = Number.isFinite(macdResult.signal) && Number.isFinite(macdResult.histogram);
|
|
54
|
+
const dataNotes = [];
|
|
55
|
+
if (!macdValid) {
|
|
56
|
+
dataNotes.push(`MACD signal/histogram unavailable — need ≥${MIN_DAYS_FOR_MACD} closes, got ${closes.length}. ` +
|
|
57
|
+
`Re-run with days=${MIN_DAYS_FOR_MACD} or higher for full trend detection.`);
|
|
58
|
+
}
|
|
59
|
+
// Direction count — only valid indicators contribute. A NaN MACD must
|
|
60
|
+
// not be counted as a 'neutral' vote, otherwise the agent reads weak
|
|
61
|
+
// data as a reason to recommend "wait and see".
|
|
41
62
|
let bullish = 0;
|
|
42
63
|
let bearish = 0;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
let votingIndicators = 0;
|
|
65
|
+
if (Number.isFinite(rsiResult.value)) {
|
|
66
|
+
votingIndicators++;
|
|
67
|
+
if (rsiResult.interpretation === 'oversold')
|
|
68
|
+
bullish++;
|
|
69
|
+
if (rsiResult.interpretation === 'overbought')
|
|
70
|
+
bearish++;
|
|
71
|
+
}
|
|
72
|
+
if (macdValid) {
|
|
73
|
+
votingIndicators++;
|
|
74
|
+
if (macdResult.trend === 'bullish')
|
|
75
|
+
bullish++;
|
|
76
|
+
if (macdResult.trend === 'bearish')
|
|
77
|
+
bearish++;
|
|
78
|
+
}
|
|
79
|
+
if (Number.isFinite(bbResult.middle)) {
|
|
80
|
+
votingIndicators++;
|
|
81
|
+
if (bbResult.position === 'below')
|
|
82
|
+
bullish++;
|
|
83
|
+
if (bbResult.position === 'above')
|
|
84
|
+
bearish++;
|
|
85
|
+
}
|
|
55
86
|
const direction = bullish > bearish ? 'bullish' : bearish > bullish ? 'bearish' : 'neutral';
|
|
56
|
-
const confidence =
|
|
87
|
+
const confidence = votingIndicators > 0
|
|
88
|
+
? Math.max(bullish, bearish) / votingIndicators
|
|
89
|
+
: 0;
|
|
57
90
|
bus.emit(makeEvent({
|
|
58
91
|
type: 'signal.detected',
|
|
59
92
|
source: 'trading',
|
|
@@ -71,6 +104,28 @@ async function executeSignal(input, _ctx) {
|
|
|
71
104
|
}));
|
|
72
105
|
const { price, change24h, marketCap, volume24h } = priceResult;
|
|
73
106
|
const last5 = closes.slice(-5).map(c => c.toFixed(2)).join(', ');
|
|
107
|
+
// MACD line: when signal/histogram are NaN, say so explicitly instead
|
|
108
|
+
// of rendering "1822.7300 / Signal: NaN / Histogram: NaN — neutral",
|
|
109
|
+
// which read as a real signal to translation models.
|
|
110
|
+
const macdLine = macdValid
|
|
111
|
+
? `- **MACD:** ${fmtNumber(macdResult.macd, 4)} / Signal: ${fmtNumber(macdResult.signal, 4)} / Histogram: ${fmtNumber(macdResult.histogram, 4)} — ${macdResult.trend}`
|
|
112
|
+
: `- **MACD:** ${fmtNumber(macdResult.macd, 4)} / Signal: insufficient data / Histogram: insufficient data — *not enough closes for trend*`;
|
|
113
|
+
// Bull / bear breakdown so the agent can echo a real verdict instead
|
|
114
|
+
// of falling back to "wait and see".
|
|
115
|
+
const bullSignals = [];
|
|
116
|
+
const bearSignals = [];
|
|
117
|
+
if (rsiResult.interpretation === 'oversold')
|
|
118
|
+
bullSignals.push('RSI oversold');
|
|
119
|
+
if (rsiResult.interpretation === 'overbought')
|
|
120
|
+
bearSignals.push('RSI overbought');
|
|
121
|
+
if (macdValid && macdResult.trend === 'bullish')
|
|
122
|
+
bullSignals.push('MACD trending up');
|
|
123
|
+
if (macdValid && macdResult.trend === 'bearish')
|
|
124
|
+
bearSignals.push('MACD trending down');
|
|
125
|
+
if (Number.isFinite(bbResult.middle) && bbResult.position === 'below')
|
|
126
|
+
bullSignals.push('price below lower Bollinger');
|
|
127
|
+
if (Number.isFinite(bbResult.middle) && bbResult.position === 'above')
|
|
128
|
+
bearSignals.push('price above upper Bollinger');
|
|
74
129
|
const output = [
|
|
75
130
|
`## ${upper} Signal Report`,
|
|
76
131
|
'',
|
|
@@ -78,11 +133,17 @@ async function executeSignal(input, _ctx) {
|
|
|
78
133
|
`**Market Cap:** ${formatUsd(marketCap)}`,
|
|
79
134
|
`**24h Volume:** ${formatUsd(volume24h)}`,
|
|
80
135
|
'',
|
|
81
|
-
`### Technical Indicators (${days}d lookback)`,
|
|
82
|
-
`- **RSI(14):** ${rsiResult.value
|
|
83
|
-
|
|
84
|
-
`- **Bollinger:** Upper ${bbResult.upper
|
|
85
|
-
`- **Volatility:** ${(volResult.annualized * 100
|
|
136
|
+
`### Technical Indicators (${days}d lookback, ${closes.length} closes)`,
|
|
137
|
+
`- **RSI(14):** ${fmtNumber(rsiResult.value, 1)} — ${rsiResult.interpretation}`,
|
|
138
|
+
macdLine,
|
|
139
|
+
`- **Bollinger:** Upper ${fmtNumber(bbResult.upper, 2)} / Middle ${fmtNumber(bbResult.middle, 2)} / Lower ${fmtNumber(bbResult.lower, 2)} — Price ${bbResult.position}`,
|
|
140
|
+
`- **Volatility:** ${fmtNumber(volResult.annualized * 100, 1)}% annualized — ${volResult.interpretation}`,
|
|
141
|
+
'',
|
|
142
|
+
`### Verdict`,
|
|
143
|
+
`**Direction:** ${direction} (${votingIndicators} indicator${votingIndicators === 1 ? '' : 's'} voting, confidence ${(confidence * 100).toFixed(0)}%)`,
|
|
144
|
+
bullSignals.length > 0 ? `**Bull signals:** ${bullSignals.join(', ')}` : '**Bull signals:** none',
|
|
145
|
+
bearSignals.length > 0 ? `**Bear signals:** ${bearSignals.join(', ')}` : '**Bear signals:** none',
|
|
146
|
+
...(dataNotes.length > 0 ? ['', `### Data Notes`, ...dataNotes.map(n => `- ${n}`)] : []),
|
|
86
147
|
'',
|
|
87
148
|
`### Raw Data`,
|
|
88
149
|
`Closes (last 5): ${last5}`,
|
|
@@ -92,12 +153,12 @@ async function executeSignal(input, _ctx) {
|
|
|
92
153
|
export const tradingSignalCapability = {
|
|
93
154
|
spec: {
|
|
94
155
|
name: 'TradingSignal',
|
|
95
|
-
description: 'Get current price, technical indicators (RSI, MACD, Bollinger Bands, volatility), and a
|
|
156
|
+
description: 'Get current price, technical indicators (RSI, MACD, Bollinger Bands, volatility), and a verdict (bullish / bearish / neutral with confidence) for a cryptocurrency. Always returns a Verdict section with bull/bear signal lists — echo it directly. When MACD signal/histogram report "insufficient data", say so explicitly; do NOT default to "wait and see".',
|
|
96
157
|
input_schema: {
|
|
97
158
|
type: 'object',
|
|
98
159
|
properties: {
|
|
99
160
|
ticker: { type: 'string', description: 'Cryptocurrency ticker, e.g. "BTC", "ETH"' },
|
|
100
|
-
days: { type: 'number', description: 'Lookback period
|
|
161
|
+
days: { type: 'number', description: 'Lookback period in days. Default 90 (recommended). Below 35 will leave MACD signal/histogram undefined.' },
|
|
101
162
|
},
|
|
102
163
|
required: ['ticker'],
|
|
103
164
|
},
|
package/package.json
CHANGED