Skip to Content
TutorialsTrading Integration

πŸ“ˆ Trading Integration Tutorial

Complete guide to integrating real trading capabilities with GoCharting SDK, including order management, position tracking, and risk controls.

πŸ“‹ Prerequisites

  • Professional or Enterprise license (trading features require paid license)
  • Completed Basic Integration Tutorial
  • Understanding of trading concepts and risk management
  • Access to a broker API or trading platform

🎯 Trading Features Overview

This tutorial covers:

  1. Order Management - Place, modify, and cancel orders
  2. Position Tracking - Monitor open positions and P&L
  3. Account Management - Handle multiple trading accounts
  4. Risk Management - Implement risk controls and limits
  5. Trade History - Track and analyze trading performance
  6. Real-time Updates - Live order and position updates

πŸ”§ Setting Up Trading Configuration

Enable Trading Features

To enable trading functionality in your GoCharting SDK implementation, use the enableTrading parameter:

// βœ… Simple enableTrading configuration const chart = GoChartingSDK.createChart("#chart-container", { symbol: "BYBIT:FUTURE:BTCUSDT", interval: "1m", datafeed: myDatafeed, licenseKey: "YOUR_LICENSE_KEY", enableTrading: true, // 🎯 Enable trading features });

This enables:

  • Right-click context menus with Buy/Sell options
  • CrossHair trading menus when hovering over prices
  • Settings > Trading tab for trading configuration
  • Trading event callbacks through appCallback

Available Configuration Options

Based on the actual implementation in the codebase, here are the configuration options you can use:

// Create chart with all available configuration options const chart = GoChartingSDK.createChart("#chart-container", { // Required options symbol: "BYBIT:FUTURE:BTCUSDT", // Trading symbol interval: "1m", // Chart interval datafeed: myTradingDatafeed, // Your datafeed implementation licenseKey: "YOUR_LICENSE_KEY", // Your license key // Trading options enableTrading: true, // 🎯 Enable trading features // Optional configuration theme: "dark", // Chart theme: "light" or "dark" debugLog: false, // Enable debug logging (default: false) // Event callbacks appCallback: (eventType, message) => { // Handle trading events like order placement, modifications, etc. console.log(`Trading Event: ${eventType}`, message); handleTradingEvent(eventType, message); }, onReady: (chartInstance) => { // Called when chart is ready myTradingDatafeed.setChart(chartInstance); console.log("βœ… Trading chart ready"); }, onError: (error) => { // Called when chart encounters an error console.error("Chart error:", error); }, });

Trading Event Handling

The appCallback function receives trading events that you can handle:

function handleTradingEvent(eventType, message) { switch (eventType) { case "CREATE_ORDER": // User initiated order creation console.log("Order creation requested:", message); break; case "PLACE_ORDER": // User confirmed order placement console.log("Order placement confirmed:", message); break; case "MODIFY_ORDER": // User requested order modification console.log("Order modification requested:", message); break; case "CANCEL_ORDER": // User requested order cancellation console.log("Order cancellation requested:", message); break; default: console.log("Unknown trading event:", eventType, message); } }

πŸ“Š Implementing Trading Datafeed

Trading-Enabled Datafeed

The current GoCharting SDK architecture uses a simple datafeed object pattern. Here’s how to implement trading functionality:

// Create your trading-enabled datafeed class TradingDatafeed { constructor(brokerAPI) { this.brokerAPI = brokerAPI; this.accounts = new Map(); this.orders = new Map(); this.positions = new Map(); this.trades = []; this.chart = null; // Will be set when chart is ready } async init() { // Initialize broker connection await this.brokerAPI.connect(); // Load account data await this.loadAccountData(); // Set up real-time updates this.setupRealTimeUpdates(); } async loadAccountData() { try { // Load accounts const accounts = await this.brokerAPI.getAccounts(); accounts.forEach((account) => { this.accounts.set(account.id, account); }); // Load orders const orders = await this.brokerAPI.getOrders(); orders.forEach((order) => { this.orders.set(order.orderId, order); }); // Load positions const positions = await this.brokerAPI.getPositions(); positions.forEach((position) => { this.positions.set(position.symbol, position); }); // Load trade history this.trades = await this.brokerAPI.getTradeHistory(); // Update chart with broker data if chart is ready if (this.chart) { this.updateChartBrokerData(); } console.log("βœ… Trading data loaded"); } catch (error) { console.error("❌ Failed to load trading data:", error); } } // Method to update chart with broker data updateChartBrokerData() { const brokerData = { accountList: Array.from(this.accounts.values()), orderBook: Array.from(this.orders.values()), tradeBook: this.trades, positions: Array.from(this.positions.values()), }; // Use the chart's setBrokerAccounts method if (this.chart && this.chart.setBrokerAccounts) { this.chart.setBrokerAccounts(brokerData); } } // Set chart reference when chart is ready setChart(chart) { this.chart = chart; // Update with any existing broker data if (this.accounts.size > 0) { this.updateChartBrokerData(); } } setupRealTimeUpdates() { // Listen for order updates this.brokerAPI.onOrderUpdate((order) => { this.handleOrderUpdate(order); }); // Listen for position updates this.brokerAPI.onPositionUpdate((position) => { this.handlePositionUpdate(position); }); // Listen for trade executions this.brokerAPI.onTradeExecution((trade) => { this.handleTradeExecution(trade); }); // Listen for account updates this.brokerAPI.onAccountUpdate((account) => { this.handleAccountUpdate(account); }); } // Required datafeed methods for GoCharting SDK async getBars(symbolInfo, resolution, periodParams) { // Implement your historical data fetching logic // This should return OHLCV data for the chart return await this.brokerAPI.getHistoricalData( symbolInfo, resolution, periodParams ); } resolveSymbol(symbolName, onResolve, onError) { // Implement symbol resolution logic // This should resolve symbol information this.brokerAPI .resolveSymbol(symbolName) .then((symbolInfo) => onResolve(symbolInfo)) .catch((error) => onError(error)); } searchSymbols(userInput, callback) { // Implement symbol search logic this.brokerAPI .searchSymbols(userInput) .then((results) => callback(results)) .catch((error) => callback([])); } // Trading methods async placeOrder(orderRequest) { try { // Validate order const validation = this.validateOrder(orderRequest); if (!validation.valid) { throw new Error(validation.error); } // Place order through broker API const order = await this.brokerAPI.placeOrder(orderRequest); // Update local state this.orders.set(order.orderId, order); // Update chart with new broker data this.updateChartBrokerData(); return order; } catch (error) { console.error("❌ Failed to place order:", error); throw error; } } async cancelOrder(orderId) { try { await this.brokerAPI.cancelOrder(orderId); // Update local state const order = this.orders.get(orderId); if (order) { order.status = "cancelled"; // Update chart with new broker data this.updateChartBrokerData(); } } catch (error) { console.error("❌ Failed to cancel order:", error); throw error; } } async modifyOrder(orderId, modifications) { try { const updatedOrder = await this.brokerAPI.modifyOrder( orderId, modifications ); // Update local state this.orders.set(orderId, updatedOrder); // Update chart with new broker data this.updateChartBrokerData(); return updatedOrder; } catch (error) { console.error("❌ Failed to modify order:", error); throw error; } } validateOrder(orderRequest) { const { symbol, side, orderType, price, size, accountId } = orderRequest; // Basic validation if (!symbol || !side || !orderType || !size || !accountId) { return { valid: false, error: "Missing required order fields" }; } // Check account exists const account = this.accounts.get(accountId); if (!account) { return { valid: false, error: "Invalid account ID" }; } // Check sufficient balance const requiredMargin = this.calculateRequiredMargin(orderRequest); if (account.availableBalance < requiredMargin) { return { valid: false, error: "Insufficient balance" }; } // Risk management checks const riskCheck = this.checkRiskLimits(orderRequest); if (!riskCheck.valid) { return riskCheck; } return { valid: true }; } calculateRequiredMargin(orderRequest) { const { size, price, orderType } = orderRequest; if (orderType === "market") { // Use current market price for margin calculation const currentPrice = this.getCurrentPrice(orderRequest.symbol); return size * currentPrice * 0.1; // 10% margin requirement } else { return size * price * 0.1; } } checkRiskLimits(orderRequest) { // Check maximum position size const currentPosition = this.positions.get(orderRequest.symbol); const newPositionSize = (currentPosition?.size || 0) + orderRequest.size; if (Math.abs(newPositionSize) > this.riskLimits.maxPositionSize) { return { valid: false, error: "Exceeds maximum position size" }; } // Check daily loss limit const dailyPnL = this.calculateDailyPnL(); if (dailyPnL < -this.riskLimits.maxDailyLoss) { return { valid: false, error: "Daily loss limit reached" }; } return { valid: true }; } // Event handlers handleOrderUpdate(order) { this.orders.set(order.orderId, order); // Update chart with new broker data this.updateChartBrokerData(); } handlePositionUpdate(position) { this.positions.set(position.symbol, position); // Update chart with new broker data this.updateChartBrokerData(); } handleTradeExecution(trade) { this.trades.push(trade); // Update position this.updatePositionFromTrade(trade); // Update chart with new broker data this.updateChartBrokerData(); } handleAccountUpdate(account) { this.accounts.set(account.id, account); // Update chart with new broker data this.updateChartBrokerData(); } updatePositionFromTrade(trade) { const position = this.positions.get(trade.symbol) || { symbol: trade.symbol, side: trade.side, size: 0, entryPrice: 0, currentPrice: trade.price, pnl: 0, }; // Update position size and average price if (trade.side === position.side || position.size === 0) { // Adding to position const totalValue = position.size * position.entryPrice + trade.size * trade.price; const totalSize = position.size + trade.size; position.size = totalSize; position.entryPrice = totalValue / totalSize; } else { // Reducing position position.size = Math.max(0, position.size - trade.size); if (position.size === 0) { position.entryPrice = 0; } } this.positions.set(trade.symbol, position); // Update chart with new position data this.updateChartBrokerData(); } // Utility methods getCurrentPrice(symbol) { // Get current market price from chart data or market feed return this.marketData?.get(symbol)?.price || 0; } calculateDailyPnL() { const today = new Date().toDateString(); return this.trades .filter( (trade) => new Date(trade.timestamp).toDateString() === today ) .reduce((total, trade) => total + (trade.pnl || 0), 0); } // Get trading data for chart (used internally by updateChartBrokerData) getBrokerAccountData() { return { accountList: Array.from(this.accounts.values()), orderBook: Array.from(this.orders.values()), tradeBook: this.trades, positions: Array.from(this.positions.values()), }; } } // Usage example with the new architecture async function createTradingChart() { // Create your trading datafeed const tradingDatafeed = new TradingDatafeed(brokerAPI); // Initialize the datafeed await tradingDatafeed.init(); // Create the chart with trading enabled const chart = GoChartingSDK.createChart("#chart-container", { symbol: "BYBIT:FUTURE:BTCUSDT", interval: "1m", datafeed: tradingDatafeed, licenseKey: "YOUR_LICENSE_KEY", enableTrading: true, onReady: (chartInstance) => { // Set chart reference in datafeed for broker data updates tradingDatafeed.setChart(chartInstance); console.log("βœ… Trading chart ready"); }, }); return { chart, tradingDatafeed }; }

🎯 Trading UI Components

Order Entry Panel

class OrderEntryPanel { constructor(chart, tradingProvider) { this.chart = chart; this.tradingProvider = tradingProvider; this.createOrderPanel(); this.setupEventListeners(); } createOrderPanel() { const panel = document.createElement("div"); panel.className = "order-entry-panel"; panel.innerHTML = ` <div class="panel-header"> <h3>Place Order</h3> <button class="close-btn">&times;</button> </div> <div class="panel-content"> <div class="form-group"> <label>Account</label> <select id="account-select"> <!-- Populated dynamically --> </select> </div> <div class="form-group"> <label>Symbol</label> <input type="text" id="symbol-input" readonly> </div> <div class="form-group"> <label>Side</label> <div class="button-group"> <button class="buy-btn active" data-side="buy">Buy</button> <button class="sell-btn" data-side="sell">Sell</button> </div> </div> <div class="form-group"> <label>Order Type</label> <select id="order-type-select"> <option value="market">Market</option> <option value="limit" selected>Limit</option> <option value="stop">Stop</option> <option value="stop_limit">Stop Limit</option> </select> </div> <div class="form-group"> <label>Price</label> <input type="number" id="price-input" step="0.01"> </div> <div class="form-group"> <label>Size</label> <input type="number" id="size-input" step="1" min="1"> </div> <div class="form-group"> <label>Time in Force</label> <select id="tif-select"> <option value="GTC" selected>Good Till Cancelled</option> <option value="IOC">Immediate or Cancel</option> <option value="FOK">Fill or Kill</option> <option value="DAY">Day Order</option> </select> </div> <div class="advanced-options"> <h4>Advanced Options</h4> <div class="form-group"> <label>Stop Loss</label> <input type="number" id="stop-loss-input" step="0.01"> </div> <div class="form-group"> <label>Take Profit</label> <input type="number" id="take-profit-input" step="0.01"> </div> </div> <div class="order-summary"> <div class="summary-row"> <span>Estimated Cost:</span> <span id="estimated-cost">$0.00</span> </div> <div class="summary-row"> <span>Available Balance:</span> <span id="available-balance">$0.00</span> </div> </div> <div class="panel-actions"> <button class="cancel-btn">Cancel</button> <button class="place-order-btn">Place Order</button> </div> </div> `; document.body.appendChild(panel); this.panel = panel; } setupEventListeners() { // Side selection this.panel.querySelectorAll("[data-side]").forEach((btn) => { btn.addEventListener("click", (e) => { this.selectSide(e.target.dataset.side); }); }); // Order type change this.panel .querySelector("#order-type-select") .addEventListener("change", (e) => { this.updateOrderType(e.target.value); }); // Price/size input changes ["price-input", "size-input"].forEach((id) => { this.panel.querySelector(`#${id}`).addEventListener("input", () => { this.updateOrderSummary(); }); }); // Place order button this.panel .querySelector(".place-order-btn") .addEventListener("click", () => { this.placeOrder(); }); // Cancel button this.panel .querySelector(".cancel-btn") .addEventListener("click", () => { this.hide(); }); // Close button this.panel.querySelector(".close-btn").addEventListener("click", () => { this.hide(); }); } show(symbol, price) { this.panel.querySelector("#symbol-input").value = symbol; this.panel.querySelector("#price-input").value = price; this.updateOrderSummary(); this.panel.style.display = "block"; } hide() { this.panel.style.display = "none"; } selectSide(side) { // Update button states this.panel.querySelectorAll("[data-side]").forEach((btn) => { btn.classList.toggle("active", btn.dataset.side === side); }); this.updateOrderSummary(); } updateOrderType(orderType) { const priceInput = this.panel.querySelector("#price-input"); priceInput.disabled = orderType === "market"; if (orderType === "market") { priceInput.value = "Market Price"; } this.updateOrderSummary(); } updateOrderSummary() { const price = parseFloat(this.panel.querySelector("#price-input").value) || 0; const size = parseFloat(this.panel.querySelector("#size-input").value) || 0; const estimatedCost = price * size; this.panel.querySelector( "#estimated-cost" ).textContent = `$${estimatedCost.toFixed(2)}`; } async placeOrder() { try { const orderRequest = this.getOrderRequest(); // Show loading state const placeBtn = this.panel.querySelector(".place-order-btn"); placeBtn.disabled = true; placeBtn.textContent = "Placing..."; // Place order const order = await this.tradingProvider.placeOrder(orderRequest); // Show success message this.showNotification(`Order placed: ${order.orderId}`, "success"); // Hide panel this.hide(); } catch (error) { this.showNotification( `Failed to place order: ${error.message}`, "error" ); } finally { // Reset button state const placeBtn = this.panel.querySelector(".place-order-btn"); placeBtn.disabled = false; placeBtn.textContent = "Place Order"; } } getOrderRequest() { const activeSideBtn = this.panel.querySelector("[data-side].active"); return { accountId: this.panel.querySelector("#account-select").value, symbol: this.panel.querySelector("#symbol-input").value, side: activeSideBtn.dataset.side, orderType: this.panel.querySelector("#order-type-select").value, price: parseFloat(this.panel.querySelector("#price-input").value), size: parseFloat(this.panel.querySelector("#size-input").value), timeInForce: this.panel.querySelector("#tif-select").value, stopLoss: parseFloat( this.panel.querySelector("#stop-loss-input").value ) || null, takeProfit: parseFloat( this.panel.querySelector("#take-profit-input").value ) || null, }; } showNotification(message, type) { // Implementation for showing notifications console.log(`${type.toUpperCase()}: ${message}`); } }

πŸ“Š Position Management

Position Tracker

class PositionTracker { constructor(chart, tradingProvider) { this.chart = chart; this.tradingProvider = tradingProvider; this.positions = new Map(); this.setupEventListeners(); } setupEventListeners() { this.tradingProvider.on("positionUpdate", (position) => { this.updatePosition(position); }); this.tradingProvider.on("tradeExecution", (trade) => { this.handleTradeExecution(trade); }); } updatePosition(position) { this.positions.set(position.symbol, position); this.updatePositionDisplay(position); this.updateChartPositionLines(position); } updatePositionDisplay(position) { // Update position in UI table const positionRow = document.querySelector( `[data-symbol="${position.symbol}"]` ); if (positionRow) { this.updatePositionRow(positionRow, position); } else { this.createPositionRow(position); } } updateChartPositionLines(position) { if (position.size === 0) { // Remove position line if position is closed this.chart.removePositionLine(position.symbol); } else { // Add or update position line this.chart.addPositionLine({ symbol: position.symbol, entryPrice: position.entryPrice, currentPrice: position.currentPrice, size: position.size, side: position.side, pnl: position.pnl, style: { color: position.pnl >= 0 ? "#4caf50" : "#f44336", width: 2, style: "solid", }, }); } } calculatePositionMetrics(position) { const { entryPrice, currentPrice, size, side } = position; const multiplier = side === "long" ? 1 : -1; const priceDiff = (currentPrice - entryPrice) * multiplier; const pnl = priceDiff * size; const pnlPercent = (priceDiff / entryPrice) * 100; return { pnl: pnl, pnlPercent: pnlPercent, unrealizedPnl: pnl, marketValue: currentPrice * size, }; } closePosition(symbol, size = null) { const position = this.positions.get(symbol); if (!position) return; const closeSize = size || position.size; const closeSide = position.side === "long" ? "sell" : "buy"; // Create market order to close position const closeOrder = { symbol: symbol, side: closeSide, orderType: "market", size: closeSize, reduceOnly: true, }; return this.tradingProvider.placeOrder(closeOrder); } }

Ready to deploy your trading application? Check out our production deployment guide!

Last updated on