Link to the source code
LLM-driven trading strategy with real-time execution and automated signal generation using backtest-kit framework. Suitable for both paper trading and real money trading.
Demonstrates AI-powered live trading strategy capabilities for:
Multi-Timeframe Analysis: 4-level market structure analysis
LLM Integration: Ollama-powered signal generation (deepseek-v3.1:671b)
Structured Output: JSON schema validation for trading signals
Debug Logging: Automatic conversation and signal dumping via dumpSignal()
Live Monitoring: Real-time tick monitoring with background execution
Partial Profit/Loss: Scale out positions at predefined profit/loss levels
Scheduled Orders: Support for limit orders with automatic activation
Event Listeners: Real-time notifications for signals, partial levels, and errors
This demo supports two trading modes:
Important: Always start with paper trading to validate strategy performance before risking real capital.
demo/live/
├── src/
│ ├── index.mjs # Main live trading configuration
│ └── utils/
│ ├── json.mjs # LLM API client (Ollama)
│ └── messages.mjs # Multi-timeframe message builder
├── package.json # Dependencies and scripts
└── README.md # This file
# Navigate to project directory
cd demo/live
# Install dependencies
npm install
# Set environment variables
export OLLAMA_API_KEY=your_ollama_api_key
# Run live trading
npm start
Create a .env file or set environment variables:
OLLAMA_API_KEY=your_ollama_api_key
The demo is pre-configured for BTCUSDT with:
LLM follows strict rules for signal generation:
Position Types:
position='wait': No clear signal, wait for better conditionsposition='long': Bullish signal, price expected to riseposition='short': Bearish signal, price expected to fallEntry Price (priceOpen):
Exit Levels:
priceTakeProfit > priceOpen > priceStopLosspriceStopLoss > priceOpen > priceTakeProfitTime Estimate (minuteEstimatedTime):
The demo implements scaling out at predefined levels:
Profit Levels (defined in Constant):
TP_LEVEL3 (25% profit): Close 33% of positionTP_LEVEL2 (50% profit): Close 33% of positionTP_LEVEL1 (100% profit): Close remaining 34%Loss Levels:
SL_LEVEL2 (-50% loss): Close 50% of positionSL_LEVEL1 (-100% loss): Close remaining 50%Run live trading:
npm start
Output:
{ action: 'idle', signal: null, strategyName: 'test_strategy', ... }
{ action: 'opened', signal: { id: '...', position: 'long', ... }, ... }
BTCUSDT reached 25% profit at 51250
Close 33% at 25% profit
BTCUSDT reached 50% profit at 51500
Close 33% at 50% profit
{ action: 'closed', signal: { ... }, pnl: { pnlPercentage: 2.0 }, ... }
Backtest report saved: ./dump/backtest/test_strategy.md
Partial profit/loss report saved: ./dump/partial/BTCUSDT_test_strategy.md
Generated files:
./dump/backtest/test_strategy.md - Live trading performance report./dump/partial/BTCUSDT_test_strategy.md - Partial profit/loss events./dump/schedule/test_strategy.md - Scheduled orders report./dump/strategy/{uuid}/ - LLM conversation logs (one per signal)After trading session, check performance report:
cat ./dump/backtest/test_strategy.md
Example output:
# Backtest Report: test_strategy
| Signal ID | Symbol | Position | Open Price | Close Price | PNL (net) | Close Reason |
|-----------|----------|----------|------------|-------------|-----------|--------------|
| signal-1 | BTCUSDT | LONG | 50000 USD | 51000 USD | +2.00% | take_profit |
| signal-2 | BTCUSDT | SHORT | 51500 USD | 50500 USD | +1.94% | take_profit |
**Total signals:** 15
**Win rate:** 73.33% (11W / 4L)
**Average PNL:** +0.85%
**Sharpe Ratio:** 1.234
Check partial profit/loss tracking:
cat ./dump/partial/BTCUSDT_test_strategy.md
Example output:
# Partial Profit/Loss Report: BTCUSDT:test_strategy
| Action | Symbol | Strategy | Signal ID | Position | Level % | Current Price | Timestamp | Mode |
|--------|--------|----------|-----------|----------|---------|---------------|-----------|------|
| PROFIT | BTCUSDT | test_strategy | abc123 | LONG | +25% | 51250.00000000 USD | 2025-12-01T10:30:00.000Z | Live |
| PROFIT | BTCUSDT | test_strategy | abc123 | LONG | +50% | 51500.00000000 USD | 2025-12-01T11:00:00.000Z | Live |
**Total events:** 2
**Profit events:** 2
**Loss events:** 0
Check scheduled order tracking:
cat ./dump/schedule/test_strategy.md
This shows all limit orders that were scheduled and their activation/cancellation status.
The demo includes three key event listeners:
listenSignalLive)Monitors all signal lifecycle events:
listenSignalLive(async (event) => {
if (event.action === "closed") {
// Generate reports when position closes
await Live.dump(event.strategyName);
await Partial.dump(event.symbol, event.strategyName);
}
if (event.action === "scheduled") {
// Track scheduled limit orders
await Schedule.dump(event.strategyName);
}
if (event.action === "cancelled") {
// Track cancelled limit orders
await Schedule.dump(event.strategyName);
}
console.log(event); // Log all events
});
Events emitted:
action: 'idle' - No active positionaction: 'opened' - Position opened at market/limit priceaction: 'active' - Position being monitoredaction: 'scheduled' - Limit order created, waiting for activationaction: 'cancelled' - Scheduled order cancelled without executionaction: 'closed' - Position closed (TP/SL/timeout)listenPartialProfit)Tracks profit milestones for scaling out:
listenPartialProfit(({ symbol, price, level }) => {
console.log(`${symbol} reached ${level}% profit at ${price}`);
if (level === Constant.TP_LEVEL3) {
console.log("Close 33% at 25% profit");
// Execute partial close via exchange API
}
if (level === Constant.TP_LEVEL2) {
console.log("Close 33% at 50% profit");
// Execute partial close via exchange API
}
if (level === Constant.TP_LEVEL1) {
console.log("Close 34% at 100% profit");
// Execute final close via exchange API
}
});
Use cases:
listenPartialLoss)Tracks loss milestones for risk management:
listenPartialLoss(({ symbol, price, level }) => {
console.log(`${symbol} reached -${level}% loss at ${price}`);
if (level === Constant.SL_LEVEL2) {
console.log("Close 50% at -50% loss");
// Execute partial stop loss via exchange API
}
if (level === Constant.SL_LEVEL1) {
console.log("Close 50% at -100% loss");
// Execute final stop loss via exchange API
}
});
Use cases:
listenError)Catches all errors for debugging and monitoring:
listenError((error) => {
console.error("Error occurred:", error);
// Send notification, log to monitoring system, etc.
});
Error types:
Each signal generates debug logs in ./dump/strategy/{uuid}/:
./dump/strategy/a1b2c3d4-e5f6-7890-abcd-ef1234567890/
├── 00_system_prompt.md # System instructions
├── 01_user_message.md # 1h candles analysis
├── 02_assistant_message.md # LLM acknowledgment
├── 03_user_message.md # 15m candles analysis
├── 04_assistant_message.md # LLM acknowledgment
├── 05_user_message.md # 5m candles analysis
├── 06_assistant_message.md # LLM acknowledgment
├── 07_user_message.md # 1m candles analysis
├── 08_assistant_message.md # LLM acknowledgment
├── 09_user_message.md # Signal generation request
└── 10_llm_output.md # Final signal with JSON
Modify src/index.mjs to trade different cryptocurrencies:
Live.background("ETHUSDT", { // Change symbol
strategyName: "test_strategy",
exchangeName: "test_exchange",
frameName: "test_frame",
});
Edit trading period in src/index.mjs (for replay/testing mode):
addFrame({
frameName: "test_frame",
interval: "1m",
startDate: new Date("2025-12-15T00:00:00.000Z"),
endDate: new Date("2025-12-15T23:59:59.000Z"),
});
Note: For true live trading, remove date constraints and let it run continuously.
Live.background() runs in a loop:
For each signal generation (every 5 minutes):
Creates structured conversation:
LLM analyzes all timeframes and returns:
{
"position": "long",
"note": "Strong bullish momentum on 1h, breakout on 5m...",
"priceOpen": 50000,
"priceTakeProfit": 51000,
"priceStopLoss": 49000,
"minuteEstimatedTime": 120
}
Framework validates:
dumpSignal() saves:
./dump/strategy/{uuid}/ directoryTwo execution paths:
Path A: Immediate Execution (if priceOpen equals current price):
action: 'opened' eventPath B: Scheduled Execution (if priceOpen is limit price):
action: 'scheduled' eventpriceOpenaction: 'opened'action: 'cancelled'While position is active:
Position closes when:
priceTakeProfitpriceStopLossminuteEstimatedTime elapsed without TP/SLClosure process:
action: 'closed' eventAfter each closure:
./dump/backtest/{strategyName}.md)./dump/partial/{symbol}_{strategyName}.md)./dump/schedule/{strategyName}.md){
role: "system",
content: `
Проанализируй торговую стратегию и верни торговый сигнал.
ПРАВИЛА ОТКРЫТИЯ ПОЗИЦИЙ:
1. ТИПЫ ПОЗИЦИЙ:
- position='wait': нет четкого сигнала
- position='long': бычий сигнал
- position='short': медвежий сигнал
2. ЦЕНА ВХОДА (priceOpen):
- Текущая рыночная или отложенная цена
- Обоснование по техническому анализу
3. УРОВНИ ВЫХОДА:
- LONG: priceTakeProfit > priceOpen > priceStopLoss
- SHORT: priceStopLoss > priceOpen > priceTakeProfit
- На основе Fibonacci, S/R, Bollinger
4. ВРЕМЕННЫЕ РАМКИ:
- minuteEstimatedTime: макс 360 минут
- Расчет по ATR, ADX, MACD, Momentum
`
}
This encourages:
Final request emphasizes caution:
Проанализируй все таймфреймы и сгенерируй торговый сигнал.
Открывай позицию ТОЛЬКО при четком сигнале.
Если сигналы противоречивы или тренд слабый то position: wait
This prevents:
Live trading evaluates strategy by:
Partial Event Metrics:
// Use exchange testnet/paper trading API
const exchange = new ccxt.binance({
apiKey: 'TESTNET_API_KEY',
secret: 'TESTNET_SECRET',
enableRateLimit: true,
options: {
defaultType: 'future',
test: true, // Enable testnet
}
});
Advantages:
Limitations:
// Use exchange production API
const exchange = new ccxt.binance({
apiKey: 'PRODUCTION_API_KEY',
secret: 'PRODUCTION_SECRET',
enableRateLimit: true,
options: {
defaultType: 'future',
}
});
Advantages:
Critical Requirements:
Never trade with real money until:
// Define custom profit levels
const CUSTOM_TP_LEVELS = {
LEVEL_1: 10, // 10% profit
LEVEL_2: 30, // 30% profit
LEVEL_3: 75, // 75% profit
};
listenPartialProfit(({ symbol, price, level }) => {
if (level === CUSTOM_TP_LEVELS.LEVEL_1) {
// Close 25% of position
await exchange.createOrder(symbol, 'market', 'sell', quantity * 0.25);
}
// ... more levels
});
let trailingStopLoss = null;
listenPartialProfit(({ symbol, price, level, signal }) => {
// Activate trailing stop after 25% profit
if (level >= 25 && !trailingStopLoss) {
trailingStopLoss = price * 0.95; // 5% trailing
console.log(`Trailing SL activated at ${trailingStopLoss}`);
}
// Update trailing stop if price moves up
if (trailingStopLoss && price > trailingStopLoss / 0.95) {
trailingStopLoss = price * 0.95;
console.log(`Trailing SL updated to ${trailingStopLoss}`);
}
});
import TelegramBot from 'node-telegram-bot-api';
const bot = new TelegramBot(TELEGRAM_BOT_TOKEN, { polling: true });
const chatId = YOUR_CHAT_ID;
listenSignalLive(async (event) => {
if (event.action === 'opened') {
await bot.sendMessage(
chatId,
`📈 OPENED ${event.signal.position.toUpperCase()}\n` +
`Symbol: ${event.symbol}\n` +
`Price: ${event.signal.priceOpen}\n` +
`TP: ${event.signal.priceTakeProfit}\n` +
`SL: ${event.signal.priceStopLoss}`
);
}
if (event.action === 'closed') {
await bot.sendMessage(
chatId,
`💰 CLOSED\n` +
`PNL: ${event.pnl.pnlPercentage.toFixed(2)}%\n` +
`Reason: ${event.closeReason}`
);
}
});
const symbols = ["BTCUSDT", "ETHUSDT", "SOLUSDT"];
for (const symbol of symbols) {
Live.background(symbol, {
strategyName: "test_strategy",
exchangeName: "test_exchange",
frameName: "test_frame",
});
}
// Track positions per symbol
const positions = new Map();
listenSignalLive((event) => {
if (event.action === 'opened') {
positions.set(event.symbol, event.signal);
}
if (event.action === 'closed') {
positions.delete(event.symbol);
}
console.log(`Active positions: ${positions.size}`);
});
// Calculate position size based on account balance and risk
const calculatePositionSize = (accountBalance, riskPercentage, stopLossPercentage) => {
const riskAmount = accountBalance * (riskPercentage / 100);
const positionSize = riskAmount / (stopLossPercentage / 100);
return positionSize;
};
listenSignalLive(async (event) => {
if (event.action === 'opened') {
const balance = await exchange.fetchBalance();
const stopLossPercentage = Math.abs(
(event.signal.priceStopLoss - event.signal.priceOpen) / event.signal.priceOpen * 100
);
const quantity = calculatePositionSize(
balance.total.USDT,
2, // Risk 2% of account per trade
stopLossPercentage
);
console.log(`Position size: ${quantity} USDT`);
// Execute order with calculated quantity
}
});
Issue: OLLAMA_API_KEY is not defined
# Solution: Set environment variable
export OLLAMA_API_KEY=your_key
Issue: Rate limit exceeded
// Solution: Add delay between requests in utils/json.mjs
await new Promise(resolve => setTimeout(resolve, 1000));
Issue: Invalid signal structure
// Solution: Check LLM output format matches schema
// Verify all required fields are present in JSON response
Issue: No signals generated (all 'wait')
// Solution: Adjust prompt to be more aggressive
// Or check if market data has sufficient volatility
Issue: Position not closing at TP/SL
// Solution: Check exchange order execution
// Verify price monitoring is working correctly
// Inspect logs for price discrepancies
Issue: Partial events not firing
// Solution: Verify Partial service is initialized
// Check if partial levels are configured correctly
// Ensure listenPartialProfit/Loss are registered before Live.background()
// Add to src/index.mjs
let emergencyStop = false;
process.on('SIGINT', async () => {
console.log('Emergency stop triggered!');
emergencyStop = true;
// Close all open positions
const positions = await exchange.fetchOpenOrders();
for (const position of positions) {
await exchange.cancelOrder(position.id);
}
process.exit(0);
});
// Check before each trade
listenSignalLive((event) => {
if (emergencyStop) {
console.log('Emergency stop active, skipping signal');
return;
}
// ... normal processing
});
MIT © tripolskypetr