feat: implement dual-pipeline signal engine service
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/push/build-2 Pipeline was successful
ci/woodpecker/push/build-1 Pipeline was successful
ci/woodpecker/push/build-3 Pipeline was successful
ci/woodpecker/push/finalize Pipeline was successful
Build and Push / lint-and-test (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.adapters.broker_adapter name:broker-adapter]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.aggregation.worker name:aggregation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.extractor.worker name:extractor]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.ingestion.worker name:ingestion]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.lake_publisher.worker name:lake-publisher]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.parser.worker name:parser]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.recommendation.worker name:recommendation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.scheduler.app name:scheduler]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.api.app:app --host 0.0.0.0 --port 8000 name:query-api]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.risk.app:app --host 0.0.0.0 --port 8000 name:risk]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.symbol_registry.app:app --host 0.0.0.0 --port 8000 name:symbol-registry]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.trading.app:app --host 0.0.0.0 --port 8000 name:trading-engine]) (push) Has been cancelled
Build and Push / build-dashboard (push) Has been cancelled
Build and Push / build-superset (push) Has been cancelled
Build and Push / integration-test (push) Has been cancelled
Build and Push / beta-gate (push) Has been cancelled

New service at services/signal_engine/ implementing concurrent heuristic
(deterministic scoring) and probabilistic (Bayesian inference) pipelines
that evaluate technical signals across 6 timeframes (M30-M) and produce
independent BUY/WATCH/SKIP verdicts per ticker per evaluation tick.

Components:
- Input Normalizer: multi-source data assembly with sentinel fallbacks
- Signal Library: Fibonacci, MA Stack, RSI, Cup & Handle, Elliott Wave
- Multi-Timeframe Confluence Engine: weighted scoring with D/W/M anchors
- Hard Filter Engine: macro_bias, valuation, earnings proximity gating
- Heuristic Pipeline: S_total scoring with confidence-gated verdicts
- Probabilistic Pipeline: Bayesian log-odds with regime priors, entropy
  gating, EV_R calculation, and signal correlation penalty
- Exit Engine: stop-loss, targets, trailing ATR-based stops
- Delta Analyzer: pipeline agreement tracking with rolling Redis metrics
- Output Formatter: SignalOutput contract + Recommendation schema mapping
- Worker orchestrator: concurrent pipelines with failure isolation
- Main entry point: queue polling with fail-safe config loading

Infrastructure:
- Migration 039: signal_engine_outputs table with 3 indexes
- Helm chart: signalEngine service entry (processing tier)
- Redis key: QUEUE_SIGNAL_ENGINE constant

Tests: 390 tests (unit + property-based) covering all components
Config: dual_pipeline_enabled=false by default (safe rollout)
This commit is contained in:
Celes Renata
2026-05-02 07:32:26 +00:00
parent 7e2343ec2c
commit f468e30af0
61 changed files with 14107 additions and 184 deletions
+148
View File
@@ -0,0 +1,148 @@
#!/usr/bin/env node
/**
* Minimal MCP server for OpenAI chat completions.
* Accepts ANY model string (gpt-5.2, gpt-5.4, etc.) — no hardcoded enum.
* Communicates over stdio using JSON-RPC (MCP protocol).
*/
import { createInterface } from "readline";
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
if (!OPENAI_API_KEY) {
process.stderr.write("ERROR: OPENAI_API_KEY environment variable is required\n");
process.exit(1);
}
const SERVER_INFO = {
name: "openai-chat",
version: "1.0.0",
};
const TOOLS = [
{
name: "openai_chat",
description:
"Send messages to OpenAI chat completions API. Supports all OpenAI models including GPT-5.x series.",
inputSchema: {
type: "object",
properties: {
model: {
type: "string",
description:
"OpenAI model name (e.g. gpt-5.2, gpt-5.4, gpt-4o, etc.)",
default: "gpt-5.2",
},
messages: {
type: "array",
description: "Array of chat messages",
items: {
type: "object",
properties: {
role: {
type: "string",
enum: ["system", "user", "assistant"],
},
content: { type: "string" },
},
required: ["role", "content"],
},
},
},
required: ["messages"],
},
},
];
async function callOpenAI(model, messages) {
const resp = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${OPENAI_API_KEY}`,
},
body: JSON.stringify({ model, messages }),
});
if (!resp.ok) {
const errText = await resp.text();
throw new Error(`OpenAI API error ${resp.status}: ${errText}`);
}
const data = await resp.json();
return data.choices?.[0]?.message?.content ?? "(no response)";
}
function jsonRpcResponse(id, result) {
return JSON.stringify({ jsonrpc: "2.0", id, result });
}
function jsonRpcError(id, code, message) {
return JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } });
}
async function handleRequest(req) {
const { id, method, params } = req;
switch (method) {
case "initialize":
return jsonRpcResponse(id, {
protocolVersion: "2024-11-05",
capabilities: { tools: {} },
serverInfo: SERVER_INFO,
});
case "notifications/initialized":
return null; // no response needed for notifications
case "tools/list":
return jsonRpcResponse(id, { tools: TOOLS });
case "tools/call": {
const toolName = params?.name;
if (toolName !== "openai_chat") {
return jsonRpcError(id, -32602, `Unknown tool: ${toolName}`);
}
const args = params?.arguments ?? {};
const model = args.model || "gpt-5.2";
const messages = args.messages || [];
if (!messages.length) {
return jsonRpcError(id, -32602, "messages array is required");
}
try {
const content = await callOpenAI(model, messages);
return jsonRpcResponse(id, {
content: [{ type: "text", text: content }],
});
} catch (err) {
return jsonRpcResponse(id, {
content: [{ type: "text", text: `Error: ${err.message}` }],
isError: true,
});
}
}
case "ping":
return jsonRpcResponse(id, {});
default:
if (method?.startsWith("notifications/")) return null;
return jsonRpcError(id, -32601, `Method not found: ${method}`);
}
}
// stdio transport
const rl = createInterface({ input: process.stdin });
rl.on("line", async (line) => {
try {
const req = JSON.parse(line);
const resp = await handleRequest(req);
if (resp) {
process.stdout.write(resp + "\n");
}
} catch (err) {
process.stderr.write(`Parse error: ${err.message}\n`);
}
});