@blockrun/franklin 3.2.4 → 3.3.0
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/commands.js +30 -1
- package/dist/agent/context.js +13 -0
- package/dist/agent/permissions.js +3 -3
- package/dist/banner.js +27 -40
- package/dist/commands/start.js +33 -2
- package/dist/events/bridge.d.ts +1 -0
- package/dist/events/bridge.js +24 -0
- package/dist/events/bus.d.ts +17 -0
- package/dist/events/bus.js +55 -0
- package/dist/events/types.d.ts +49 -0
- package/dist/events/types.js +8 -0
- package/dist/learnings/extractor.d.ts +16 -0
- package/dist/learnings/extractor.js +234 -0
- package/dist/learnings/index.d.ts +3 -0
- package/dist/learnings/index.js +2 -0
- package/dist/learnings/store.d.ts +15 -0
- package/dist/learnings/store.js +130 -0
- package/dist/learnings/types.d.ts +24 -0
- package/dist/learnings/types.js +7 -0
- package/dist/narrative/state.d.ts +30 -0
- package/dist/narrative/state.js +69 -0
- package/dist/social/browser-pool.d.ts +29 -0
- package/dist/social/browser-pool.js +57 -0
- package/dist/social/preflight.d.ts +14 -0
- package/dist/social/preflight.js +26 -0
- package/dist/social/x.d.ts +8 -0
- package/dist/social/x.js +9 -1
- package/dist/tools/index.js +7 -0
- package/dist/tools/posttox.d.ts +7 -0
- package/dist/tools/posttox.js +137 -0
- package/dist/tools/searchx.d.ts +7 -0
- package/dist/tools/searchx.js +111 -0
- package/dist/tools/trading.d.ts +3 -0
- package/dist/tools/trading.js +168 -0
- package/dist/trading/config.d.ts +23 -0
- package/dist/trading/config.js +45 -0
- package/dist/trading/data.d.ts +30 -0
- package/dist/trading/data.js +112 -0
- package/dist/trading/metrics.d.ts +29 -0
- package/dist/trading/metrics.js +105 -0
- package/package.json +1 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const BASE = "https://api.coingecko.com/api/v3";
|
|
2
|
+
const UA = "franklin/3.3.0 (trading)";
|
|
3
|
+
const TIMEOUT = 10_000;
|
|
4
|
+
const TICKER_MAP = {
|
|
5
|
+
BTC: "bitcoin", ETH: "ethereum", SOL: "solana", BNB: "binancecoin", XRP: "ripple",
|
|
6
|
+
ADA: "cardano", DOGE: "dogecoin", AVAX: "avalanche-2", DOT: "polkadot", MATIC: "matic-network",
|
|
7
|
+
LINK: "chainlink", UNI: "uniswap", ATOM: "cosmos", LTC: "litecoin", NEAR: "near",
|
|
8
|
+
APT: "aptos", ARB: "arbitrum", OP: "optimism", SUI: "sui", SEI: "sei-network",
|
|
9
|
+
FIL: "filecoin", AAVE: "aave", MKR: "maker", SNX: "synthetix-network-token",
|
|
10
|
+
COMP: "compound-governance-token", INJ: "injective-protocol", TIA: "celestia",
|
|
11
|
+
PEPE: "pepe", WIF: "dogwifcoin", RENDER: "render-token",
|
|
12
|
+
};
|
|
13
|
+
const cache = new Map();
|
|
14
|
+
function cached(key, ttlMs, fn) {
|
|
15
|
+
const hit = cache.get(key);
|
|
16
|
+
if (hit && hit.expiry > Date.now())
|
|
17
|
+
return Promise.resolve(hit.data);
|
|
18
|
+
return fn().then(data => {
|
|
19
|
+
cache.set(key, { data, expiry: Date.now() + ttlMs });
|
|
20
|
+
return data;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const TTL_PRICE = 5 * 60_000;
|
|
24
|
+
const TTL_OHLCV = 60 * 60_000;
|
|
25
|
+
const TTL_TRENDING = 15 * 60_000;
|
|
26
|
+
// Fetch helper
|
|
27
|
+
async function geckofetch(path) {
|
|
28
|
+
const ctrl = new AbortController();
|
|
29
|
+
const timer = setTimeout(() => ctrl.abort(), TIMEOUT);
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch(`${BASE}${path}`, {
|
|
32
|
+
headers: { "User-Agent": UA },
|
|
33
|
+
signal: ctrl.signal,
|
|
34
|
+
});
|
|
35
|
+
if (res.status === 429)
|
|
36
|
+
return "rate-limited: CoinGecko 429 — retry later";
|
|
37
|
+
if (!res.ok)
|
|
38
|
+
return `CoinGecko error ${res.status}`;
|
|
39
|
+
return await res.json();
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
if (e instanceof DOMException && e.name === "AbortError")
|
|
43
|
+
return "request timed out";
|
|
44
|
+
return String(e);
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function resolveId(ticker) {
|
|
51
|
+
return TICKER_MAP[ticker.toUpperCase()] ?? ticker.toLowerCase();
|
|
52
|
+
}
|
|
53
|
+
export async function getPrice(ticker) {
|
|
54
|
+
const id = resolveId(ticker);
|
|
55
|
+
return cached(`price:${id}`, TTL_PRICE, async () => {
|
|
56
|
+
const raw = await geckofetch(`/simple/price?ids=${id}&vs_currencies=usd&include_24hr_change=true&include_market_cap=true&include_24hr_vol=true`);
|
|
57
|
+
if (typeof raw === "string")
|
|
58
|
+
return raw;
|
|
59
|
+
const d = raw[id];
|
|
60
|
+
if (!d)
|
|
61
|
+
return `no data for ${ticker}`;
|
|
62
|
+
return {
|
|
63
|
+
price: d.usd,
|
|
64
|
+
change24h: d.usd_24h_change,
|
|
65
|
+
volume24h: d.usd_24h_vol,
|
|
66
|
+
marketCap: d.usd_market_cap,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
export async function getOHLCV(ticker, days = 30) {
|
|
71
|
+
const id = resolveId(ticker);
|
|
72
|
+
return cached(`ohlcv:${id}:${days}`, TTL_OHLCV, async () => {
|
|
73
|
+
const raw = await geckofetch(`/coins/${id}/market_chart?vs_currency=usd&days=${days}&interval=daily`);
|
|
74
|
+
if (typeof raw === "string")
|
|
75
|
+
return raw;
|
|
76
|
+
const prices = raw.prices;
|
|
77
|
+
return {
|
|
78
|
+
timestamps: prices.map(p => p[0]),
|
|
79
|
+
closes: prices.map(p => p[1]),
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
export async function getTrending() {
|
|
84
|
+
return cached("trending", TTL_TRENDING, async () => {
|
|
85
|
+
const raw = await geckofetch("/search/trending");
|
|
86
|
+
if (typeof raw === "string")
|
|
87
|
+
return raw;
|
|
88
|
+
const coins = raw.coins;
|
|
89
|
+
return coins.map(c => ({
|
|
90
|
+
id: c.item.id,
|
|
91
|
+
name: c.item.name,
|
|
92
|
+
symbol: c.item.symbol,
|
|
93
|
+
marketCapRank: c.item.market_cap_rank,
|
|
94
|
+
}));
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
export async function getMarketOverview() {
|
|
98
|
+
return cached("markets", TTL_TRENDING, async () => {
|
|
99
|
+
const raw = await geckofetch("/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=20&page=1");
|
|
100
|
+
if (typeof raw === "string")
|
|
101
|
+
return raw;
|
|
102
|
+
return raw.map(c => ({
|
|
103
|
+
id: c.id,
|
|
104
|
+
symbol: c.symbol,
|
|
105
|
+
name: c.name,
|
|
106
|
+
price: c.current_price,
|
|
107
|
+
change24h: c.price_change_percentage_24h,
|
|
108
|
+
marketCap: c.market_cap,
|
|
109
|
+
volume24h: c.total_volume,
|
|
110
|
+
}));
|
|
111
|
+
});
|
|
112
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface RSIResult {
|
|
2
|
+
value: number;
|
|
3
|
+
values: number[];
|
|
4
|
+
interpretation: 'oversold' | 'neutral' | 'overbought';
|
|
5
|
+
}
|
|
6
|
+
export interface MACDResult {
|
|
7
|
+
macd: number;
|
|
8
|
+
signal: number;
|
|
9
|
+
histogram: number;
|
|
10
|
+
trend: 'bullish' | 'bearish' | 'neutral';
|
|
11
|
+
}
|
|
12
|
+
export interface BollingerResult {
|
|
13
|
+
upper: number;
|
|
14
|
+
middle: number;
|
|
15
|
+
lower: number;
|
|
16
|
+
bandwidth: number;
|
|
17
|
+
position: 'above' | 'within' | 'below';
|
|
18
|
+
}
|
|
19
|
+
export interface VolatilityResult {
|
|
20
|
+
daily: number;
|
|
21
|
+
annualized: number;
|
|
22
|
+
interpretation: 'low' | 'medium' | 'high';
|
|
23
|
+
}
|
|
24
|
+
export declare function sma(data: number[], period: number): number;
|
|
25
|
+
export declare function ema(closes: number[], period: number): number[];
|
|
26
|
+
export declare function rsi(closes: number[], period?: number): RSIResult;
|
|
27
|
+
export declare function macd(closes: number[], fast?: number, slow?: number, signal?: number): MACDResult;
|
|
28
|
+
export declare function bollingerBands(closes: number[], period?: number, stdDev?: number): BollingerResult;
|
|
29
|
+
export declare function volatility(closes: number[], period?: number): VolatilityResult;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export function sma(data, period) {
|
|
2
|
+
if (data.length < period)
|
|
3
|
+
return NaN;
|
|
4
|
+
const slice = data.slice(data.length - period);
|
|
5
|
+
return slice.reduce((sum, v) => sum + v, 0) / period;
|
|
6
|
+
}
|
|
7
|
+
export function ema(closes, period) {
|
|
8
|
+
const result = new Array(closes.length).fill(NaN);
|
|
9
|
+
if (closes.length < period)
|
|
10
|
+
return result;
|
|
11
|
+
let sum = 0;
|
|
12
|
+
for (let i = 0; i < period; i++)
|
|
13
|
+
sum += closes[i];
|
|
14
|
+
result[period - 1] = sum / period;
|
|
15
|
+
const k = 2 / (period + 1);
|
|
16
|
+
for (let i = period; i < closes.length; i++) {
|
|
17
|
+
result[i] = closes[i] * k + result[i - 1] * (1 - k);
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
export function rsi(closes, period = 14) {
|
|
22
|
+
const values = new Array(closes.length).fill(NaN);
|
|
23
|
+
if (closes.length < period + 1) {
|
|
24
|
+
return { value: NaN, values, interpretation: 'neutral' };
|
|
25
|
+
}
|
|
26
|
+
const gains = [];
|
|
27
|
+
const losses = [];
|
|
28
|
+
for (let i = 1; i < closes.length; i++) {
|
|
29
|
+
const diff = closes[i] - closes[i - 1];
|
|
30
|
+
gains.push(diff > 0 ? diff : 0);
|
|
31
|
+
losses.push(diff < 0 ? -diff : 0);
|
|
32
|
+
}
|
|
33
|
+
let avgGain = gains.slice(0, period).reduce((s, v) => s + v, 0) / period;
|
|
34
|
+
let avgLoss = losses.slice(0, period).reduce((s, v) => s + v, 0) / period;
|
|
35
|
+
const computeRSI = (ag, al) => al === 0 ? 100 : 100 - 100 / (1 + ag / al);
|
|
36
|
+
values[period] = computeRSI(avgGain, avgLoss);
|
|
37
|
+
for (let i = period; i < gains.length; i++) {
|
|
38
|
+
avgGain = (avgGain * (period - 1) + gains[i]) / period;
|
|
39
|
+
avgLoss = (avgLoss * (period - 1) + losses[i]) / period;
|
|
40
|
+
values[i + 1] = computeRSI(avgGain, avgLoss);
|
|
41
|
+
}
|
|
42
|
+
const latest = values[values.length - 1];
|
|
43
|
+
const interpretation = latest < 30 ? 'oversold' : latest > 70 ? 'overbought' : 'neutral';
|
|
44
|
+
return { value: latest, values, interpretation };
|
|
45
|
+
}
|
|
46
|
+
export function macd(closes, fast = 12, slow = 26, signal = 9) {
|
|
47
|
+
const emaFast = ema(closes, fast);
|
|
48
|
+
const emaSlow = ema(closes, slow);
|
|
49
|
+
const macdLine = closes.map((_, i) => isNaN(emaFast[i]) || isNaN(emaSlow[i]) ? NaN : emaFast[i] - emaSlow[i]);
|
|
50
|
+
const validMacd = macdLine.filter((v) => !isNaN(v));
|
|
51
|
+
const signalLine = ema(validMacd, signal);
|
|
52
|
+
const padded = new Array(macdLine.length - validMacd.length)
|
|
53
|
+
.fill(NaN)
|
|
54
|
+
.concat(signalLine);
|
|
55
|
+
const histogram = macdLine.map((v, i) => isNaN(v) || isNaN(padded[i]) ? NaN : v - padded[i]);
|
|
56
|
+
const last = macdLine[macdLine.length - 1];
|
|
57
|
+
const lastSignal = padded[padded.length - 1];
|
|
58
|
+
const lastHist = histogram[histogram.length - 1];
|
|
59
|
+
const prevHist = histogram[histogram.length - 2];
|
|
60
|
+
let trend = 'neutral';
|
|
61
|
+
if (!isNaN(lastHist) && !isNaN(prevHist)) {
|
|
62
|
+
if (lastHist > 0 && lastHist > prevHist)
|
|
63
|
+
trend = 'bullish';
|
|
64
|
+
else if (lastHist < 0 && lastHist < prevHist)
|
|
65
|
+
trend = 'bearish';
|
|
66
|
+
}
|
|
67
|
+
return { macd: last, signal: lastSignal, histogram: lastHist, trend };
|
|
68
|
+
}
|
|
69
|
+
export function bollingerBands(closes, period = 20, stdDev = 2) {
|
|
70
|
+
if (closes.length < period) {
|
|
71
|
+
return {
|
|
72
|
+
upper: NaN,
|
|
73
|
+
middle: NaN,
|
|
74
|
+
lower: NaN,
|
|
75
|
+
bandwidth: NaN,
|
|
76
|
+
position: 'within',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const slice = closes.slice(closes.length - period);
|
|
80
|
+
const middle = slice.reduce((s, v) => s + v, 0) / period;
|
|
81
|
+
const variance = slice.reduce((s, v) => s + (v - middle) ** 2, 0) / period;
|
|
82
|
+
const sigma = Math.sqrt(variance);
|
|
83
|
+
const upper = middle + stdDev * sigma;
|
|
84
|
+
const lower = middle - stdDev * sigma;
|
|
85
|
+
const bandwidth = (upper - lower) / middle;
|
|
86
|
+
const price = closes[closes.length - 1];
|
|
87
|
+
const position = price > upper ? 'above' : price < lower ? 'below' : 'within';
|
|
88
|
+
return { upper, middle, lower, bandwidth, position };
|
|
89
|
+
}
|
|
90
|
+
export function volatility(closes, period = 14) {
|
|
91
|
+
if (closes.length < period + 1) {
|
|
92
|
+
return { daily: NaN, annualized: NaN, interpretation: 'medium' };
|
|
93
|
+
}
|
|
94
|
+
const returns = [];
|
|
95
|
+
const start = closes.length - period - 1;
|
|
96
|
+
for (let i = start + 1; i < closes.length; i++) {
|
|
97
|
+
returns.push(Math.log(closes[i] / closes[i - 1]));
|
|
98
|
+
}
|
|
99
|
+
const mean = returns.reduce((s, v) => s + v, 0) / returns.length;
|
|
100
|
+
const variance = returns.reduce((s, v) => s + (v - mean) ** 2, 0) / (returns.length - 1);
|
|
101
|
+
const daily = Math.sqrt(variance);
|
|
102
|
+
const annualized = daily * Math.sqrt(365);
|
|
103
|
+
const interpretation = annualized < 0.3 ? 'low' : annualized > 0.8 ? 'high' : 'medium';
|
|
104
|
+
return { daily, annualized, interpretation };
|
|
105
|
+
}
|
package/package.json
CHANGED