Extending Makora

Code examples for common customizations.

For a full customization guide, see Harness. This page provides copy-paste examples.

Signal Format

Data sources return signals in this format:

{
  symbol: "AAPL",
  source: "my_source",
  source_detail: "my_source_v1",
  sentiment: 0.65,        // -1 to 1, weighted
  raw_sentiment: 0.72,    // Before source weight applied
  volume: 42,             // Mention count
  freshness: 0.9,         // Time decay factor
  source_weight: 0.9,     // How much to trust this source
  reason: "MySource: 42 mentions, 65% bullish"
}
      

Example: News API Integration

private async gatherNewsAPI(): Promise<Signal[]> {
  const signals: Signal[] = [];
  
  try {
    const res = await fetch("https://newsapi.example/finance", {
      headers: { "Authorization": `Bearer ${this.env.NEWS_API_KEY}` }
    });
    const data = await res.json();
    
    for (const article of data.articles) {
      // Extract tickers from headline
      const tickers = extractTickers(article.title + " " + article.description);
      
      // Simple sentiment from title keywords
      const sentiment = article.title.toLowerCase().includes("surge") ? 0.7
        : article.title.toLowerCase().includes("drop") ? -0.5
        : 0.1;
      
      for (const symbol of tickers) {
        signals.push({
          symbol,
          source: "news_api",
          source_detail: article.source.name,
          sentiment: sentiment * 0.8,  // Apply source weight
          raw_sentiment: sentiment,
          volume: 1,
          freshness: calculateTimeDecay(new Date(article.publishedAt).getTime()),
          source_weight: 0.8,
          reason: `News: ${article.title.slice(0, 60)}...`,
        });
      }
    }
  } catch (error) {
    this.log("NewsAPI", "error", { message: String(error) });
  }
  
  return signals;
}
      

Example: Custom Exit Logic

Add trailing stops or time-based exits in runAnalyst():

// Trailing stop: sell if we're up 5%+ but have dropped 2% from peak
const entry = this.state.positionEntries[pos.symbol];
if (entry) {
  const plPct = ((pos.current_price - entry.entry_price) / entry.entry_price) * 100;
  const peakPct = ((entry.peak_price - entry.entry_price) / entry.entry_price) * 100;
  
  if (plPct > 5 && plPct < peakPct - 2) {
    await this.executeSell(pos.symbol, pos.qty, "Trailing stop triggered");
  }
}
      

Example: Multi-Source Confirmation

Only trade when multiple sources agree:

// Group signals by symbol
const bySymbol = new Map<string, Signal[]>();
for (const signal of this.state.signalCache) {
  const arr = bySymbol.get(signal.symbol) || [];
  arr.push(signal);
  bySymbol.set(signal.symbol, arr);
}

// Only consider symbols with 2+ sources agreeing
const confirmed = [...bySymbol.entries()]
  .filter(([_, signals]) => {
    const sources = new Set(signals.map(s => s.source));
    return sources.size >= 2;
  })
  .filter(([_, signals]) => {
    const avgSentiment = signals.reduce((a, b) => a + b.sentiment, 0) / signals.length;
    return avgSentiment >= 0.5;
  });
      

Ideas for Extension

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