π 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:
- Order Management - Place, modify, and cancel orders
- Position Tracking - Monitor open positions and P&L
- Account Management - Handle multiple trading accounts
- Risk Management - Implement risk controls and limits
- Trade History - Track and analyze trading performance
- 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">×</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);
}
}π Related Documentation
- API Reference - Complete API documentation
- Configuration Guide - Trading configuration
- Production Guide - Deploy trading applications
Ready to deploy your trading application? Check out our production deployment guide!
Last updated on