Add your own data sources and trading strategies.
The agent file (agent-v1.mjs) has three clearly marked sections:
┌─────────────────────────────────────────────────────────────────┐
│ SECTION 1: DATA SOURCE (customize this) │
│ - StockTwitsAgent class │
│ - Add your own: news APIs, SEC filings, etc. │
├─────────────────────────────────────────────────────────────────┤
│ SECTION 2: TRADING STRATEGY (customize this) │
│ - runTradingLogic() method │
│ - Change buy/sell rules, add technical indicators, etc. │
├─────────────────────────────────────────────────────────────────┤
│ SECTION 3: HARNESS (probably don't touch) │
│ - MCP connection, execution, dashboard API │
│ - Modify only if you know what you're doing │
└─────────────────────────────────────────────────────────────────┘
Create a new class that returns signals in this format:
{
symbol: "AAPL",
source: "my-source",
sentiment: 0.65, // -1 to 1
volume: 42, // message/mention count
bullish: 30,
bearish: 12,
reason: "My Source: 30B/12b (65%)"
}
class CustomDataAgent {
async gatherSignals() {
const signals = [];
// Fetch from your data source
const res = await fetch("https://your-api.com/signals");
const data = await res.json();
for (const item of data.items) {
const sentiment = this.analyzeSentiment(item);
signals.push({
symbol: item.ticker,
source: "custom",
sentiment,
volume: item.mentions,
bullish: sentiment > 0 ? 1 : 0,
bearish: sentiment < 0 ? 1 : 0,
reason: `Custom: ${item.summary.slice(0, 50)}...`
});
}
return this.aggregateSignals(signals);
}
analyzeSentiment(item) {
// Your sentiment logic here
// Return value between -1 and 1
return item.score || 0;
}
aggregateSignals(signals) {
// Group by symbol and average sentiment
const grouped = {};
for (const s of signals) {
if (!grouped[s.symbol]) grouped[s.symbol] = [];
grouped[s.symbol].push(s);
}
return Object.entries(grouped).map(([symbol, arr]) => ({
symbol,
source: "custom",
sentiment: arr.reduce((a, b) => a + b.sentiment, 0) / arr.length,
volume: arr.reduce((a, b) => a + b.volume, 0),
bullish: arr.filter(s => s.sentiment > 0).length,
bearish: arr.filter(s => s.sentiment < 0).length,
reason: `Custom: ${arr.length} signals`
}));
}
}
Merge signals from multiple sources for confirmation:
async gatherAllSignals() {
const [source1, source2] = await Promise.all([
this.source1Agent.gatherSignals(),
this.source2Agent.gatherSignals()
]);
// Merge by symbol
const merged = {};
for (const s of [...source1, ...source2]) {
if (!merged[s.symbol]) {
merged[s.symbol] = { ...s, sources: [s.source] };
} else {
// Average sentiment, sum volume
merged[s.symbol].sentiment =
(merged[s.symbol].sentiment + s.sentiment) / 2;
merged[s.symbol].volume += s.volume;
merged[s.symbol].sources.push(s.source);
}
}
return Object.values(merged);
}
Edit the runTradingLogic() method:
async runTradingLogic() {
// Get current state
const [account, positions] = await Promise.all([
this.mcp.call("accounts-get"),
this.mcp.call("positions-list")
]);
// Your custom exit logic
for (const pos of positions) {
const plPct = pos.unrealized_plpc * 100;
// Example: Trailing stop
if (plPct > 5 && plPct < this.peakPnL[pos.symbol] - 2) {
await this.sell(pos.symbol, "Trailing stop");
}
// Example: Time-based exit
const holdHours = (Date.now() - this.entryTime[pos.symbol]) / 3600000;
if (holdHours > 24 && plPct < 2) {
await this.sell(pos.symbol, "Stale position");
}
}
// Your custom entry logic
const candidates = this.signals
.filter(s => s.sources.length >= 2) // Multi-source confirmation
.filter(s => s.sentiment >= 0.5)
.sort((a, b) => b.sentiment - a.sentiment);
// ... execute buys
}
The MCP server provides technical analysis:
const technicals = await this.mcp.call("technicals-get", {
symbol: "AAPL",
indicators: ["rsi", "macd", "sma_20", "sma_50"]
});
// technicals = {
// rsi: 65.4,
// macd: { value: 1.2, signal: 0.8, histogram: 0.4 },
// sma_20: 175.50,
// sma_50: 172.30
// }
// Example: Only buy if RSI < 70 and price above SMA
if (technicals.rsi < 70 && price > technicals.sma_20) {
await this.buy(symbol);
}