Files
aitrade/cmd/ibtest/main.go
T
kaedwen 62809a01ea
Build and Push Docker Image / build-and-push (push) Successful in 4m7s
parallel source fetch with timeout
Signed-off-by: kaedwen <kaedwen@heinrich.blue>
2026-07-09 21:11:58 +02:00

229 lines
6.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"flag"
"fmt"
"log"
"os"
"os/signal"
"strconv"
"syscall"
"time"
"github.com/scmhub/ibapi"
)
type Wrapper struct {
ibapi.Wrapper
connected bool
nextID int64
errorMsg string
marketDataRecv bool
}
func (w *Wrapper) ConnectAck() {
fmt.Println("✓ Connected to IB Gateway")
w.connected = true
}
func (w *Wrapper) NextValidID(orderId int64) {
fmt.Printf("✓ Next valid order ID: %d\n", orderId)
w.nextID = orderId
}
func (w *Wrapper) ManagedAccounts(accountsList []string) {
fmt.Printf("✓ Managed accounts: %v\n", accountsList)
}
func (w *Wrapper) Error(id int64, errorCode int64, errorMsgType int64, errorString string, advancedOrderRejectJson string) {
// Info messages (2100-2199) or connection status messages
if errorCode >= 2100 && errorCode < 2200 {
fmt.Printf(" Info [%d]: %s\n", errorCode, errorString)
} else if errorCode >= 1100 && errorCode < 1300 {
// Warning messages (1100-1299) - system/connection warnings
fmt.Printf("⚠ Warning [%d]: %s\n", errorCode, errorString)
} else if errorCode > 1783000000000 {
// Very high error codes are typically connection status messages
// Ignore or log as debug
} else {
fmt.Printf("✗ Error [%d]: %s\n", errorCode, errorString)
w.errorMsg = errorString
}
}
func (w *Wrapper) CurrentTime(t int64) {
serverTime := time.Unix(t, 0)
fmt.Printf("✓ Server time: %s\n", serverTime.Format("2006-01-02 15:04:05 MST"))
}
func (w *Wrapper) ConnectionClosed() {
fmt.Println("⚠ Connection closed by server")
w.connected = false
}
func (w *Wrapper) TickPrice(reqID int64, tickType ibapi.TickType, price float64, attrib ibapi.TickAttrib) {
w.marketDataRecv = true
tickName := map[ibapi.TickType]string{
1: "BID", 2: "ASK", 4: "LAST", 6: "HIGH", 7: "LOW", 9: "CLOSE",
14: "OPEN", 15: "LOW_13_WEEK", 16: "HIGH_13_WEEK", 17: "LOW_26_WEEK", 18: "HIGH_26_WEEK",
19: "LOW_52_WEEK", 20: "HIGH_52_WEEK", 35: "LAST_TIMESTAMP", 37: "MARK_PRICE",
66: "BID_EXCH", 67: "ASK_EXCH", 68: "LAST_EXCH", 72: "HIGH_52W", 73: "LOW_52W",
75: "PREV_CLOSE", 76: "HALTED",
}
name := tickName[tickType]
if name == "" {
name = fmt.Sprintf("Type_%d", tickType)
}
timestamp := time.Now().Format("15:04:05")
fmt.Printf("[%s] %-22s %.2f\n", timestamp, name, price)
}
func (w *Wrapper) TickSize(reqID int64, tickType ibapi.TickType, size ibapi.Decimal) {
w.marketDataRecv = true
tickName := map[ibapi.TickType]string{
0: "BID_SIZE", 3: "ASK_SIZE", 5: "LAST_SIZE", 8: "VOLUME",
21: "AVG_VOLUME", 27: "CALL_OPTION_VOLUME", 28: "PUT_OPTION_VOLUME",
29: "CALL_OPEN_INTEREST", 30: "PUT_OPEN_INTEREST", 34: "AUCTION_VOLUME",
69: "BID_SIZE_EXCH", 70: "ASK_SIZE_EXCH", 71: "LAST_SIZE_EXCH", 74: "VOLUME_52W",
}
name := tickName[tickType]
if name == "" {
name = fmt.Sprintf("Type_%d", tickType)
}
timestamp := time.Now().Format("15:04:05")
fmt.Printf("[%s] %-22s %s\n", timestamp, name, size.String())
}
func (w *Wrapper) TickString(reqID int64, tickType ibapi.TickType, value string) {
w.marketDataRecv = true
tickName := map[ibapi.TickType]string{
32: "BID_EXCH", 33: "ASK_EXCH", 45: "LAST_TIMESTAMP", 48: "RT_VOLUME",
84: "LAST_EXCH", 85: "LAST_REG_TIME", 88: "DELAYED_LAST_TIMESTAMP",
}
name := tickName[tickType]
if name == "" {
name = fmt.Sprintf("Type_%d", tickType)
}
// Convert Unix timestamp to ISO format for timestamp fields
displayValue := value
if tickType == 45 || tickType == 88 { // LAST_TIMESTAMP or DELAYED_LAST_TIMESTAMP
if ts, err := strconv.ParseInt(value, 10, 64); err == nil {
displayValue = time.Unix(ts, 0).Format(time.RFC3339)
}
}
timestamp := time.Now().Format("15:04:05")
fmt.Printf("[%s] %-22s %s\n", timestamp, name, displayValue)
}
func main() {
host := flag.String("host", "127.0.0.1", "IB Gateway host")
port := flag.Int("port", 4002, "IB Gateway port (4001 for live, 4002 for paper)")
clientID := flag.Int("client", 1, "Client ID")
timeout := flag.Int("timeout", 10, "Connection timeout in seconds")
symbol := flag.String("symbol", "", "Symbol to subscribe to market data (e.g. AAPL, TSLA)")
exchange := flag.String("exchange", "SMART", "Exchange for market data")
currency := flag.String("currency", "USD", "Currency for market data")
marketDataType := flag.Int("mdtype", 3, "Market data type: 1=Live, 2=Frozen, 3=Delayed(15min), 4=Delayed-Frozen")
flag.Parse()
fmt.Printf("Testing IB Gateway connection...\n")
fmt.Printf("Host: %s:%d\n", *host, *port)
fmt.Printf("Client ID: %d\n\n", *clientID)
wrapper := &Wrapper{}
client := ibapi.NewEClient(wrapper)
// Connect
fmt.Println("→ Connecting...")
if err := client.Connect(*host, *port, int64(*clientID)); err != nil {
log.Fatalf("✗ Connection failed: %v\n", err)
}
defer client.Disconnect()
// Wait for connection
start := time.Now()
for !wrapper.connected && time.Since(start) < time.Duration(*timeout)*time.Second {
time.Sleep(100 * time.Millisecond)
}
if !wrapper.connected {
log.Fatal("✗ Connection timeout")
}
// Wait for NextValidId
start = time.Now()
for wrapper.nextID == 0 && time.Since(start) < time.Duration(*timeout)*time.Second {
time.Sleep(100 * time.Millisecond)
}
if wrapper.nextID == 0 {
log.Fatal("✗ Did not receive NextValidId")
}
// Request server time
fmt.Println("\n→ Requesting server time...")
client.ReqCurrentTime()
// Wait for response
time.Sleep(1 * time.Second)
// Subscribe to market data if symbol provided
if *symbol != "" {
fmt.Printf("\n→ Subscribing to market data for %s...\n", *symbol)
contract := &ibapi.Contract{
Symbol: *symbol,
SecType: "STK",
Exchange: *exchange,
Currency: *currency,
}
// Request market data type
// MarketDataType: 1=Live, 2=Frozen, 3=Delayed(15min), 4=Delayed-Frozen
mdTypeDesc := map[int]string{
1: "Live",
2: "Frozen",
3: "Delayed (15min)",
4: "Delayed-Frozen",
}
fmt.Printf(" Market data type: %s\n", mdTypeDesc[*marketDataType])
client.ReqMarketDataType(int64(*marketDataType))
time.Sleep(500 * time.Millisecond)
// Request market data
// ReqID: 1, Contract, GenericTickList: "", Snapshot: false, RegulatorySnapshot: false, MktDataOptions: nil
client.ReqMktData(1, contract, "", false, false, nil)
// Wait for initial market data
fmt.Println(" Receiving market data... (Press Ctrl+C to exit)")
time.Sleep(2 * time.Second)
if !wrapper.marketDataRecv {
fmt.Println(" ⚠ No market data received yet - may require market data subscription or market is closed")
}
// Setup signal handler for graceful shutdown
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
// Wait for Ctrl+C
<-sigChan
fmt.Println("\n\n→ Shutting down...")
// Cancel market data subscription
client.CancelMktData(1)
fmt.Println(" Market data subscription cancelled")
} else {
fmt.Println("\n✓ Connection test successful!")
fmt.Println("\nConnection details:")
fmt.Printf(" - Next Order ID: %d\n", wrapper.nextID)
fmt.Printf(" - Client ID: %d\n", *clientID)
}
os.Exit(0)
}