Extending MAHORAGA-Next
Code examples for common customizations.
For a full customization guide, see Strategies. 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",
timestamp: 1707500000000
}
Example: News API Integration
Write a standalone Gatherer module in your strategy directory:
// src/strategy/my-strategy/gatherers/news.ts
import type { Gatherer, StrategyContext } from "../../types";
import { extractTickers } from "../default/helpers/ticker";
import { calculateTimeDecay } from "../default/helpers/sentiment";
export const newsGatherer: Gatherer = {
name: "news_api",
gather: async (ctx: StrategyContext) => {
const signals = [];
try {
const res = await fetch("https://newsapi.example/finance", {
headers: { Authorization: `Bearer ${ctx.env.NEWS_API_KEY}` },
});
const data = await res.json();
for (const article of data.articles) {
const tickers = extractTickers(article.title + " " + article.description);
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,
raw_sentiment: sentiment,
volume: 1,
freshness: calculateTimeDecay(new Date(article.publishedAt).getTime() / 1000),
source_weight: 0.8,
reason: `News: ${article.title.slice(0, 60)}...`,
timestamp: Date.now(),
});
}
}
} catch (error) {
ctx.log("NewsAPI", "error", { message: String(error) });
}
return signals;
},
};
Example: Custom Exit Logic
Write a selectExits function that returns sell candidates:
// src/strategy/my-strategy/rules/exits.ts
import type { SellCandidate, StrategyContext } from "../../types";
import type { Account, Position } from "../../../core/types";
export function selectExits(
ctx: StrategyContext,
positions: Position[],
_account: Account
): SellCandidate[] {
const exits: SellCandidate[] = [];
for (const pos of positions) {
const entry = ctx.positionEntries[pos.symbol];
if (!entry) continue;
const plPct = ((pos.current_price - entry.entry_price) / entry.entry_price) * 100;
const peakPct = ((entry.peak_price - entry.entry_price) / entry.entry_price) * 100;
// Trailing stop: sell if up 5%+ but dropped 2% from peak
if (plPct > 5 && plPct < peakPct - 2) {
exits.push({ symbol: pos.symbol, reason: "Trailing stop triggered" });
}
}
return exits;
}
Example: Multi-Source Confirmation
Filter entries to only trade when multiple sources agree:
// In your selectEntries function
export function selectEntries(ctx, research, positions, account) {
// Group signals by symbol
const bySymbol = new Map();
for (const signal of ctx.signals) {
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]) => new Set(signals.map(s => s.source)).size >= 2)
.filter(([_, signals]) => {
const avg = signals.reduce((a, b) => a + b.sentiment, 0) / signals.length;
return avg >= 0.5;
});
// Return BuyCandidate[] for confirmed symbols...
}
Ideas for Extension
- News APIs — Benzinga, Polygon, Alpha Vantage
- SEC Filings — Insider trading, 13F filings
- Options Flow — Unusual options activity
- Earnings — Pre/post earnings strategies
- Sector Rotation — Track money flow between sectors
- Analyst Ratings — Track upgrades/downgrades
Tip: Start simple. Add one data source, test it thoroughly, then add more complexity.