For educational purposes only. Trading involves substantial risk of loss. Not financial advice.

Extending Mahoraga

Add your own data sources and trading strategies.

Agent Structure

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                    │
└─────────────────────────────────────────────────────────────────┘
    

Adding a Data Source

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%)"
}
    

Example: Custom Data Source

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`
    }));
  }
}
    

Combining Data Sources

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);
}
    

Modifying Trading Logic

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
}
    

Using Technical Indicators

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);
}
    

Ideas for Extension

Tip: Start simple. Add one data source, test it thoroughly, then add more complexity.