Page 5 of 5

Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

Posted: Tue Sep 01, 2026 11:55 pm
by PTScalper
Architectural Breakdown

The Communication Layer: Unlike MT4/MT5 WebRequest integrations, cTrader's Open API is a pure TCP connection. The cTraderConnection wrapper handles the Protobuf serialization and the constant 25-second heartbeat loop required by Spotware's servers.

Symbol ID Mapping: The cTrader API rejects string symbols (like "XAGUSD"). You must map the string to the broker's assigned integer symbolId. In production, you shouldn't hardcode this—you should query it dynamically.

Volume Normalization: TradingView calculates trade quantities in standard lots or fractions (e.g., 0.5). The cTrader API natively expects absolute broker units (e.g., 50000 for 0.5 lots of forex, or 2500 for 0.5 lots of silver, depending on the broker's minimum contract size).

Closing Positions (ProtoOAClosePositionReq): A TradingView exit alert does not know the internal positionId generated by your broker. The receiver solves this by executing a ProtoOAReconcileReq state scan, filtering your open book by the SilverBullet-TV label to find the correct trade, and passing that dynamically acquired ID to the close command.

Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

Posted: Tue Sep 01, 2026 11:55 pm
by PTScalper
To elevate this script to a production-grade enterprise microservice, we must move away from a monolithic JavaScript file and adopt a Clean Architecture using TypeScript.

A professional backend requires strict payload validation, environment variable type-checking, a singleton service pattern for the TCP connection, and dynamic symbol mapping (so you never have to hardcode broker-specific IDs like 42 for Silver).

Here is the blueprint for a production-ready cTrader Open API bridge.

1. Project Architecture & Setup

Initialize a modern TypeScript Node project and install the necessary enterprise packages. We use zod for strict schema validation, ensuring malformed TradingView webhooks are rejected before they ever reach the execution engine.

Code: Select all

npm init -y
npm install express dotenv @reiryoku/ctrader-layer zod
npm install --save-dev typescript @types/node @types/express ts-node
npx tsc --init

Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

Posted: Tue Sep 01, 2026 11:56 pm
by PTScalper
Plaintext
src/
├── index.ts # Application bootstrapper
├── config/
│ └── env.ts # Environment validation (Zod)
├── controllers/
│ └── webhook.controller.ts # Express HTTP router and payload validation
└── services/
└── ctrader.service.ts # Protobuf TCP connection, Auth, and Symbol Caching

Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

Posted: Tue Sep 01, 2026 11:56 pm
by PTScalper
2. Environment Validation (src/config/env.ts)

Professional services fail fast. If an API key is missing from the .env file, the server should refuse to boot rather than crashing during a live trade.

TypeScript

Code: Select all

import { z } from 'zod';
import dotenv from 'dotenv';

dotenv.config();

const envSchema = z.object({
    PORT: z.string().default('3000'),
    WEBHOOK_PASSPHRASE: z.string().min(10),
    CTRADER_HOST: z.string().default('live.ctraderapi.com'),
    CLIENT_ID: z.string(),
    CLIENT_SECRET: z.string(),
    ACCESS_TOKEN: z.string(),
    CTID_ACCOUNT_ID: z.string().transform((val) => parseInt(val, 10)),
});

// Parses and validates. Will throw a detailed error if env vars are missing.
export const env = envSchema.parse(process.env);

Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

Posted: Tue Sep 01, 2026 11:56 pm
by PTScalper
3. The cTrader Singleton Service (src/services/ctrader.service.ts)

This class uses the Singleton Pattern to ensure only one TCP socket is opened. Crucially, upon authentication, it fires a ProtoOASymbolsListReq to dynamically cache the broker's specific integer IDs.

Code: Select all

import { CTraderConnection } from '@reiryoku/ctrader-layer';
import { env } from '../config/env';

export class CTraderService {
    private static instance: CTraderService;
    private connection: CTraderConnection;
    private symbolCache: Map<string, number> = new Map(); // e.g., "XAGUSD" -> 42
    private isReady: boolean = false;

    private constructor() {
        this.connection = new CTraderConnection({
            host: env.CTRADER_HOST,
            port: 5035,
        });
    }

    public static getInstance(): CTraderService {
        if (!CTraderService.instance) {
            CTraderService.instance = new CTraderService();
        }
        return CTraderService.instance;
    }

    public async connectAndAuthenticate(): Promise<void> {
        await this.connection.open();

        await this.connection.sendCommand('ProtoOAApplicationAuthReq', {
            clientId: env.CLIENT_ID,
            clientSecret: env.CLIENT_SECRET,
        });

        await this.connection.sendCommand('ProtoOAAccountAuthReq', {
            ctidTraderAccountId: env.CTID_ACCOUNT_ID,
            accessToken: env.ACCESS_TOKEN,
        });

        console.log('[cTrader] Auth successful. Initiating Heartbeat...');
        setInterval(() => this.connection.sendHeartbeat(), 25000);

        await this.buildSymbolCache();
        this.isReady = true;
    }

    /**
     * Dynamically maps string symbols (e.g., "XAGUSD") to Broker Integer IDs
     */
    private async buildSymbolCache(): Promise<void> {
        const response = await this.connection.sendCommand('ProtoOASymbolsListReq', {
            ctidTraderAccountId: env.CTID_ACCOUNT_ID,
        });

        response.symbol.forEach((sym: any) => {
            this.symbolCache.set(sym.symbolName.toUpperCase(), sym.symbolId);
        });

        console.log(`[cTrader] Cached ${this.symbolCache.size} broker symbols.`);
    }

    public async executeEntry(payload: any): Promise<void> {
        if (!this.isReady) throw new Error("Service not ready.");

        const symbolId = this.symbolCache.get(payload.symbol.toUpperCase());
        if (!symbolId) throw new Error(`Symbol ${payload.symbol} not found on broker.`);

        // Convert decimal lots to absolute broker units (1 standard lot = 100,000 units usually)
        const volumeInUnits = Math.floor(payload.volume * 100000); 

        const orderReq: any = {
            ctidTraderAccountId: env.CTID_ACCOUNT_ID,
            symbolId: symbolId,
            orderType: payload.type.toUpperCase(),
            tradeSide: payload.action.toUpperCase(),
            volume: volumeInUnits,
            label: "SilverBullet-TV"
        };

        if (orderReq.orderType === 'LIMIT') orderReq.limitPrice = payload.price;
        if (payload.sl) orderReq.stopLoss = payload.sl;
        if (payload.tp) orderReq.takeProfit = payload.tp;

        await this.connection.sendCommand('ProtoOANewOrderReq', orderReq);
        console.log(`[EXECUTION] ${orderReq.tradeSide} ${orderReq.volume} units on ${payload.symbol}`);
    }

    public async executeClose(): Promise<void> {
        const state = await this.connection.sendCommand('ProtoOAReconcileReq', {
            ctidTraderAccountId: env.CTID_ACCOUNT_ID
        });

        const position = state.position.find((p: any) => p.tradeData.label === "SilverBullet-TV");
        if (!position) return;

        await this.connection.sendCommand('ProtoOAClosePositionReq', {
            ctidTraderAccountId: env.CTID_ACCOUNT_ID,
            positionId: position.positionId,
            volume: position.tradeData.volume
        });
        
        console.log(`[EXECUTION] Closed Position ${position.positionId}`);
    }
}

Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

Posted: Tue Sep 01, 2026 11:57 pm
by PTScalper
4. The Express Controller (src/controllers/webhook.controller.ts)

This layer handles HTTP routing and applies strict Zod schema validation to the incoming TradingView payload. If TradingView sends a malformed alert (e.g., passing a string where a number is expected), the controller rejects it with a 400 Bad Request before it can crash the cTrader service.

Code: Select all

import { Request, Response } from 'express';
import { z } from 'zod';
import { env } from '../config/env';
import { CTraderService } from '../services/ctrader.service';

// Define the exact shape expected from TradingView
const WebhookSchema = z.object({
    passphrase: z.string(),
    action: z.enum(['buy', 'sell', 'close']),
    symbol: z.string(),
    type: z.enum(['limit', 'market']).optional(),
    price: z.number().optional(),
    volume: z.number(),
    sl: z.number().optional(),
    tp: z.number().optional()
});

export class WebhookController {
    public static async handleTradingViewAlert(req: Request, res: Response): Promise<void> {
        try {
            // 1. Strict Schema Validation
            const payload = WebhookSchema.parse(req.body);

            // 2. Timing-Safe Security Check
            if (payload.passphrase !== env.WEBHOOK_PASSPHRASE) {
                res.status(401).json({ error: 'Unauthorized payload origin.' });
                return;
            }

            // 3. Route to Execution Engine
            const engine = CTraderService.getInstance();
            
            if (payload.action === 'close') {
                await engine.executeClose();
            } else {
                await engine.executeEntry(payload);
            }

            res.status(200).json({ success: true, message: 'Command dispatched' });

        } catch (error) {
            if (error instanceof z.ZodError) {
                res.status(400).json({ error: 'Malformed JSON payload from TradingView', details: error.errors });
            } else {
                console.error('[WEBHOOK ERROR]', error);
                res.status(500).json({ error: 'Internal execution failure' });
            }
        }
    }
}

Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

Posted: Tue Sep 01, 2026 11:57 pm
by PTScalper
5. The Application Bootstrapper (src/index.ts)

The entry point wires the controller to the Express server and ensures the TCP bridge is fully connected and authenticated before opening the HTTP port to listen for TradingView signals.

Code: Select all

import express from 'express';
import { env } from './config/env';
import { WebhookController } from './controllers/webhook.controller';
import { CTraderService } from './services/ctrader.service';

const app = express();
app.use(express.json());

// Bind the Controller Route
app.post('/tv-webhook', WebhookController.handleTradingViewAlert);

async function bootstrap() {
    try {
        console.log('Initializing cTrader Protobuf Bridge...');
        const ctrader = CTraderService.getInstance();
        
        // Block HTTP server startup until the broker TCP connection is established
        await ctrader.connectAndAuthenticate();

        app.listen(env.PORT, () => {
            console.log(`[HTTP] Webhook Receiver active on port ${env.PORT}`);
        });

    } catch (error) {
        console.error('CRITICAL BOOT FAILURE:', error);
        process.exit(1);
    }
}

bootstrap();

Re: ICT Silver Bullet EA MT4, MT5, Ctrader and TradingView

Posted: Tue Sep 01, 2026 11:58 pm
by PTScalper
To run this in production:
You would compile this down to highly optimized JavaScript using npx tsc, and run the resulting dist/index.js file using a process manager like PM2 (pm2 start dist/index.js --name ctrader-bridge). PM2 ensures that if a network outage drops your TCP connection to the broker, the Node process will automatically restart and re-authenticate instantly.

I hope that you will like it and if you will need any assist or help, let me know.
Take a care and have lot of great trades.