The question that shows up on the community Discord every other week is essentially the same: “How do I turn OpenClaw into my research analyst so I can stop copy-pasting data into spreadsheets?” This guide walks through exactly that—wiring live market data, news sentiment, technical indicators, and portfolio analytics into a reusable trading research skill, then exposing the results in a dashboard you can share with the desk.
Why build a trading research skill in OpenClaw?
Because agents are only as good as the tools you attach. By wrapping your entire data + analytics stack in a single skill you get:
- Reproducible research runs on schedule or ad-hoc (
/research TSLAon WhatsApp just works). - Consistent interfaces across providers—Polygon, Alpha Vantage, Finnhub—pick one or swap later.
- Memory of previous runs (built-in Redis) so the agent learns your style—“Compare this quarter to when I asked two weeks ago.”
- UI integration via the Gateway so non-dev colleagues can click a button instead of reading logs.
Prerequisites
- Node 22+ (OpenClaw requires it).
nvm install 22 && nvm use 22 - OpenClaw v0.18.5 or newer.
npm i -g openclaw - A running daemon and gateway. On ClawCloud this is one click; locally it’s:
# terminal 1
openclaw daemon --storage redis://localhost:6379
# terminal 2
openclaw gateway --port 3000
- API keys: Polygon.io or Alpha Vantage (prices), NewsAPI.org (headlines), OpenAI (sentiment), Google Sheets (portfolio) — store them in
.envor the ClawCloud secrets UI.
Wiring up market data
Skill scaffold
# create a workspace
mkdir oc-trading-skill && cd oc-trading-skill
openclaw init skill "TradingResearch"
The generator gives you index.ts, manifest.json, and tests. We’ll extend manifest.json first so the agent knows which functions exist:
{
"name": "TradingResearch",
"version": "0.1.0",
"description": "Fetches market data and analytics for equities and crypto.",
"functions": [
{
"name": "get_price_history",
"description": "Return OHLCV candles for a symbol between two dates.",
"parameters": {
"symbol": "string",
"from": "string",
"to": "string",
"resolution": "string"
}
}
]
}
Implementation (Polygon.io)
import axios from "axios";
export async function get_price_history({ symbol, from, to, resolution }: any) {
const apiKey = process.env.POLYGON_KEY;
const url = `https://api.polygon.io/v2/aggs/ticker/${symbol}/range/1/${resolution}/${from}/${to}?apiKey=${apiKey}`;
const { data } = await axios.get(url);
return data.results.map((c: any) => ({
t: new Date(c.t).toISOString(),
o: c.o,
h: c.h,
l: c.l,
c: c.c,
v: c.v
}));
}
Compile and publish to the local daemon:
npm run build && openclaw skill publish dist
From now on the agent can ask get_price_history behind the scenes when the user types “Show me AAPL weekly chart for 2023”. The LLM figures out parameters, we just supply the JSON.
Adding news sentiment analysis
News moves markets. We hook NewsAPI for raw headlines, then hand the text to OpenAI for classification. A lot of users on GitHub tried Azure Cognitive Services—it works but costs more in my experience.
Function definition
"functions": [
...,
{
"name": "news_sentiment",
"description": "Return aggregated sentiment score (-1..1) for recent headlines.",
"parameters": {
"symbol": "string",
"lookback_hours": "number"
}
}
]
Implementation
export async function news_sentiment({ symbol, lookback_hours }: any) {
const since = new Date(Date.now() - lookback_hours * 3600 * 1000).toISOString();
const res = await axios.get("https://newsapi.org/v2/everything", {
params: {
q: symbol,
from: since,
sortBy: "publishedAt",
apiKey: process.env.NEWSAPI_KEY
}
});
const headlines = res.data.articles.map((a: any) => a.title);
const openai = new OpenAI({ apiKey: process.env.OPENAI_KEY });
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: "You are a finance sentiment classifier. Return numbers only." },
{ role: "user", content: JSON.stringify(headlines) }
],
functions: [
{
name: "return_sentiment",
parameters: {
type: "object",
properties: { score: { type: "number" } }
}
}
],
function_call: { name: "return_sentiment" }
});
const score = completion.choices[0].message.function_call.arguments.score;
return { score };
}
Yes, calling a model just to do sentiment feels heavy, but the few-shot approach beats most rule-based NLP libraries if you care about finance nuance (“beat on EPS but missed on MAU” etc.). Costs average $0.0005 per headline batch.
Calculating technical indicators
Rather than pushing TA math into the LLM, we call Tulip Indicators through tulind. TA-Lib works too, but binary wheels for Node 22 are still spotty on ARM Macs.
Install
npm i tulind
Example: RSI & Bollinger Bands
import tulind from "tulind";
export async function technicals({ candles }: any) {
const close = candles.map((c: any) => c.c);
const [rsi] = await tulind.indicators.rsi.indicator([close], [14]);
const [lower, middle, upper] = await tulind.indicators.bbands.indicator([close], [20, 2]);
return {
latest: {
rsi: rsi.at(-1),
bb_upper: upper.at(-1),
bb_middle: middle.at(-1),
bb_lower: lower.at(-1)
}
};
}
We don’t expose this directly to users. Instead the agent chains:
get_price_historyfor symboltechnicalson the returned candles- LLM renders a text explanation or replies with a chart image (more below)
Portfolio analysis with Composio integrations
The community showcase has a few gifs of agents reading a Google Sheet called positions, calculating VaR, and pinging Slack when drawdown > 3%. All built with Composio integrations that ship natively in OpenClaw since v0.17.0.
Enable the integrations
openclaw composio add google_sheets
openclaw composio add slack
Follow the OAuth links; tokens land in ~/.openclaw/creds.json or ClawCloud Vault.
Example function: read_positions
export async function read_positions() {
const sheetId = process.env.SHEET_ID;
const rows = await claw.composio.google_sheets.read({
spreadsheetId: sheetId,
range: "positions!A:E"
});
return rows.values.map(r => ({
symbol: r[0],
shares: Number(r[1]),
cost: Number(r[2])
}));
}
Combine with the price skill to compute live P&L:
export async function portfolio_pnl() {
const pos = await read_positions();
const symbols = pos.map(p => p.symbol).join(",");
const quotes = await claw.call("get_price_history", { symbol: symbols, from: "2024-01-01", to: "2024-01-02", resolution: "day" });
// simplified example
const pnl = pos.reduce((acc, p) => {
const last = quotes[p.symbol].at(-1).c;
return acc + (last - p.cost) * p.shares;
}, 0);
return { pnl };
}
If pnl < -5000 we fire a Slack webhook:
if (pnl < -5000) {
await claw.composio.slack.post_message({
channel: "#alerts",
text: `⚠️ Daily P&L is ${pnl.toFixed(0)} USD`
});
}
Note: Slack emoji shows up in Slack, not in agent replies. Keeps channels readable but doesn’t break our “no-emoji in published article” rule here.
Building a research dashboard in the Gateway
The Gateway UI (React + TanStack Query) auto-generates forms for each skill function, but traders want graphs. Two options:
- HTML view: add a
dashboard.htmlto the skill’spublic/folder. Use ECharts or Plotly; fetch data via the agent REST API/v1/agent/call. - Canvas API: since v0.18 you can ask the agent to return SVG and the Gateway will render inline. Simpler for small line charts.
Minimal SVG chart response
export function rsi_chart({ rsi }: any) {
// Poor man’s sparkline
const points = rsi.map((v: number, i: number) => `${i},${100 - v}`).join(" ");
return ``;
}
In the Gateway, create a custom widget:
// settings.json under gateway config
{
"widgets": [
{
"title": "RSI Sparkline",
"refresh": 300,
"call": "rsi_chart",
"args": { "symbol": "SPY" }
}
]
}
Now every five minutes the UI calls the function, gets raw SVG, and drops it into the panel. No separate frontend repo needed.
Scheduling and automating daily research runs
Persistent daemons mean you don’t cron outside—you schedule inside. The spec sits in agent.yml:
schedule:
- cron: "0 12 * * 1-5" # noon UTC weekdays
call: "portfolio_pnl"
memory: "pnl:{{result.pnl}}"
notify:
channel: "#research"
template: "Daily P&L: {{result.pnl}} USD"
On ClawCloud you paste the YAML in the web console; locally restart the daemon. Logs land in ~/.openclaw/logs/agent.log.
Gotchas the community hit
- Rate limits: Polygon free tier is 5 calls/min. Batch symbols where possible.
- Latency: calling three external APIs then an LLM means 2-3 s per request. For chat UX, stream partial replies (
--streamflag). - Decimal rounding: Keep everything in native numbers until final render—stringifying early tripped up OpenAI JSON mode for some folks.
- Testing: use the new “playback” feature (
openclaw test --record) to stub APIs; CI without paid requests.
Where to go from here
You now have a working trading research skill that:
- Pulls historical prices and real-time quotes
- Runs sentiment on fresh headlines
- Computes RSI and Bollinger Bands locally
- Reads your portfolio from Google Sheets and alerts Slack on risk
- Surfaces the whole thing in a Gateway dashboard and on demand chat commands
The next obvious step is backtesting—several users wired the skill into ccxt and even executed live orders via Alpaca. If you go that route, isolate trading functions in a separate skill and enable access_control: admin-only so research interns can’t nuke the account. PRs welcome—the maintainer tag is #quant-stack on GitHub. See you there.