Onepagecode

Onepagecode

Building Production-Grade Algorithmic Trading Bots

Decoupling strategy logic from execution using a secure, plugin-based architecture.

Onepagecode's avatar
Onepagecode
Dec 28, 2025
∙ Paid

Download the source code using the button at the end of this article:

In the high-stakes world of algorithmic trading, the difference between success and failure often lies not just in the “alpha” of a strategy, but in the resilience of the infrastructure that supports it. Building a trading robot is deceptively complex; beyond the buy and sell logic, developers must solve difficult engineering problems regarding reliable connectivity, crash recovery, state persistence, and concurrent execution. In this article, we explore goalgo, a comprehensive framework built in Go (Golang) that professionalizes the development of algorithmic trading bots by abstracting away this complexity. We are looking under the hood of a system designed to separate the “business logic” of trading from the “plumbing” of execution, ensuring that strategies can run safely in a production environment.

We are dissecting the framework’s architecture to understand how it achieves isolation and scalability. At its core, goalgo does not run strategies as simple internal functions, but rather as independent processes managed via HashiCorp’s go-plugin system. We will examine how this design choice allows for robust fault tolerance—ensuring that if a single strategy crashes, it does not bring down the entire host system. By leveraging gRPC for communication between the host broker and the individual strategy plugins, the framework creates a strict contract for lifecycle management, allowing the system to start, stop, pause, and configure robots remotely with precision.

Furthermore, we will dive into the specific implementation details that make goalgo production-ready. We will analyze how the framework handles data persistence using a custom Key-Value store backed by MessagePack serialization, enabling robots to “remember” their state (such as open positions or entry prices) even after a restart. We will also look at the unified exchange integration, seeing how the code standardizes interactions with diverse platforms like BitMEX and Binance through a common interface. Through this code-level walkthrough, we demonstrate how goalgo combines modern software engineering practices with financial trading requirements to create a powerful, distributed algorithmic trading engine.

Overview

Goalgo is designed around a plugin architecture that separates trading strategy logic from the platform infrastructure. Each trading strategy runs as its own operating system process, communicating with a central host application via gRPC. This design provides:

  • Isolation: Strategy crashes don’t affect other strategies or the host

  • Independent Updates: Deploy new strategy versions without system restarts

  • Scalability: Run multiple strategy instances across different exchanges

  • Centralized Management: Monitor all strategies from a single dashboard

How It Works

The Plugin Model

When you build a strategy with goalgo, it compiles into a standalone executable. This executable doesn’t start trading immediately when launched. Instead:

  1. Handshake: The strategy performs a handshake with the host application

  2. Serving: It starts an RPC server on localhost

  3. Control: The host connects and sends commands (Start, Stop, Configure)

This separation means the host application controls when and how strategies run.

Strategy Lifecycle

Every strategy goes through these phases:

Communication Flow

Writing a Strategy

Basic Structure

package main
import (
    “time”
    “github.com/frankrap/goalgo”
    “github.com/frankrap/goalgo/algo”
    “github.com/frankrap/goalgo/log”
    “github.com/nntaoli-project/goex”
)
// Define your strategy struct
type MyStrategy struct {
    algo.GoExStrategy // Embed the exchange adapter
    // Define configurable parameters using struct tags
    // Format: option:”Display Name,Default Value”
    Symbol   string  `option:”Trading Symbol,BTC/USDT”`
    Interval int     `option:”Loop Interval (seconds),10”`
    Amount   float64 `option:”Trade Amount,0.001”`
}
// Init runs once before the main loop starts
func (s *MyStrategy) Init() error {
    log.Info(”Strategy initializing...”)
    
    // Check account balance
    account, err := s.Exchange.GetAccount()
    if err != nil {
        return err
    }
    log.Infof(”Available balance: %v”, account.Balance)
    
    return nil
}
// Run contains your main trading logic
func (s *MyStrategy) Run() error {
    log.Info(”Strategy started!”)
    // Main loop - runs until the host sends a stop command
    for s.IsRunning() {
        
        // Fetch current market data
        ticker, err := s.Exchange.GetTicker(goex.BTC_USDT)
        if err != nil {
            log.Errorf(”Failed to get ticker: %v”, err)
        } else {
            log.Infof(”Current price: %v”, ticker.Last)
            
            // Your trading logic here...
        }
        // Sleep between iterations
        time.Sleep(time.Duration(s.Interval) * time.Second)
    }
    log.Info(”Strategy stopped gracefully”)
    return nil
}
func main() {
    // Hand control to the goalgo framework
    goalgo.Serve(&MyStrategy{})
}

Configurable Parameters

Use struct tags to define parameters that appear in the host UI:

type MyStrategy struct {
    algo.GoExStrategy
    // String parameter
    Symbol string `option:”Symbol,ETH/USDT”`
    
    // Integer parameter  
    MaxTrades int `option:”Max Daily Trades,10”`
    
    // Float parameter
    StopLoss float64 `option:”Stop Loss %,2.5”`
    
    // Boolean parameter
    EnableShorts bool `option:”Enable Short Selling,false”`
}

The framework uses Go reflection to automatically:

  • Extract parameter names and defaults from tags

  • Generate a configuration UI in the host application

  • Populate your struct fields when the host sends options

Persistent State

Strategies can save and load state that survives restarts:

func (s *MyStrategy) Init() error {
    // Load saved position from last run
    value, err := goalgo.GetValue(”position”)
    if err != nil {
        return err
    }
    s.currentPosition = value.ToFloat64()
    
    return nil
}
func (s *MyStrategy) Run() error {
    for s.IsRunning() {
        // ... trading logic ...
        
        // Save current position
        goalgo.SetValue(”position”, goalgo.FloatValue(s.currentPosition))
    }
    return nil
}

Handling Commands

The host can send commands to running strategies:

func (s *MyStrategy) Run() error {
    for s.IsRunning() {
        
        // Check for commands from the host
        if cmd := s.GetCommand(); cmd != nil {
            switch cmd.Name {
            case “close_all”:
                s.closeAllPositions()
            case “update_params”:
                // Parameters are updated automatically
                log.Info(”Parameters updated”)
            }
        }
        
        // ... rest of trading logic ...
    }
    return nil
}

Supported Exchanges

Via GoExStrategy (18+ Exchanges)

Embed algo.GoExStrategy to connect to any of these exchanges:

Via BitMEXRStrategy

For BitMEX specifically:

type MyBitMEXStrategy struct {
    algo.BitMEXRStrategy // Embed BitMEX adapter
    
    // Your parameters...
}

Use identifier bitmex for production or bitmex_test for testnet.

Running Strategies

Development Mode

Strategies are typically not run directly from the command line. However, for development and testing:

# Build your strategy
go build -o mystrategy main.go
# Run with required flags
./mystrategy --id=”robot-001” --sid=1 --address=”127.0.0.1:9900”

Required Flags:

  • --id: Unique robot identifier

  • --sid: Session ID for log correlation

  • --address: Host server address (default: 127.0.0.1:9900)

Option Discovery Mode

To see what options your strategy exposes (useful for UI generation):

./mystrategy --id=”^”

This outputs a JSON schema of all configurable parameters and exits.

Production Deployment

In production, the host application manages strategy processes:

  1. Register your compiled strategy binary with the host

  2. Configure parameters through the host UI

  3. The host launches/stops strategies as needed

Logging

The framework provides a structured logging system with centralized aggregation:

import “github.com/frankrap/goalgo/log”
// Simple messages
log.Debug(”Detailed debug info”)
log.Info(”Strategy started”)
log.Warn(”Low balance detected”)
log.Error(”Failed to place order”)
log.Fatal(”Critical failure”)
// Formatted messages
log.Infof(”Price: %v, Volume: %v”, price, volume)
log.Errorf(”Order failed: %v”, err)

Logs are automatically:

  • Displayed locally with timestamps

  • Sent to the host for centralized viewing

  • Tagged with unique IDs for traceability

Project Dependencies

// Core
github.com/hashicorp/go-plugin     // Plugin architecture
github.com/vmihailenco/msgpack     // Binary serialization
github.com/facebookgo/inject       // Dependency injection
// Exchange APIs
github.com/nntaoli-project/goex    // Multi-exchange support
github.com/frankrap/bitmex-api     // BitMEX integration
// Utilities
github.com/sirupsen/logrus         // Logging backend
github.com/Workiva/go-datastructures // Command queue

Quick Start Checklist

  1. ☐ Install Go 1.11+

  2. ☐ Clone the goalgo repository

  3. ☐ Create your strategy struct embedding algo.GoExStrategy

  4. ☐ Define configurable parameters with option:"..." tags

  5. ☐ Implement Init() and Run() methods

  6. ☐ Call goalgo.Serve(&MyStrategy{}) in main

  7. ☐ Build and register with your host application

  8. ☐ Configure and launch from the host UI

package goalgo

Package goalgo base definitions. This file contains the fundamental type definitions and constants shared across the goalgo framework. It defines the core data structures for robot lifecycle management and external command interfacing, which are universally used by the strategy implementations.

type RobotStatus int8

RobotStatus represents the current lifecycle state of a trading robot. It is an enumerated integer type used to track whether the robot is running, stopped, starting up, or in an error state. This status is critical for controlling the main execution loop and ensuring safe state transitions.

const (
	 
	RobotStatusDisabled RobotStatus = iota
	 
	RobotStatusStopped
	 
	RobotStatusStarting
	 
	RobotStatusRunning
	 
	RobotStatusRequested
	 
	RobotStatusError
)

This block defines the exhaustive list of possible RobotStatus values.

  • RobotStatusDisabled (0): The robot is configured but disabled from running.

  • RobotStatusStopped (1): The robot is currently inactive and not processing market data.

  • RobotStatusStarting (2): The robot is in the initialization phase.

  • RobotStatusRunning (3): The robot is fully active, processing events and executing trades.

  • RobotStatusRequested (4): A stop signal has been received, and the robot is preparing to shut down.

  • RobotStatusError (5): The robot has encountered a critical failure and halted execution.

type Command struct {
	Name  string `json:”name”`
	Value string `json:”value”`
}

Command represents an external instruction sent to the strategy. It is a simple key-value structure used to trigger specific actions or updates dynamically while the robot is running. These commands are typically serialized as JSON and typically handled via the QueueCommand method in the strategy base.

File: basestrategy.go

package goalgo

Package goalgo provides the foundational framework for building trading strategies and robots. It defines the core interfaces, life-cycle management, and utility functions required to interact with the trading platform. This package abstracts away the complexities of inter-process communication, configuration management, and execution control, allowing developers to focus on the specific logic of their trading strategies.

import (
	“encoding/json”
	“fmt”
	“reflect”
	“strings”
	“sync”
	“github.com/frankrap/goalgo/log”
	“runtime/debug”
	“github.com/Workiva/go-datastructures/queue”
	“github.com/hashicorp/go-plugin”
)
const (
	 
	OptionTag = “option”
)

This import block includes necessary libraries for the strategy framework.

  • encoding/json: Used for parsing command messages and handling JSON data.

  • fmt, strings: Standard string formatting and manipulation utilities.

  • reflect: Heavily used to dynamically inspect strategy structs for configuration options.

  • sync: Provides synchronization primitives (RWMutex) for thread-safe state access.

  • log: Custom logging package for the framework.

  • runtime/debug: Used to capture stack traces ensuring robust error reporting during crashes.

  • go-datastructures/queue: A high-performance queue used for managing incoming commands.

  • hashicorp/go-plugin: The plugin system that allows strategies to run as separate processes.

type OptionInfo struct {
	Name         string      `json:”name”`
	Description  string      `json:”description”`
	Type         string      `json:”type”`
	Value        interface{} `json:”value”`
	DefaultValue interface{} `json:”default_value”`
}

OptionInfo describes a single configuration parameter available in a strategy. It captures metadata such as the parameter’s name, a human-readable description, its data type, current value, and default value. This structure is primarily used to export the strategy’s configuration schema to the UI or controlling system, allowing users to adjust settings dynamically.

type AfterOptionsChanged interface {
	AfterOptionsChanged()
}

AfterOptionsChanged is an optional interface that strategies can implement. If a strategy struct implements this interface, the AfterOptionsChanged method will be automatically invoked immediately after new configuration options have been applied via SetOptions. This provides a hook for the strategy to re-calculate internal state, validate new settings, or reset components based on the updated configuration.

type BaseStrategy struct {
	self         interface{}
	mutex        sync.RWMutex
	commandQueue queue.Queue
	status       RobotStatus
	proxy string
}

BaseStrategy is the foundational struct that all specific trading strategies should embed. It implements common functionality required by the Strategy interface, such as state management, options handling, command queuing, and proxy configuration. By embedding BaseStrategy, a concrete strategy gains robust implementations of these features, reducing boilerplate and ensuring consistent behavior across different robots. fields:

  • self: A reference to the concrete strategy instance, used for reflection.

  • mutex: Protects concurrent access to the strategy’s state.

  • commandQueue: Buffers external commands destined for the strategy.

  • status: Tracks the current lifecycle state of the robot (Running, Stopped, etc.).

  • proxy: Stores the configured proxy address (e.g., SOCKS5) for network requests.

func (s *BaseStrategy) SetSelf(self Strategy) {
	s.self = self.(interface{})
}

SetSelf initializes the reference to the concrete strategy instance. This linkage is crucial for the reflection-based mechanisms in BaseStrategy (like GetOptions and SetOptions) to inspect and modify the fields of the actual strategy struct that embeds BaseStrategy. This method must be called during initialization.

func (s *BaseStrategy) SetProxy(proxy string) {
	s.proxy = proxy
}

SetProxy sets the network proxy address for the strategy. This configuration is typically passed down from the platform settings. Note that simply setting this string does not automatically apply it to all network clients; helper functions like proxy.SOCKS5Client must be utilized by the strategy logic to actually route traffic through this proxy.

func (s *BaseStrategy) GetProxy() string {
	return s.proxy
}

GetProxy retrieves the currently configured proxy address. Strategies can use this method to access the proxy settings when creating network connections or clients, ensuring they adhere to the user’s networking preferences.

func (s *BaseStrategy) GetState() RobotStatus {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return s.status
}

GetState returns the current operational status of the robot safely. It uses a read lock to ensure that checking the status is thread-safe, protecting against race conditions where the status might be updating (e.g., changing from Running to Stopped) simultaneously.

func (s *BaseStrategy) IsRunning() bool {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return s.status == RobotStatusRunning
}

IsRunning checks if the robot is currently in the ‘Running’ state. This is a convenience method wrapping GetState, commonly used in the main loop of a strategy to determine whether it should continue processing market data and executing trades or perform a graceful shutdown.

func (s *BaseStrategy) GetOptions() (optionMap map[string]*OptionInfo) {
	 
	optionMap = map[string]*OptionInfo{}
	if s.self == nil {
		return
	}
	val := reflect.ValueOf(s.self)
	 
	if val.Kind() == reflect.Ptr && val.Elem().Kind() == reflect.Struct {
		val = val.Elem()
	} else {
		return
	}
	valNumFields := val.NumField()
	for i := 0; i < valNumFields; i++ {
		field := val.Field(i)
		fieldKind := field.Kind()
		if fieldKind == reflect.Ptr && field.Elem().Kind() == reflect.Struct {
			continue
		}
		typeField := val.Type().Field(i)
		fieldName := typeField.Name
		tag := typeField.Tag
		if !field.CanInterface() {
			continue
		}
		option := tag.Get(OptionTag)
		if option == “” {
			continue
		}
		var description string
		var defaultValueString string
		index := strings.Index(option, “,”)
		 
		if index != -1 {
			description = option[0:index]
			defaultValueString = option[index+1:]
		} else {
			description = option
		}
		value := field.Interface()
		defaultValue := s.getDefaultValue(fieldKind, defaultValueString)
		optionMap[fieldName] = &OptionInfo{
			Name:         fieldName,
			Description:  description,
			Type:         typeField.Type.String(),
			Value:        value,
			DefaultValue: defaultValue,
		}
		 
	}
	return
}
func (s *BaseStrategy) getDefaultValue(kind reflect.Kind, value string) interface{} {
	switch kind {
	case reflect.Bool:
		return ToBool(value)
	case reflect.String:
		return value
	case reflect.Int:
		return ToInt(value)
	case reflect.Int8:
		return int8(ToInt(value))
	case reflect.Int16:
		return int16(ToInt(value))
	case reflect.Int32:
		return int32(ToInt(value))
	case reflect.Int64:
		return ToInt64(value)
	case reflect.Uint:
		return uint(ToInt(value))
	case reflect.Uint8:
		return uint8(ToInt(value))
	case reflect.Uint16:
		return uint16(ToInt(value))
	case reflect.Uint32:
		return uint32(ToInt(value))
	case reflect.Uint64:
		return uint64(ToInt64(value))
	case reflect.Float32:
		return ToFloat32(value)
	case reflect.Float64:
		return ToFloat(value)
	}
	return 0
}

GetOptions inspects the specific strategy struct to detect configurable parameters. It uses Go reflection to iterate over the fields of the strategy instance (self). For each field, it checks for the “option” structural tag. If present, it parses the tag to extract the parameter description and default value. It creates an OptionInfo object for each valid field, mapping the internal field name to its user-facing metadata and current value. This allows the system to automatically generate a settings UI without manual configuration code.

func (s *BaseStrategy) SetOptions(options map[string]interface{}) plugin.BasicError {
	log.Info(”SetOptions”)
	if len(options) == 0 {
		return plugin.BasicError{}
	}
	rawOptions := s.GetOptions()
	 
	val := reflect.ValueOf(s.self)
	 
	if val.Kind() == reflect.Ptr && val.Elem().Kind() == reflect.Struct {
		val = val.Elem()
	} else {
		return plugin.BasicError{}
	}
	for name, value := range options {
		var fieldName string
		if ipi, ok := rawOptions[name]; !ok {
			continue
		} else {
			fieldName = ipi.Name
		}
		 
		v := val.FieldByName(fieldName)
		if !v.IsValid() {
			continue
		}
		switch v.Kind() {
		default:
			fmt.Printf(”Error Kind: %v\n”, v.Kind())
		case reflect.Bool:
			v.SetBool(ToBool(value))
		case reflect.String:
			v.SetString(value.(string))
		case reflect.Int:
			v.SetInt(ToInt64(value))
		case reflect.Int8:
			v.SetInt(ToInt64(value))
		case reflect.Int16:
			v.SetInt(ToInt64(value))
		case reflect.Int32:
			v.SetInt(ToInt64(value))
		case reflect.Int64:
			v.SetInt(ToInt64(value))
		case reflect.Uint:
			v.SetUint(ToUint64(value))
		case reflect.Uint8:
			v.SetUint(ToUint64(value))
		case reflect.Uint16:
			v.SetUint(ToUint64(value))
		case reflect.Uint32:
			v.SetUint(ToUint64(value))
		case reflect.Uint64:
			v.SetUint(ToUint64(value))
		case reflect.Float32:
			v.SetFloat(ToFloat(value))
		case reflect.Float64:
			v.SetFloat(ToFloat(value))
			 
			 
		}
	}
	 
	if v, ok := s.self.(AfterOptionsChanged); ok {
		v.AfterOptionsChanged()
	}
	return plugin.BasicError{}
}

SetOptions dynamically updates the fields of the strategy struct based on a provided map. It matches keys in the options map to field names in the strategy using the previously discovered OptionInfo metadata. It uses reflection to safely set the values of these fields, performing necessary type conversions (e.g., string to int/float). After setting the values, it checks if the strategy implements AfterOptionsChanged and invokes it, allowing the strategy to react to the configuration update.

func (s *BaseStrategy) QueueCommand(command string) plugin.BasicError {
	cmd := Command{}
	err := json.Unmarshal([]byte(command), &cmd)
	if err != nil {
		return plugin.BasicError{}
	}
	_ = s.commandQueue.Put(&cmd)
	return plugin.BasicError{}
}

QueueCommand parses a JSON-encoded command string and adds it to the command queue. It attempts to unmarshal the input string into a Command object. If successful, the command is placed into a thread-safe queue for processing by the strategy loop. This is the primary entry point for external control messages sent to the robot.

func (s *BaseStrategy) QueueCommandRaw(command Command) {
	cmd := command
	_ = s.commandQueue.Put(&cmd)
}

QueueCommandRaw adds a pre-constructed Command object directly to the queue. It bypasses the JSON parsing step and is useful for internal command generation or advanced testing scenarios where commands are already in struct form.

func (s *BaseStrategy) GetCommand() *Command {
	_, err := s.commandQueue.Peek()
	if err != nil {
		return nil
	}
	result, err := s.commandQueue.Get(1)
	if err != nil {
		return nil
	}
	if len(result) != 1 {
		return nil
	}
	return result[0].(*Command)
}

GetCommand retrieves the next pending command from the queue. It is a non-blocking check (or minimally blocking depending on queue implementation nuances, here it peeks first). If a command is available, it is dequeued and returned; otherwise, it returns nil. Strategies typically poll this method in their main execution loop to handle real-time interactions.

func (s *BaseStrategy) Start() plugin.BasicError {
	go s.run()
	return plugin.BasicError{}
}

Start initiates the strategy execution in a separate goroutine. It returns immediately with a success status, effectively launching the run loop in the background. This method is the standardized trigger used by the plugin host to start the robot.

func (s *BaseStrategy) run() {
	defer func() {
		if err := recover(); err != nil {
			log.Errorf(”Run error: %v”, err)
			s.status = RobotStatusStopped
		}
	}()
	 
	if s.self == nil {
		log.Error(”The strategy this is nil”)
		s.status = RobotStatusStopped
		s.updateStatus(s.status)
		return
	}
	strategy, ok := s.self.(Strategy)
	if !ok {
		log.Error(”The strategy does not implement Strategy”)
		s.status = RobotStatusStopped
		s.updateStatus(s.status)
		return
	}
	var rError error
	var pErr plugin.BasicError
	 
	func() {
		defer func() {
			if r := recover(); r != nil {
				rError = fmt.Errorf(”%v”, r)
			}
		}()
		client := GetClient()
		options, err := client.GetRobotOptions(”“, id)
		if err != nil {
			log.Errorf(”GetRobotOptions error: %v”, err)
		}
		mOptions := map[string]interface{}{}
		for _, v := range options {
			mOptions[v.Key] = v.Value
		}
		pErr = strategy.SetOptions(mOptions)
	}()
	if rError != nil {
		log.Errorf(”Setup error: %v”, rError)
		s.status = RobotStatusError
		s.updateStatus(s.status)
		return
	}
	if pErr.Error() != “” {
		log.Errorf(”SetOptions error: %v”, pErr.Error())
		s.status = RobotStatusError
		s.updateStatus(s.status)
		return
	}
	 
	func() {
		defer func() {
			if r := recover(); r != nil {
				rError = fmt.Errorf(”%v”, r)
			}
		}()
		client := GetClient()
		exchanges, err := client.GetRobotExchangeInfo(”“, id)
		if err != nil {
			log.Errorf(”GetRobotExchangeInfo error: %v”, err)
		} else {
			var params []ExchangeParams
			for _, ex := range exchanges {
				params = append(params, ExchangeParams{
					Label:     ex.Label,
					Name:      ex.Name,
					AccessKey: ex.AccessKey,
					SecretKey: ex.SecretKey,
				})
			}
			 
			rError = strategy.Setup(params)
			 
		}
	}()
	if rError != nil {
		log.Errorf(”Setup error: %v”, rError)
		s.status = RobotStatusError
		s.updateStatus(s.status)
		return
	}
	func() {
		defer func() {
			if r := recover(); r != nil {
				rError = fmt.Errorf(”%v”, r)
			}
		}()
		rError = strategy.Init()
	}()
	if rError != nil {
		log.Errorf(”Init error: %v”, rError)
		s.status = RobotStatusError
		s.updateStatus(s.status)
		return
	}
	s.status = RobotStatusRunning
	func() {
		defer func() {
			if r := recover(); r != nil {
				rError = fmt.Errorf(”%v”, r)
				log.Errorf(”Run error: stack=%v”, string(debug.Stack()))
			}
		}()
		rError = strategy.Run()
	}()
	 
	if rError != nil {
		s.status = RobotStatusError
		log.Errorf(”Run error: %v”, rError)
	} else {
		s.status = RobotStatusStopped
		log.Info(”Stopped”)
	}
	 
	s.updateStatus(s.status)
}
func (s *BaseStrategy) updateStatus(status RobotStatus) {
	client := GetClient()
	client.UpdateStatus(id, status)
}

run is the core execution loop and lifecycle manager for the strategy. It performs the following critical sequences:

  1. Safety Checks: Ensures a valid strategy instance is bound.

  2. Setup: Fetches remote options and exchange configurations from the client/host, applying them to the strategy.

  3. Initialization: Calls the user-defined Init() method.

  4. Execution: Sets the status to Running and enters the user-defined Run() method. This is where the main strategy logic resides and blocks until finished.

  5. Error Handling: Catches panics via recover, logs errors, and updates the robot status to specific error states if setup or execution fails.

  6. Cleanup: Updates the status to Stopped upon normal completion.

func (s *BaseStrategy) Stop() plugin.BasicError {
	log.Info(”OnStop”)
	s.mutex.Lock()
	defer s.mutex.Unlock()
	if s.status == RobotStatusStopped {
		return plugin.BasicError{}
	}
	if s.status != RobotStatusRunning {
		return plugin.BasicError{Message: “State error”}
	}
	s.status = RobotStatusRequested
	return plugin.BasicError{}
}

Stop requests a graceful shutdown of the strategy. It transitions the robot status to ‘Requested’, signaling the running loop (checked via IsRunning) to terminate its operations safely. If the robot is already stopped or not in a running state, it handles these cases appropriately to prevent state corruption.

func (s *BaseStrategy) Pause() plugin.BasicError {
	return plugin.BasicError{}
}

Pause is a placeholder for a functionality to temporarily suspend strategy execution. Currently, it returns an empty error (success) but performs no action. Future implementations could freeze state updates or trade execution without full termination.

func (s *BaseStrategy) GetValue(key string) (Value, error) {
	return GetValue(key)
}

GetValue retrieves a global shared value by key from the goalgo environment. It acts as a proxy to the global GetValue function, allowing strategies to access shared data or configuration set elsewhere in the system.

func (s *BaseStrategy) SetValue(key string, value Value) error {
	return SetValue(key, value)
}

SetValue stores a global shared value associated with a key in the goalgo environment. It delegates to the global SetValue function. This mechanism is useful for inter-strategy communication or persisting state that needs to survive restarts if supported.

func (s *BaseStrategy) UpdateStat(name string, value []byte) error {
	return GetClient().UpdateStat(name, value)
}

UpdateStat pushes a named statistic update to the remote client/host. It enables real-time monitoring of custom metrics (like PnL, open positions, etc.) by sending the data payload through the client interface.

File: global.go

package goalgo
var (
	id      string
	sid     int
	address string
)

Global configuration variables for the robot instance.

  • id: A string identifier for the robot, potentially a UUID or a unique name assigned by the platform.

  • sid: A numerical strategy ID usually associated with the specific strategy logic version.

  • address: The network address (host:port) of the control server or message broker that the robot communicates with. These variables are typically populated during the initialization phase (e.g., via flags or environment variables) and are used throughout the application to identify the robot context in API calls and log messages.

File: grpc_broker_client.go

package goalgo

Package goalgo provides the gRPC client-side implementation for the RobotCtl service, which is the core communication layer enabling algorithmic trading robots to interact with a central broker server. This file contains the client stub code that is typically generated from a Protocol Buffers service definition (grpc_broker.proto) and allows trading strategy implementations to make remote procedure calls to the broker server.

The RobotCtl service provides several essential capabilities that trading robots require during their lifecycle. These include retrieving runtime configuration options, accessing exchange credentials and settings, reading and writing persistent values for state management, logging operational messages to a central location, updating the robot’s operational status, and reporting statistical data about trading performance.

This client implementation follows the standard gRPC Go pattern where an interface defines the available RPC methods, a private struct holds the connection, and each method wraps the underlying gRPC Invoke mechanism to send requests and receive responses. The design allows trading strategies to communicate with the broker server without needing to understand the underlying protocol details, providing a clean abstraction layer between the trading logic and the network communication.

import (
	context “golang.org/x/net/context”
	grpc “google.golang.org/grpc”
)

The import block brings in the essential dependencies required for gRPC client functionality. The context package from golang.org/x/net/context provides the Context type which is used throughout gRPC to carry deadlines, cancellations, and request-scoped values across API boundaries. The grpc package from google.golang.org/grpc provides the core gRPC functionality including the ClientConn type for managing connections and the CallOption type for configuring individual RPC calls.

These imports are aliased to their package names (context and grpc) which is a common pattern in generated gRPC code to ensure consistency and avoid naming conflicts with any local definitions that might share the same names.

var _ context.Context
var _ grpc.ClientConn

These blank identifier variable declarations serve as compile-time assertions that ensure the imported packages are actually used and prevent the Go compiler from reporting “imported and not used” errors. This is a standard pattern in generated gRPC code because while the imports are essential for the type definitions and functionality they provide, there may not always be explicit references to package- level symbols that the compiler can detect statically.

The context.Context type is used extensively in the RPC method signatures for request context management, and grpc.ClientConn is the fundamental type representing the connection to the gRPC server. By assigning zero values of these types to the blank identifier, the code guarantees compilation will fail if these packages are not properly available, while not introducing any runtime overhead.

const _ = grpc.SupportPackageIsVersion4

This constant declaration is a compile-time version compatibility check that ensures this generated client code is compatible with the version of the gRPC package being used. The grpc.SupportPackageIsVersion4 constant is defined in the grpc package and represents a specific API version. If the grpc package is updated to a version that no longer defines this constant or defines it differently, compilation will fail, alerting developers to a potential incompatibility between the generated code and the runtime library.

This mechanism is particularly important in gRPC-based systems where the generated code and runtime library must remain in sync to ensure proper serialization, deserialization, and transport behavior.

type RobotCtlClient interface {
	GetRobotOptions(ctx context.Context, in *RobotOptionsRequest, opts ...grpc.CallOption) (*RobotOptionsReply, error)
	GetRobotExchangeInfo(ctx context.Context, in *RobotExchangeInfoRequest, opts ...grpc.CallOption) (*RobotExchangeInfoReply, error)
	GetValue(ctx context.Context, in *GetValueRequest, opts ...grpc.CallOption) (*GetValueReply, error)
	SetValue(ctx context.Context, in *SetValueRequest, opts ...grpc.CallOption) (*SetValueReply, error)
	Log(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*LogReply, error)
	UpdateStatus(ctx context.Context, in *UpdateStatusRequest, opts ...grpc.CallOption) (*UpdateStatusReply, error)
	UpdateStat(ctx context.Context, in *StatRequest, opts ...grpc.CallOption) (*StatReply, error)
}

RobotCtlClient defines the public interface for the RobotCtl gRPC service client. This interface exposes all the remote procedure calls that a trading robot can make to the broker server, providing a clean contract that client code can program against. By defining this as an interface, the code follows Go best practices for dependency injection and testability, allowing mock implementations to be substituted during unit testing.

The interface defines seven methods corresponding to the seven RPC endpoints defined in the RobotCtl service. Each method follows the standard gRPC unary RPC pattern, accepting a context for request lifecycle management, a request message containing the input parameters, and optional CallOptions for configuring the specific call. Each method returns a reply message and an error, where a nil error indicates success.

GetRobotOptions retrieves the runtime configuration parameters for a specific robot, allowing the robot to access its operational settings at startup or during execution. GetRobotExchangeInfo retrieves the exchange connection details including credentials, enabling the robot to connect to trading exchanges. GetValue and SetValue provide key-value persistence for storing and retrieving arbitrary data that survives robot restarts. Log allows the robot to send log messages to a central logging facility. UpdateStatus reports the robot’s current operational state. UpdateStat sends statistical metrics about the robot’s trading performance.

type robotCtlClient struct {
	cc *grpc.ClientConn
}

The robotCtlClient struct is the private concrete implementation of the RobotCtlClient interface. It encapsulates a pointer to a grpc.ClientConn which represents the underlying network connection to the gRPC server. This connection is established before creating the client and is reused across all RPC calls, enabling connection pooling, load balancing, and other transport-level optimizations that gRPC provides.

The struct is unexported (lowercase first letter) because external code should only interact with the RobotCtlClient interface, not with the concrete implementation directly. This encapsulation ensures that the implementation details can change without affecting client code and promotes the use of the interface for better testability and flexibility.

func NewRobotCtlClient(cc *grpc.ClientConn) RobotCtlClient {
	return &robotCtlClient{cc}
}

NewRobotCtlClient is the constructor function that creates a new RobotCtlClient instance from an established gRPC connection. This function is the primary entry point for trading robot code that needs to communicate with the broker server. Callers must first establish a gRPC connection using grpc.Dial or similar functions, then pass that connection to this constructor to obtain a usable client.

The function takes a pointer to a grpc.ClientConn representing an active connection to the RobotCtl server and returns an interface type rather than the concrete struct, allowing the implementation to be substituted with mocks or alternative implementations. Internally, it simply wraps the connection in the robotCtlClient struct and returns it, relying on the connection for all subsequent communication with the server.

func (c *robotCtlClient) GetRobotOptions(ctx context.Context, in *RobotOptionsRequest, opts ...grpc.CallOption) (*RobotOptionsReply, error) {
	out := new(RobotOptionsReply)
	err := c.cc.Invoke(ctx, “/goalgo.RobotCtl/GetRobotOptions”, in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

GetRobotOptions is a method on the robotCtlClient that retrieves the runtime configuration options for a trading robot from the broker server. This is typically one of the first calls a robot makes during initialization to obtain its operational parameters such as trading strategy settings, risk limits, and other configurable values.

The method implements the standard gRPC unary RPC pattern. It first allocates a new RobotOptionsReply message to receive the server’s response. It then invokes the underlying gRPC mechanism by calling cc.Invoke with the fully qualified method name “/goalgo.RobotCtl/GetRobotOptions”, passing the input request and output reply messages. The method name follows the gRPC convention of “/package.Service/Method”. Any additional call options provided by the caller are forwarded to the Invoke call. If the invocation fails, the error is returned along with a nil reply; otherwise, the populated reply message is returned with a nil error.

The robotic trading strategy code depends on this method to access its configuration at startup, making it a critical part of the robot initialization sequence.

func (c *robotCtlClient) GetRobotExchangeInfo(ctx context.Context, in *RobotExchangeInfoRequest, opts ...grpc.CallOption) (*RobotExchangeInfoReply, error) {
	out := new(RobotExchangeInfoReply)
	err := c.cc.Invoke(ctx, “/goalgo.RobotCtl/GetRobotExchangeInfo”, in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

GetRobotExchangeInfo retrieves the exchange connection information for a trading robot from the broker server. This information typically includes exchange labels, names, and most importantly, the API credentials (access key and secret key) needed to connect to financial exchanges for trading operations.

The method follows the same implementation pattern as GetRobotOptions. It allocates a RobotExchangeInfoReply to receive the response, invokes the gRPC call with the method path “/goalgo.RobotCtl/GetRobotExchangeInfo”, and returns either the reply on success or an error on failure.

This method is essential for robots that need to interact with multiple exchanges or need to securely obtain their API credentials at runtime rather than having them hardcoded. The broker server acts as a secure credential store, and robots fetch their exchange configuration through this method when they need to establish connections to trading platforms.

func (c *robotCtlClient) GetValue(ctx context.Context, in *GetValueRequest, opts ...grpc.CallOption) (*GetValueReply, error) {
	out := new(GetValueReply)
	err := c.cc.Invoke(ctx, “/goalgo.RobotCtl/GetValue”, in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

GetValue retrieves a stored value from the broker server’s key-value store for a specific robot. This provides a persistent storage mechanism that allows trading robots to save and retrieve arbitrary data that persists across restarts, such as accumulated state, cached calculations, or position information that needs to survive service disruptions.

The method operates like the other client methods, allocating a GetValueReply to receive the response and invoking the gRPC call with the method path “/goalgo.RobotCtl/GetValue”. The request specifies the robot ID and the key to retrieve, and the reply contains the value as a byte slice along with success status and any error messages.

Trading strategies use this method in conjunction with SetValue to maintain state across execution sessions. For example, a robot might store its last processed trade ID, accumulated profit calculations, or market analysis results that are expensive to recompute.

func (c *robotCtlClient) SetValue(ctx context.Context, in *SetValueRequest, opts ...grpc.CallOption) (*SetValueReply, error) {
	out := new(SetValueReply)
	err := c.cc.Invoke(ctx, “/goalgo.RobotCtl/SetValue”, in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

SetValue stores a value in the broker server’s key-value store for a specific robot. This is the write counterpart to GetValue and together they provide a complete persistence mechanism for trading robots to store arbitrary data that survives robot restarts and allows for stateful trading strategies.

The method follows the standard pattern of allocating a SetValueReply for the response and invoking the gRPC call with “/goalgo.RobotCtl/SetValue”. The request includes the robot ID, a key string, and the value as a byte slice, allowing any serializable data to be stored. The reply indicates whether the operation succeeded and includes any relevant messages.

Robots typically use this method to checkpoint their state periodically or after significant events, ensuring that if the robot restarts, it can resume operations from a known state rather than starting fresh. This is particularly important for strategies that track positions, order history, or accumulated metrics.

func (c *robotCtlClient) Log(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*LogReply, error) {
	out := new(LogReply)
	err := c.cc.Invoke(ctx, “/goalgo.RobotCtl/Log”, in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

Log sends a log message from the trading robot to the central broker server for storage and potential display in monitoring interfaces. This provides a centralized logging facility where all robots can send their operational logs, making it easier to monitor and debug robot behavior from a single location.

The method invokes the gRPC call with “/goalgo.RobotCtl/Log” and processes the request which includes session ID, a unique log ID, timestamp, log level, and the message content. The server acknowledges receipt with a reply indicating success and any additional messages.

Centralized logging is crucial in algorithmic trading systems where multiple robots may be running simultaneously and operators need visibility into their behavior. By routing logs through the broker server, the system enables aggregated log viewing, alerting based on log content, and historical log analysis for debugging production issues.

func (c *robotCtlClient) UpdateStatus(ctx context.Context, in *UpdateStatusRequest, opts ...grpc.CallOption) (*UpdateStatusReply, error) {
	out := new(UpdateStatusReply)
	err := c.cc.Invoke(ctx, “/goalgo.RobotCtl/UpdateStatus”, in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

UpdateStatus reports the current operational status of a trading robot to the broker server. This allows the central system to track whether robots are running, stopped, in error states, or in other defined operational modes, enabling monitoring dashboards to display the health of the trading system.

The method invokes the gRPC call with “/goalgo.RobotCtl/UpdateStatus”, sending a request that includes the robot ID and a numeric status code. The meaning of different status codes is defined by the application protocol and might represent states such as “starting”, “running”, “paused”, “stopping”, or “error”.

Status updates are essential for operational monitoring of trading systems. They allow operators to quickly see which robots are active and identify any that have failed or entered unexpected states. The broker server can use this information to trigger alerts, restart failed robots, or take other automated actions.

func (c *robotCtlClient) UpdateStat(ctx context.Context, in *StatRequest, opts ...grpc.CallOption) (*StatReply, error) {
	out := new(StatReply)
	err := c.cc.Invoke(ctx, “/goalgo.RobotCtl/UpdateStat”, in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

UpdateStat sends statistical data about the trading robot’s performance to the broker server. This allows robots to report metrics such as profit and loss, trade counts, win rates, or any other quantitative measures of performance that operators need to monitor the health and effectiveness of their trading strategies.

The method invokes the gRPC call with “/goalgo.RobotCtl/UpdateStat”, sending a request that includes the robot ID, a metric name, and the metric value as a byte slice (allowing for various data types to be serialized). The server acknowledges receipt and stores or processes the statistical data appropriately.

Statistical reporting is a key feature for algorithmic trading operations, enabling real-time performance dashboards, historical analysis, and automated alerting when metrics fall outside expected ranges. Robots typically call this method periodically or after completing trading cycles to report their cumulative or per-period metrics.

File: grpc_broker_messages.go

package goalgo

Package goalgo contains the Protocol Buffer message type definitions for the RobotCtl gRPC service. This file is generated from the grpc_broker.proto file and defines all the request and reply message structures used for communication between trading robots and the broker server.

The messages defined here form the data contract for the RobotCtl service, which provides algorithmic trading robots with essential capabilities including configuration management, exchange credential retrieval, key-value persistence, logging, status reporting, and statistics collection. Each RPC method in the service has a corresponding request message containing the input parameters and a reply message containing the response data.

This generated code includes not only the message struct definitions but also the methods required by the Protocol Buffer runtime for serialization, deserialization, and reflection. Each message type has methods for resetting to zero state, converting to string representation, accessing field values safely, and handling unknown fields for forward compatibility.

import (
	fmt “fmt”
	proto “github.com/golang/protobuf/proto”
	math “math”
)

The import block brings in the essential dependencies required for Protocol Buffer functionality. The fmt package provides string formatting used in text representations. The proto package from github.com/golang/protobuf/proto provides the core Protocol Buffer runtime including serialization, deserialization, and type registration. The math package provides constants and functions used in numeric conversions.

These imports are aliased to ensure consistent naming and avoid conflicts with any local definitions that might share the same names.

var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

These reference variables are blank identifier assignments that suppress “imported and not used” errors from the Go compiler. The protobuf generator includes these to ensure the imports are present even if they are not directly referenced in the generated code. Each variable is assigned a zero value of a function or constant from the respective package, guaranteeing that removing these imports would cause a compilation error.

const _ = proto.ProtoPackageIsVersion2

This constant declaration is a compile-time version compatibility check that ensures this generated code is compatible with the version of the proto package being used. The proto.ProtoPackageIsVersion2 constant represents a specific API version of the Protocol Buffers Go runtime. If the proto package is updated to an incompatible version, compilation will fail, alerting developers to regenerate the protobuf code.

type RobotOptionsRequest struct {
	Uid                  string   `protobuf:”bytes,1,opt,name=uid,proto3” json:”uid,omitempty”`
	RobotId              string   `protobuf:”bytes,2,opt,name=robot_id,json=robotId,proto3” json:”robot_id,omitempty”`
	XXX_NoUnkeyedLiteral struct{} `json:”-”`
	XXX_unrecognized     []byte   `json:”-”`
	XXX_sizecache        int32    `json:”-”`
}

RobotOptionsRequest represents a request to retrieve the configuration options for a specific trading robot. This message is sent by robots during initialization or when they need to refresh their configuration. The Uid field identifies the user who owns the robot, while the RobotId field identifies the specific robot instance.

The struct includes three internal fields prefixed with XXX_ which are used by the Protocol Buffer runtime for handling unknown fields, caching serialized sizes, and preventing unkeyed literal initialization. These fields are excluded from JSON serialization and should not be accessed directly by application code.

func (m *RobotOptionsRequest) Reset()         { *m = RobotOptionsRequest{} }

Reset clears all fields of the RobotOptionsRequest message by replacing the receiver with a new zero-value instance. This method is required by the proto.Message interface and is used when reusing message objects to avoid memory allocation overhead.

func (m *RobotOptionsRequest) String() string { return proto.CompactTextString(m) }

String returns a human-readable text representation of the RobotOptionsRequest message using the Protocol Buffer compact text format. This is useful for debugging and logging purposes.

func (*RobotOptionsRequest) ProtoMessage()    {}

ProtoMessage is a marker method that identifies this type as a Protocol Buffer message. It enables the type to be used with the proto package’s reflection and serialization functions.

func (*RobotOptionsRequest) Descriptor() ([]byte, []int) {
	return fileDescriptor_802e9beed3ec3b28, []int{0}
}
func (m *RobotOptionsRequest) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_RobotOptionsRequest.Unmarshal(m, b)
}
func (m *RobotOptionsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_RobotOptionsRequest.Marshal(b, m, deterministic)
}
func (m *RobotOptionsRequest) XXX_Merge(src proto.Message) {
	xxx_messageInfo_RobotOptionsRequest.Merge(m, src)
}
func (m *RobotOptionsRequest) XXX_Size() int {
	return xxx_messageInfo_RobotOptionsRequest.Size(m)
}
func (m *RobotOptionsRequest) XXX_DiscardUnknown() {
	xxx_messageInfo_RobotOptionsRequest.DiscardUnknown(m)
}
var xxx_messageInfo_RobotOptionsRequest proto.InternalMessageInfo

Descriptor returns the raw Protocol Buffer descriptor bytes and the path indices identifying this message type within the file descriptor. This is used by the proto package for reflection and dynamic message handling.

func (m *RobotOptionsRequest) GetUid() string {
	if m != nil {
		return m.Uid
	}
	return “”
}

GetUid safely retrieves the Uid field value from the RobotOptionsRequest message. If the receiver is nil, it returns an empty string to prevent nil pointer panics. This getter pattern is standard for Protocol Buffer generated code and allows safe field access without explicit nil checks.

func (m *RobotOptionsRequest) GetRobotId() string {
	if m != nil {
		return m.RobotId
	}
	return “”
}

GetRobotId safely retrieves the RobotId field value from the RobotOptionsRequest message. If the receiver is nil, it returns an empty string. The robot ID is used to identify the specific robot instance whose options are being requested.

type RobotOption struct {
	Key                  string   `protobuf:”bytes,1,opt,name=key,proto3” json:”key,omitempty”`
	Value                string   `protobuf:”bytes,2,opt,name=value,proto3” json:”value,omitempty”`
	XXX_NoUnkeyedLiteral struct{} `json:”-”`
	XXX_unrecognized     []byte   `json:”-”`
	XXX_sizecache        int32    `json:”-”`
}
func (m *RobotOption) Reset()         { *m = RobotOption{} }
func (m *RobotOption) String() string { return proto.CompactTextString(m) }
func (*RobotOption) ProtoMessage()    {}
func (*RobotOption) Descriptor() ([]byte, []int) {
	return fileDescriptor_802e9beed3ec3b28, []int{1}
}
func (m *RobotOption) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_RobotOption.Unmarshal(m, b)
}
func (m *RobotOption) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_RobotOption.Marshal(b, m, deterministic)
}
func (m *RobotOption) XXX_Merge(src proto.Message) {
	xxx_messageInfo_RobotOption.Merge(m, src)
}
func (m *RobotOption) XXX_Size() int {
	return xxx_messageInfo_RobotOption.Size(m)
}
func (m *RobotOption) XXX_DiscardUnknown() {
	xxx_messageInfo_RobotOption.DiscardUnknown(m)
}
var xxx_messageInfo_RobotOption proto.InternalMessageInfo

RobotOption represents a single configuration option as a key-value pair. Options are used to configure trading robot behavior and are typically set through a web interface or API. The Key field contains the option name and the Value field contains the option setting as a string.

func (m *RobotOption) GetKey() string {
	if m != nil {
		return m.Key
	}
	return “”
}

GetKey safely retrieves the Key field value from the RobotOption message. The key represents the name or identifier of the configuration option.

func (m *RobotOption) GetValue() string {
	if m != nil {
		return m.Value
	}
	return “”
}

GetValue safely retrieves the Value field value from the RobotOption message. The value contains the configuration setting as a string representation.

type RobotOptionsReply struct {
	Options              []*RobotOption `protobuf:”bytes,1,rep,name=options,proto3” json:”options,omitempty”`
	XXX_NoUnkeyedLiteral struct{}       `json:”-”`
	XXX_unrecognized     []byte         `json:”-”`
	XXX_sizecache        int32          `json:”-”`
}
func (m *RobotOptionsReply) Reset()         { *m = RobotOptionsReply{} }
func (m *RobotOptionsReply) String() string { return proto.CompactTextString(m) }
func (*RobotOptionsReply) ProtoMessage()    {}
func (*RobotOptionsReply) Descriptor() ([]byte, []int) {
	return fileDescriptor_802e9beed3ec3b28, []int{2}
}
func (m *RobotOptionsReply) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_RobotOptionsReply.Unmarshal(m, b)
}
func (m *RobotOptionsReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_RobotOptionsReply.Marshal(b, m, deterministic)
}
func (m *RobotOptionsReply) XXX_Merge(src proto.Message) {
	xxx_messageInfo_RobotOptionsReply.Merge(m, src)
}
func (m *RobotOptionsReply) XXX_Size() int {
	return xxx_messageInfo_RobotOptionsReply.Size(m)
}
func (m *RobotOptionsReply) XXX_DiscardUnknown() {
	xxx_messageInfo_RobotOptionsReply.DiscardUnknown(m)
}
var xxx_messageInfo_RobotOptionsReply proto.InternalMessageInfo

RobotOptionsReply contains the response to a GetRobotOptions RPC call. It includes a repeated field of RobotOption messages representing all configuration options for the requested robot. The Options slice allows multiple key-value pairs to be returned in a single response.

func (m *RobotOptionsReply) GetOptions() []*RobotOption {
	if m != nil {
		return m.Options
	}
	return nil
}

GetOptions safely retrieves the Options slice from the RobotOptionsReply message. Returns nil if the receiver is nil. The returned slice contains all configuration options for the robot.

type RobotExchangeInfoRequest struct {
	Uid                  string   `protobuf:”bytes,1,opt,name=uid,proto3” json:”uid,omitempty”`
	RobotId              string   `protobuf:”bytes,2,opt,name=robot_id,json=robotId,proto3” json:”robot_id,omitempty”`
	XXX_NoUnkeyedLiteral struct{} `json:”-”`
	XXX_unrecognized     []byte   `json:”-”`
	XXX_sizecache        int32    `json:”-”`
}
func (m *RobotExchangeInfoRequest) Reset()         { *m = RobotExchangeInfoRequest{} }
func (m *RobotExchangeInfoRequest) String() string { return proto.CompactTextString(m) }
func (*RobotExchangeInfoRequest) ProtoMessage()    {}
func (*RobotExchangeInfoRequest) Descriptor() ([]byte, []int) {
	return fileDescriptor_802e9beed3ec3b28, []int{3}
}
func (m *RobotExchangeInfoRequest) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_RobotExchangeInfoRequest.Unmarshal(m, b)
}
func (m *RobotExchangeInfoRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_RobotExchangeInfoRequest.Marshal(b, m, deterministic)
}
func (m *RobotExchangeInfoRequest) XXX_Merge(src proto.Message) {
	xxx_messageInfo_RobotExchangeInfoRequest.Merge(m, src)
}
func (m *RobotExchangeInfoRequest) XXX_Size() int {
	return xxx_messageInfo_RobotExchangeInfoRequest.Size(m)
}
func (m *RobotExchangeInfoRequest) XXX_DiscardUnknown() {
	xxx_messageInfo_RobotExchangeInfoRequest.DiscardUnknown(m)
}
var xxx_messageInfo_RobotExchangeInfoRequest proto.InternalMessageInfo
func (m *RobotExchangeInfoRequest) GetUid() string {
	if m != nil {
		return m.Uid
	}
	return “”
}
func (m *RobotExchangeInfoRequest) GetRobotId() string {
	if m != nil {
		return m.RobotId
	}
	return “”
}

RobotExchangeInfoRequest represents a request to retrieve exchange connection information for a specific trading robot. This includes API credentials needed to connect to cryptocurrency or financial exchanges. The Uid and RobotId fields identify the user and robot requesting the information.

type RobotExchangeInfo struct {
	Label                string   `protobuf:”bytes,1,opt,name=label,proto3” json:”label,omitempty”`
	Name                 string   `protobuf:”bytes,2,opt,name=name,proto3” json:”name,omitempty”`
	AccessKey            string   `protobuf:”bytes,3,opt,name=access_key,json=accessKey,proto3” json:”access_key,omitempty”`
	SecretKey            string   `protobuf:”bytes,4,opt,name=secret_key,json=secretKey,proto3” json:”secret_key,omitempty”`
	XXX_NoUnkeyedLiteral struct{} `json:”-”`
	XXX_unrecognized     []byte   `json:”-”`
	XXX_sizecache        int32    `json:”-”`
}
func (m *RobotExchangeInfo) Reset()         { *m = RobotExchangeInfo{} }
func (m *RobotExchangeInfo) String() string { return proto.CompactTextString(m) }
func (*RobotExchangeInfo) ProtoMessage()    {}
func (*RobotExchangeInfo) Descriptor() ([]byte, []int) {
	return fileDescriptor_802e9beed3ec3b28, []int{4}
}
func (m *RobotExchangeInfo) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_RobotExchangeInfo.Unmarshal(m, b)
}
func (m *RobotExchangeInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_RobotExchangeInfo.Marshal(b, m, deterministic)
}
func (m *RobotExchangeInfo) XXX_Merge(src proto.Message) {
	xxx_messageInfo_RobotExchangeInfo.Merge(m, src)
}
func (m *RobotExchangeInfo) XXX_Size() int {
	return xxx_messageInfo_RobotExchangeInfo.Size(m)
}
func (m *RobotExchangeInfo) XXX_DiscardUnknown() {
	xxx_messageInfo_RobotExchangeInfo.DiscardUnknown(m)
}
var xxx_messageInfo_RobotExchangeInfo proto.InternalMessageInfo
func (m *RobotExchangeInfo) GetLabel() string {
	if m != nil {
		return m.Label
	}
	return “”
}
func (m *RobotExchangeInfo) GetName() string {
	if m != nil {
		return m.Name
	}
	return “”
}
func (m *RobotExchangeInfo) GetAccessKey() string {
	if m != nil {
		return m.AccessKey
	}
	return “”
}
func (m *RobotExchangeInfo) GetSecretKey() string {
	if m != nil {
		return m.SecretKey
	}
	return “”
}

RobotExchangeInfo contains the connection details for a single exchange configured for a trading robot. This includes the display label for the exchange, its official name, and the API credentials (access key and secret key) required to authenticate with the exchange’s trading API.

type RobotExchangeInfoReply struct {
	Exchanges            []*RobotExchangeInfo `protobuf:”bytes,1,rep,name=exchanges,proto3” json:”exchanges,omitempty”`
	XXX_NoUnkeyedLiteral struct{}             `json:”-”`
	XXX_unrecognized     []byte               `json:”-”`
	XXX_sizecache        int32                `json:”-”`
}
func (m *RobotExchangeInfoReply) Reset()         { *m = RobotExchangeInfoReply{} }
func (m *RobotExchangeInfoReply) String() string { return proto.CompactTextString(m) }
func (*RobotExchangeInfoReply) ProtoMessage()    {}
func (*RobotExchangeInfoReply) Descriptor() ([]byte, []int) {
	return fileDescriptor_802e9beed3ec3b28, []int{5}
}
func (m *RobotExchangeInfoReply) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_RobotExchangeInfoReply.Unmarshal(m, b)
}
func (m *RobotExchangeInfoReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_RobotExchangeInfoReply.Marshal(b, m, deterministic)
}
func (m *RobotExchangeInfoReply) XXX_Merge(src proto.Message) {
	xxx_messageInfo_RobotExchangeInfoReply.Merge(m, src)
}
func (m *RobotExchangeInfoReply) XXX_Size() int {
	return xxx_messageInfo_RobotExchangeInfoReply.Size(m)
}
func (m *RobotExchangeInfoReply) XXX_DiscardUnknown() {
	xxx_messageInfo_RobotExchangeInfoReply.DiscardUnknown(m)
}
var xxx_messageInfo_RobotExchangeInfoReply proto.InternalMessageInfo
func (m *RobotExchangeInfoReply) GetExchanges() []*RobotExchangeInfo {
	if m != nil {
		return m.Exchanges
	}
	return nil
}

RobotExchangeInfoReply contains the response to a GetRobotExchangeInfo RPC call. It includes a repeated field of RobotExchangeInfo messages, allowing a robot to be configured with multiple exchange connections for multi-exchange trading strategies.

type GetValueRequest struct {
	RobotId              string   `protobuf:”bytes,1,opt,name=robot_id,json=robotId,proto3” json:”robot_id,omitempty”`
	Key                  string   `protobuf:”bytes,2,opt,name=key,proto3” json:”key,omitempty”`
	XXX_NoUnkeyedLiteral struct{} `json:”-”`
	XXX_unrecognized     []byte   `json:”-”`
	XXX_sizecache        int32    `json:”-”`
}
func (m *GetValueRequest) Reset()         { *m = GetValueRequest{} }
func (m *GetValueRequest) String() string { return proto.CompactTextString(m) }
func (*GetValueRequest) ProtoMessage()    {}
func (*GetValueRequest) Descriptor() ([]byte, []int) {
	return fileDescriptor_802e9beed3ec3b28, []int{6}
}
func (m *GetValueRequest) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_GetValueRequest.Unmarshal(m, b)
}
func (m *GetValueRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_GetValueRequest.Marshal(b, m, deterministic)
}
func (m *GetValueRequest) XXX_Merge(src proto.Message) {
	xxx_messageInfo_GetValueRequest.Merge(m, src)
}
func (m *GetValueRequest) XXX_Size() int {
	return xxx_messageInfo_GetValueRequest.Size(m)
}
func (m *GetValueRequest) XXX_DiscardUnknown() {
	xxx_messageInfo_GetValueRequest.DiscardUnknown(m)
}
var xxx_messageInfo_GetValueRequest proto.InternalMessageInfo
func (m *GetValueRequest) GetRobotId() string {
	if m != nil {
		return m.RobotId
	}
	return “”
}
func (m *GetValueRequest) GetKey() string {
	if m != nil {
		return m.Key
	}
	return “”
}

GetValueRequest represents a request to retrieve a stored value from the broker server’s key-value persistence store. The RobotId identifies which robot’s data to access, and the Key specifies which value to retrieve. This enables robots to maintain state across restarts.

type GetValueReply struct {
	Success              bool     `protobuf:”varint,1,opt,name=success,proto3” json:”success,omitempty”`
	Message              string   `protobuf:”bytes,2,opt,name=message,proto3” json:”message,omitempty”`
	Value                []byte   `protobuf:”bytes,3,opt,name=value,proto3” json:”value,omitempty”`
	XXX_NoUnkeyedLiteral struct{} `json:”-”`
	XXX_unrecognized     []byte   `json:”-”`
	XXX_sizecache        int32    `json:”-”`
}
func (m *GetValueReply) Reset()         { *m = GetValueReply{} }
func (m *GetValueReply) String() string { return proto.CompactTextString(m) }
func (*GetValueReply) ProtoMessage()    {}
func (*GetValueReply) Descriptor() ([]byte, []int) {
	return fileDescriptor_802e9beed3ec3b28, []int{7}
}
func (m *GetValueReply) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_GetValueReply.Unmarshal(m, b)
}
func (m *GetValueReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_GetValueReply.Marshal(b, m, deterministic)
}
func (m *GetValueReply) XXX_Merge(src proto.Message) {
	xxx_messageInfo_GetValueReply.Merge(m, src)
}
func (m *GetValueReply) XXX_Size() int {
	return xxx_messageInfo_GetValueReply.Size(m)
}
func (m *GetValueReply) XXX_DiscardUnknown() {
	xxx_messageInfo_GetValueReply.DiscardUnknown(m)
}
var xxx_messageInfo_GetValueReply proto.InternalMessageInfo
func (m *GetValueReply) GetSuccess() bool {
	if m != nil {
		return m.Success
	}
	return false
}
func (m *GetValueReply) GetMessage() string {
	if m != nil {
		return m.Message
	}
	return “”
}
func (m *GetValueReply) GetValue() []byte {
	if m != nil {
		return m.Value
	}
	return nil
}

GetValueReply contains the response to a GetValue RPC call. The Success field indicates whether the value was found, Message provides any error or status information, and Value contains the retrieved data as a byte slice allowing for arbitrary serialized data storage.

type SetValueRequest struct {
	RobotId              string   `protobuf:”bytes,1,opt,name=robot_id,json=robotId,proto3” json:”robot_id,omitempty”`
	Key                  string   `protobuf:”bytes,2,opt,name=key,proto3” json:”key,omitempty”`
	Value                []byte   `protobuf:”bytes,3,opt,name=value,proto3” json:”value,omitempty”`
	XXX_NoUnkeyedLiteral struct{} `json:”-”`
	XXX_unrecognized     []byte   `json:”-”`
	XXX_sizecache        int32    `json:”-”`
}
func (m *SetValueRequest) Reset()         { *m = SetValueRequest{} }
func (m *SetValueRequest) String() string { return proto.CompactTextString(m) }
func (*SetValueRequest) ProtoMessage()    {}
func (*SetValueRequest) Descriptor() ([]byte, []int) {
	return fileDescriptor_802e9beed3ec3b28, []int{8}
}
func (m *SetValueRequest) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_SetValueRequest.Unmarshal(m, b)
}
func (m *SetValueRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_SetValueRequest.Marshal(b, m, deterministic)
}
func (m *SetValueRequest) XXX_Merge(src proto.Message) {
	xxx_messageInfo_SetValueRequest.Merge(m, src)
}
func (m *SetValueRequest) XXX_Size() int {
	return xxx_messageInfo_SetValueRequest.Size(m)
}
func (m *SetValueRequest) XXX_DiscardUnknown() {
	xxx_messageInfo_SetValueRequest.DiscardUnknown(m)
}
var xxx_messageInfo_SetValueRequest proto.InternalMessageInfo
func (m *SetValueRequest) GetRobotId() string {
	if m != nil {
		return m.RobotId
	}
	return “”
}
func (m *SetValueRequest) GetKey() string {
	if m != nil {
		return m.Key
	}
	return “”
}
func (m *SetValueRequest) GetValue() []byte {
	if m != nil {
		return m.Value
	}
	return nil
}

SetValueRequest represents a request to store a value in the broker server’s key-value persistence store. The RobotId identifies which robot’s data namespace to use, Key specifies the storage key, and Value contains the data as a byte slice to enable storage of any serializable data.

type SetValueReply struct {
	Success              bool     `protobuf:”varint,1,opt,name=success,proto3” json:”success,omitempty”`
	Message              string   `protobuf:”bytes,2,opt,name=message,proto3” json:”message,omitempty”`
	XXX_NoUnkeyedLiteral struct{} `json:”-”`
	XXX_unrecognized     []byte   `json:”-”`
	XXX_sizecache        int32    `json:”-”`
}

func (m *SetValueReply) Reset()         { *m = SetValueReply{} }
func (m *SetValueReply) String() string { return proto.CompactTextString(m) }
func (*SetValueReply) ProtoMessage()    {}
func (*SetValueReply) Descriptor() ([]byte, []int) {
	return fileDescriptor_802e9beed3ec3b28, []int{9}
}

func (m *SetValueReply) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_SetValueReply.Unmarshal(m, b)
}
func (m *SetValueReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_SetValueReply.Marshal(b, m, deterministic)
}
func (m *SetValueReply) XXX_Merge(src proto.Message) {
	xxx_messageInfo_SetValueReply.Merge(m, src)
}
func (m *SetValueReply) XXX_Size() int {
	return xxx_messageInfo_SetValueReply.Size(m)
}
func (m *SetValueReply) XXX_DiscardUnknown() {
	xxx_messageInfo_SetValueReply.DiscardUnknown(m)
}

var xxx_messageInfo_SetValueReply proto.InternalMessageInfo

func (m *SetValueReply) GetSuccess() bool {
	if m != nil {
		return m.Success
	}
	return false
}

func (m *SetValueReply) GetMessage() string {
	if m != nil {
		return m.Message
	}
	return “”
}

SetValueReply contains the response to a SetValue RPC call. The Success field indicates whether the value was stored successfully, and Message provides any error or status information about the operation.

type LogRequest struct {
	Sid                  int32    `protobuf:”varint,1,opt,name=sid,proto3” json:”sid,omitempty”`
	Id                   uint64   `protobuf:”varint,2,opt,name=id,proto3” json:”id,omitempty”`
	Time                 int64    `protobuf:”varint,3,opt,name=time,proto3” json:”time,omitempty”`
	Level                int32    `protobuf:”varint,4,opt,name=level,proto3” json:”level,omitempty”`
	Message              string   `protobuf:”bytes,5,opt,name=message,proto3” json:”message,omitempty”`
	XXX_NoUnkeyedLiteral struct{} `json:”-”`
	XXX_unrecognized     []byte   `json:”-”`
	XXX_sizecache        int32    `json:”-”`
}

func (m *LogRequest) Reset()         { *m = LogRequest{} }
func (m *LogRequest) String() string { return proto.CompactTextString(m) }
func (*LogRequest) ProtoMessage()    {}
func (*LogRequest) Descriptor() ([]byte, []int) {
	return fileDescriptor_802e9beed3ec3b28, []int{10}
}

func (m *LogRequest) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_LogRequest.Unmarshal(m, b)
}
func (m *LogRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_LogRequest.Marshal(b, m, deterministic)
}
func (m *LogRequest) XXX_Merge(src proto.Message) {
	xxx_messageInfo_LogRequest.Merge(m, src)
}
func (m *LogRequest) XXX_Size() int {
	return xxx_messageInfo_LogRequest.Size(m)
}
func (m *LogRequest) XXX_DiscardUnknown() {
	xxx_messageInfo_LogRequest.DiscardUnknown(m)
}

var xxx_messageInfo_LogRequest proto.InternalMessageInfo

func (m *LogRequest) GetSid() int32 {
	if m != nil {
		return m.Sid
	}
	return 0
}

func (m *LogRequest) GetId() uint64 {
	if m != nil {
		return m.Id
	}
	return 0
}

func (m *LogRequest) GetTime() int64 {
	if m != nil {
		return m.Time
	}
	return 0
}

func (m *LogRequest) GetLevel() int32 {
	if m != nil {
		return m.Level
	}
	return 0
}

func (m *LogRequest) GetMessage() string {
	if m != nil {
		return m.Message
	}
	return “”
}

LogRequest represents a request to send a log message from a trading robot to the central broker server. The Sid is a session identifier, Id is a unique log entry identifier, Time is the Unix timestamp when the log was generated, Level indicates the severity (e.g., debug, info, warn, error), and Message contains the log content.

type LogReply struct {
	Success              bool     `protobuf:”varint,1,opt,name=success,proto3” json:”success,omitempty”`
	Message              string   `protobuf:”bytes,2,opt,name=message,proto3” json:”message,omitempty”`
	XXX_NoUnkeyedLiteral struct{} `json:”-”`
	XXX_unrecognized     []byte   `json:”-”`
	XXX_sizecache        int32    `json:”-”`
}

func (m *LogReply) Reset()         { *m = LogReply{} }
func (m *LogReply) String() string { return proto.CompactTextString(m) }
func (*LogReply) ProtoMessage()    {}
func (*LogReply) Descriptor() ([]byte, []int) {
	return fileDescriptor_802e9beed3ec3b28, []int{11}
}

func (m *LogReply) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_LogReply.Unmarshal(m, b)
}
func (m *LogReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_LogReply.Marshal(b, m, deterministic)
}
func (m *LogReply) XXX_Merge(src proto.Message) {
	xxx_messageInfo_LogReply.Merge(m, src)
}
func (m *LogReply) XXX_Size() int {
	return xxx_messageInfo_LogReply.Size(m)
}
func (m *LogReply) XXX_DiscardUnknown() {
	xxx_messageInfo_LogReply.DiscardUnknown(m)
}

var xxx_messageInfo_LogReply proto.InternalMessageInfo

func (m *LogReply) GetSuccess() bool {
	if m != nil {
		return m.Success
	}
	return false
}

func (m *LogReply) GetMessage() string {
	if m != nil {
		return m.Message
	}
	return “”
}

LogReply contains the response to a Log RPC call. The Success field indicates whether the log message was received and stored successfully, and Message provides any error or status information about the logging operation.

type UpdateStatusRequest struct {
	RobotId              string   `protobuf:”bytes,1,opt,name=robot_id,json=robotId,proto3” json:”robot_id,omitempty”`
	Status               int32    `protobuf:”varint,2,opt,name=status,proto3” json:”status,omitempty”`
	XXX_NoUnkeyedLiteral struct{} `json:”-”`
	XXX_unrecognized     []byte   `json:”-”`
	XXX_sizecache        int32    `json:”-”`
}

func (m *UpdateStatusRequest) Reset()         { *m = UpdateStatusRequest{} }
func (m *UpdateStatusRequest) String() string { return proto.CompactTextString(m) }
func (*UpdateStatusRequest) ProtoMessage()    {}
func (*UpdateStatusRequest) Descriptor() ([]byte, []int) {
	return fileDescriptor_802e9beed3ec3b28, []int{12}
}

func (m *UpdateStatusRequest) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_UpdateStatusRequest.Unmarshal(m, b)
}
func (m *UpdateStatusRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_UpdateStatusRequest.Marshal(b, m, deterministic)
}
func (m *UpdateStatusRequest) XXX_Merge(src proto.Message) {
	xxx_messageInfo_UpdateStatusRequest.Merge(m, src)
}
func (m *UpdateStatusRequest) XXX_Size() int {
	return xxx_messageInfo_UpdateStatusRequest.Size(m)
}
func (m *UpdateStatusRequest) XXX_DiscardUnknown() {
	xxx_messageInfo_UpdateStatusRequest.DiscardUnknown(m)
}

var xxx_messageInfo_UpdateStatusRequest proto.InternalMessageInfo

func (m *UpdateStatusRequest) GetRobotId() string {
	if m != nil {
		return m.RobotId
	}
	return “”
}

func (m *UpdateStatusRequest) GetStatus() int32 {
	if m != nil {
		return m.Status
	}
	return 0
}

UpdateStatusRequest represents a request to update the operational status of a trading robot. The RobotId identifies the robot, and Status is a numeric code representing the robot’s current state such as starting, running, paused, or error.

type UpdateStatusReply struct {
	Success              bool     `protobuf:”varint,1,opt,name=success,proto3” json:”success,omitempty”`
	Message              string   `protobuf:”bytes,2,opt,name=message,proto3” json:”message,omitempty”`
	XXX_NoUnkeyedLiteral struct{} `json:”-”`
	XXX_unrecognized     []byte   `json:”-”`
	XXX_sizecache        int32    `json:”-”`
}

func (m *UpdateStatusReply) Reset()         { *m = UpdateStatusReply{} }
func (m *UpdateStatusReply) String() string { return proto.CompactTextString(m) }
func (*UpdateStatusReply) ProtoMessage()    {}
func (*UpdateStatusReply) Descriptor() ([]byte, []int) {
	return fileDescriptor_802e9beed3ec3b28, []int{13}
}

func (m *UpdateStatusReply) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_UpdateStatusReply.Unmarshal(m, b)
}
func (m *UpdateStatusReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_UpdateStatusReply.Marshal(b, m, deterministic)
}
func (m *UpdateStatusReply) XXX_Merge(src proto.Message) {
	xxx_messageInfo_UpdateStatusReply.Merge(m, src)
}
func (m *UpdateStatusReply) XXX_Size() int {
	return xxx_messageInfo_UpdateStatusReply.Size(m)
}
func (m *UpdateStatusReply) XXX_DiscardUnknown() {
	xxx_messageInfo_UpdateStatusReply.DiscardUnknown(m)
}

var xxx_messageInfo_UpdateStatusReply proto.InternalMessageInfo

func (m *UpdateStatusReply) GetSuccess() bool {
	if m != nil {
		return m.Success
	}
	return false
}

func (m *UpdateStatusReply) GetMessage() string {
	if m != nil {
		return m.Message
	}
	return “”
}

UpdateStatusReply contains the response to an UpdateStatus RPC call. The Success field indicates whether the status update was recorded successfully, and Message provides any error or status information about the operation.

type StatRequest struct {
	RobotId              string   `protobuf:”bytes,1,opt,name=robot_id,json=robotId,proto3” json:”robot_id,omitempty”`
	Name                 string   `protobuf:”bytes,2,opt,name=name,proto3” json:”name,omitempty”`
	Value                []byte   `protobuf:”bytes,3,opt,name=value,proto3” json:”value,omitempty”`
	XXX_NoUnkeyedLiteral struct{} `json:”-”`
	XXX_unrecognized     []byte   `json:”-”`
	XXX_sizecache        int32    `json:”-”`
}

func (m *StatRequest) Reset()         { *m = StatRequest{} }
func (m *StatRequest) String() string { return proto.CompactTextString(m) }
func (*StatRequest) ProtoMessage()    {}
func (*StatRequest) Descriptor() ([]byte, []int) {
	return fileDescriptor_802e9beed3ec3b28, []int{14}
}

func (m *StatRequest) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_StatRequest.Unmarshal(m, b)
}
func (m *StatRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_StatRequest.Marshal(b, m, deterministic)
}
func (m *StatRequest) XXX_Merge(src proto.Message) {
	xxx_messageInfo_StatRequest.Merge(m, src)
}
func (m *StatRequest) XXX_Size() int {
	return xxx_messageInfo_StatRequest.Size(m)
}
func (m *StatRequest) XXX_DiscardUnknown() {
	xxx_messageInfo_StatRequest.DiscardUnknown(m)
}

var xxx_messageInfo_StatRequest proto.InternalMessageInfo

func (m *StatRequest) GetRobotId() string {
	if m != nil {
		return m.RobotId
	}
	return “”
}

func (m *StatRequest) GetName() string {
	if m != nil {
		return m.Name
	}
	return “”
}

func (m *StatRequest) GetValue() []byte {
	if m != nil {
		return m.Value
	}
	return nil
}

StatRequest represents a request to report statistical data from a trading robot to the broker server. The RobotId identifies the robot, Name specifies the metric being reported (e.g., profit, trade count), and Value contains the metric data as a byte slice to allow for complex or serialized data structures.

type StatReply struct {
	Success              bool     `protobuf:”varint,1,opt,name=success,proto3” json:”success,omitempty”`
	Message              string   `protobuf:”bytes,2,opt,name=message,proto3” json:”message,omitempty”`
	XXX_NoUnkeyedLiteral struct{} `json:”-”`
	XXX_unrecognized     []byte   `json:”-”`
	XXX_sizecache        int32    `json:”-”`
}

func (m *StatReply) Reset()         { *m = StatReply{} }
func (m *StatReply) String() string { return proto.CompactTextString(m) }
func (*StatReply) ProtoMessage()    {}
func (*StatReply) Descriptor() ([]byte, []int) {
	return fileDescriptor_802e9beed3ec3b28, []int{15}
}

func (m *StatReply) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_StatReply.Unmarshal(m, b)
}
func (m *StatReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_StatReply.Marshal(b, m, deterministic)
}
func (m *StatReply) XXX_Merge(src proto.Message) {
	xxx_messageInfo_StatReply.Merge(m, src)
}
func (m *StatReply) XXX_Size() int {
	return xxx_messageInfo_StatReply.Size(m)
}
func (m *StatReply) XXX_DiscardUnknown() {
	xxx_messageInfo_StatReply.DiscardUnknown(m)
}

var xxx_messageInfo_StatReply proto.InternalMessageInfo

func (m *StatReply) GetSuccess() bool {
	if m != nil {
		return m.Success
	}
	return false
}

func (m *StatReply) GetMessage() string {
	if m != nil {
		return m.Message
	}
	return “”
}

StatReply contains the response to an UpdateStat RPC call. The Success field indicates whether the statistical data was received and stored successfully, and Message provides any error or status information about the operation.

func init() {
	proto.RegisterType((*RobotOptionsRequest)(nil), “goalgo.RobotOptionsRequest”)
	proto.RegisterType((*RobotOption)(nil), “goalgo.RobotOption”)
	proto.RegisterType((*RobotOptionsReply)(nil), “goalgo.RobotOptionsReply”)
	proto.RegisterType((*RobotExchangeInfoRequest)(nil), “goalgo.RobotExchangeInfoRequest”)
	proto.RegisterType((*RobotExchangeInfo)(nil), “goalgo.RobotExchangeInfo”)
	proto.RegisterType((*RobotExchangeInfoReply)(nil), “goalgo.RobotExchangeInfoReply”)
	proto.RegisterType((*GetValueRequest)(nil), “goalgo.GetValueRequest”)
	proto.RegisterType((*GetValueReply)(nil), “goalgo.GetValueReply”)
	proto.RegisterType((*SetValueRequest)(nil), “goalgo.SetValueRequest”)
	proto.RegisterType((*SetValueReply)(nil), “goalgo.SetValueReply”)
	proto.RegisterType((*LogRequest)(nil), “goalgo.LogRequest”)
	proto.RegisterType((*LogReply)(nil), “goalgo.LogReply”)
	proto.RegisterType((*UpdateStatusRequest)(nil), “goalgo.UpdateStatusRequest”)
	proto.RegisterType((*UpdateStatusReply)(nil), “goalgo.UpdateStatusReply”)
	proto.RegisterType((*StatRequest)(nil), “goalgo.StatRequest”)
	proto.RegisterType((*StatReply)(nil), “goalgo.StatReply”)
}

The init function registers all Protocol Buffer message types defined in this file with the global proto registry. This registration is required for the proto package to perform operations like unmarshaling messages by type name or using reflection to inspect message types. Each call to RegisterType associates a Go type with its Protocol Buffer full name in the format “package.MessageName”.

func init() { proto.RegisterFile(”grpc_broker.proto”, fileDescriptor_802e9beed3ec3b28) }

This init function registers the source proto file with the proto package using the file name and the compressed file descriptor. This enables proto reflection capabilities and allows tools to examine the original Protocol Buffer schema.

var fileDescriptor_802e9beed3ec3b28 = []byte{
	// 686 bytes of a gzipped FileDescriptorProto
	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0x5d, 0x6f, 0xd3, 0x30,
	0x14, 0x5d, 0x9b, 0x7e, 0xde, 0x6e, 0x6c, 0x75, 0xc7, 0x68, 0x8b, 0x40, 0x95, 0x9f, 0xf6, 0xb2,
	0x3e, 0x0c, 0x01, 0x2f, 0x68, 0x48, 0x9b, 0x50, 0x37, 0x75, 0x12, 0xc2, 0x01, 0x24, 0x78, 0xa9,
	0xd2, 0xd6, 0x84, 0xa8, 0x69, 0x1d, 0x62, 0x67, 0x22, 0xe2, 0xcf, 0xf2, 0x53, 0x90, 0xed, 0xb8,
	0x49, 0xd6, 0x14, 0x4d, 0xdd, 0x9b, 0xef, 0xd7, 0xf1, 0xb9, 0xc7, 0xf7, 0x26, 0xd0, 0x76, 0xc3,
	0x60, 0x36, 0x99, 0x86, 0x6c, 0x41, 0xc3, 0x61, 0x10, 0x32, 0xc1, 0x50, 0xcd, 0x65, 0x8e, 0xef,
	0x32, 0x7c, 0x09, 0x1d, 0xc2, 0xa6, 0x4c, 0x7c, 0x0c, 0x84, 0xc7, 0x56, 0x9c, 0xd0, 0x5f, 0x11,
	0xe5, 0x02, 0x1d, 0x81, 0x15, 0x79, 0xf3, 0x6e, 0x69, 0x50, 0x3a, 0x6d, 0x12, 0x79, 0x44, 0x3d,
	0x68, 0x84, 0x32, 0x71, 0xe2, 0xcd, 0xbb, 0x65, 0xe5, 0xae, 0x2b, 0xfb, 0x66, 0x8e, 0x5f, 0x43,
	0x2b, 0x83, 0x21, 0x6b, 0x17, 0x34, 0x36, 0xb5, 0x0b, 0x1a, 0xa3, 0x63, 0xa8, 0xde, 0x39, 0x7e,
	0x44, 0x93, 0x42, 0x6d, 0xe0, 0x4b, 0x68, 0xe7, 0xaf, 0x0e, 0xfc, 0x18, 0x9d, 0x41, 0x9d, 0x69,
	0xbb, 0x5b, 0x1a, 0x58, 0xa7, 0xad, 0xf3, 0xce, 0x50, 0x33, 0x1d, 0x66, 0x72, 0x89, 0xc9, 0xc1,
	0x23, 0xe8, 0x2a, 0xff, 0x87, 0xdf, 0xb3, 0x9f, 0xce, 0xca, 0xa5, 0x37, 0xab, 0x1f, 0x6c, 0xa7,
	0x1e, 0xfe, 0x24, 0x64, 0xb2, 0x40, 0x92, 0xb7, 0xef, 0x4c, 0xa9, 0x9f, 0x60, 0x68, 0x03, 0x21,
	0xa8, 0xac, 0x9c, 0xa5, 0x69, 0x46, 0x9d, 0xd1, 0x0b, 0x00, 0x67, 0x36, 0xa3, 0x9c, 0x4f, 0x64,
	0xeb, 0x96, 0x8a, 0x34, 0xb5, 0x67, 0x4c, 0x63, 0x19, 0xe6, 0x74, 0x16, 0x52, 0xa1, 0xc2, 0x15,
	0x1d, 0xd6, 0x9e, 0x31, 0x8d, 0xf1, 0x27, 0x38, 0x29, 0xe8, 0x42, 0xca, 0xf1, 0x16, 0x9a, 0x34,
	0x71, 0x1a, 0x41, 0x7a, 0x39, 0x41, 0x72, 0x25, 0x69, 0x2e, 0xbe, 0x80, 0xc3, 0x11, 0x15, 0x5f,
	0xa5, 0xd0, 0x46, 0x8f, 0x6c, 0xf7, 0xa5, 0x5c, 0xf7, 0xe6, 0xc9, 0xca, 0xeb, 0x27, 0xc3, 0xdf,
	0xe0, 0x20, 0xad, 0x97, 0x4c, 0xba, 0x50, 0xe7, 0x91, 0x6a, 0x48, 0x15, 0x37, 0x88, 0x31, 0x65,
	0x64, 0x49, 0x39, 0x77, 0x5c, 0x23, 0x89, 0x31, 0xd3, 0x77, 0x97, 0x82, 0xec, 0x9b, 0x77, 0xff,
	0x0c, 0x87, 0xf6, 0x23, 0xa8, 0x6d, 0x41, 0xbd, 0x82, 0x03, 0xfb, 0xb1, 0x84, 0x71, 0x08, 0x70,
	0xcb, 0xdc, 0xcc, 0x00, 0xf1, 0x84, 0x50, 0x95, 0xc8, 0x23, 0x7a, 0x02, 0xe5, 0x64, 0x74, 0x2a,
	0xa4, 0xec, 0xcd, 0xe5, 0x28, 0x08, 0x6f, 0xa9, 0x99, 0x58, 0x44, 0x9d, 0xd5, 0xd0, 0xd0, 0x3b,
	0xea, 0xab, 0x67, 0xae, 0x12, 0x6d, 0x64, 0xef, 0xac, 0xe6, 0xef, 0xbc, 0x80, 0x86, 0xba, 0x73,
	0x57, 0xce, 0xd7, 0xd0, 0xf9, 0x12, 0xcc, 0x1d, 0x41, 0x6d, 0xe1, 0x88, 0x88, 0x3f, 0x40, 0xd2,
	0x13, 0xa8, 0x71, 0x95, 0xab, 0xa0, 0xaa, 0x24, 0xb1, 0xf0, 0x08, 0xda, 0x79, 0xa4, 0x5d, 0x29,
	0x11, 0x68, 0x49, 0x88, 0x07, 0x50, 0x29, 0xda, 0xa5, 0xe2, 0xf7, 0x7d, 0x0f, 0x4d, 0x8d, 0xb9,
	0x23, 0xa9, 0xf3, 0xbf, 0x16, 0x34, 0xd4, 0xca, 0x5c, 0x09, 0x1f, 0x8d, 0xd5, 0x7a, 0x64, 0x3f,
	0x3f, 0xe8, 0x79, 0xc1, 0x87, 0xc6, 0xa8, 0xd9, 0xef, 0x15, 0x07, 0x03, 0x3f, 0xc6, 0x7b, 0xe8,
	0x3b, 0x1c, 0x1b, 0xb0, 0xdc, 0xe7, 0x63, 0xb0, 0x7d, 0x53, 0x13, 0xd8, 0x97, 0xff, 0xc9, 0xd0,
	0xd8, 0xef, 0xa0, 0x61, 0xf6, 0x10, 0x3d, 0x33, 0xd9, 0xf7, 0x36, 0xbb, 0xff, 0x74, 0x33, 0xb0,
	0xae, 0xb6, 0x37, 0xaa, 0xed, 0x6d, 0xd5, 0xf6, 0xbd, 0xea, 0x33, 0xb0, 0x6e, 0x99, 0x8b, 0x90,
	0x89, 0xa7, 0xab, 0xd1, 0x3f, 0xca, 0xf9, 0x74, 0xfa, 0x35, 0xec, 0x67, 0xc7, 0x27, 0x15, 0xb4,
	0x60, 0x3c, 0x53, 0x41, 0x37, 0x26, 0x0e, 0xef, 0xa1, 0x37, 0x00, 0xa9, 0x1b, 0xad, 0xff, 0x00,
	0x99, 0x99, 0xea, 0xb7, 0xf3, 0x4e, 0x55, 0x37, 0xad, 0xa9, 0x7f, 0xdb, 0xab, 0x7f, 0x01, 0x00,
	0x00, 0xff, 0xff, 0xbd, 0xa8, 0xb4, 0xb5, 0xf0, 0x06, 0x00, 0x00,
}

The fileDescriptor variable contains the gzip-compressed bytes of the original Protocol Buffer file descriptor. This descriptor contains complete schema information about the proto file including message definitions, field types, and service definitions. It is used by the proto package for reflection and by gRPC for describing services to clients. The compression reduces memory usage while still allowing full schema introspection when needed.

File: grpc_broker_server.go

package goalgo

Package goalgo provides the gRPC server-side implementation for the RobotCtl service, which is the central broker component that handles requests from algorithmic trading robots. This file contains the server stub code that is typically generated from a Protocol Buffers service definition (grpc_broker.proto) and provides the infrastructure needed to receive and dispatch RPC calls to the appropriate handler implementations.

The RobotCtl service acts as the central nervous system for the trading robot ecosystem, providing trading robots with essential capabilities during their lifecycle. These include configuration retrieval, exchange credential management, persistent key-value storage, centralized logging, status reporting, and statistical data collection.

This server implementation follows the standard gRPC Go pattern where an interface defines the methods that must be implemented by the service handler, a registration function connects the handler to the gRPC server, and individual handler functions wrap the RPC dispatch to support interceptors. The service descriptor at the end ties everything together with method metadata used by the gRPC runtime.

import (
	context “golang.org/x/net/context”
	grpc “google.golang.org/grpc”
)

The import block brings in the essential dependencies required for gRPC server functionality. The context package from golang.org/x/net/context provides the Context type which carries deadlines, cancellations, and request-scoped values across API boundaries. The grpc package from google.golang.org/grpc provides the core gRPC server functionality including the Server type for hosting services and the UnaryServerInterceptor type for adding middleware to RPC handlers.

These imports are aliased to their package names which is a common pattern in generated gRPC code to ensure consistency and avoid naming conflicts.

type RobotCtlServer interface {
	GetRobotOptions(context.Context, *RobotOptionsRequest) (*RobotOptionsReply, error)
	GetRobotExchangeInfo(context.Context, *RobotExchangeInfoRequest) (*RobotExchangeInfoReply, error)
	GetValue(context.Context, *GetValueRequest) (*GetValueReply, error)
	SetValue(context.Context, *SetValueRequest) (*SetValueReply, error)
	Log(context.Context, *LogRequest) (*LogReply, error)
	UpdateStatus(context.Context, *UpdateStatusRequest) (*UpdateStatusReply, error)
	UpdateStat(context.Context, *StatRequest) (*StatReply, error)
}

RobotCtlServer defines the interface that must be implemented by any type that wants to serve the RobotCtl gRPC service. This interface is the contract between the gRPC infrastructure and the application-specific business logic. Developers implementing the broker server must create a type that implements all seven methods defined in this interface.

Each method in the interface corresponds to one RPC endpoint in the RobotCtl service. The methods receive a context for request lifecycle management and a request message containing the input parameters, and must return a reply message and an error. A nil error indicates success, while a non-nil error will be converted to an appropriate gRPC status code for transmission back to the client.

GetRobotOptions should return the runtime configuration for a specified robot. GetRobotExchangeInfo should return the exchange connection credentials for a robot. GetValue and SetValue provide the persistence layer for robot state management. Log handles incoming log messages from robots for centralized logging. UpdateStatus tracks the operational state of robots. UpdateStat records performance metrics.

func RegisterRobotCtlServer(s *grpc.Server, srv RobotCtlServer) {
	s.RegisterService(&_RobotCtl_serviceDesc, srv)
}

RegisterRobotCtlServer registers a RobotCtlServer implementation with a gRPC server instance, making the RobotCtl service available to clients. This function must be called during server initialization before the gRPC server begins accepting connections. It connects the service implementation to the gRPC server using the service descriptor that defines the method names and their handlers.

The function takes the gRPC server instance and the service implementation, then calls the server’s RegisterService method with the service descriptor. After this registration, any incoming RPC calls targeting the “/goalgo.RobotCtl/” namespace will be routed to the appropriate handler functions which will in turn invoke the corresponding methods on the registered RobotCtlServer implementation.

func _RobotCtl_GetRobotOptions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(RobotOptionsRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(RobotCtlServer).GetRobotOptions(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: “/goalgo.RobotCtl/GetRobotOptions”,
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(RobotCtlServer).GetRobotOptions(ctx, req.(*RobotOptionsRequest))
	}
	return interceptor(ctx, in, info, handler)
}

_RobotCtl_GetRobotOptions_Handler is the internal handler function for the GetRobotOptions RPC method. This function is invoked by the gRPC runtime when a GetRobotOptions request arrives at the server. It acts as the bridge between the gRPC transport layer and the application’s RobotCtlServer implementation.

The handler first allocates a new RobotOptionsRequest message and uses the provided decoder function to deserialize the incoming request bytes into this message. If decoding fails, the error is returned immediately. If no interceptor is configured, the handler calls the GetRobotOptions method directly on the server implementation. When an interceptor is present, the handler creates a UnaryServerInfo structure with method metadata and a handler function, then invokes the interceptor which may add logging, authentication, or other cross-cutting concerns before eventually calling the actual handler. This design allows interceptors to wrap the RPC call with additional behavior without modifying the core business logic.

func _RobotCtl_GetRobotExchangeInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(RobotExchangeInfoRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(RobotCtlServer).GetRobotExchangeInfo(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: “/goalgo.RobotCtl/GetRobotExchangeInfo”,
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(RobotCtlServer).GetRobotExchangeInfo(ctx, req.(*RobotExchangeInfoRequest))
	}
	return interceptor(ctx, in, info, handler)
}

_RobotCtl_GetRobotExchangeInfo_Handler is the internal handler function for the GetRobotExchangeInfo RPC method. This function processes requests for exchange connection information including API credentials needed to connect to trading exchanges.

The handler follows the same pattern as all other handlers in this file. It deserializes the incoming request into a RobotExchangeInfoRequest, checks for interceptors, and either calls the server method directly or routes through the interceptor chain. The method path “/goalgo.RobotCtl/GetRobotExchangeInfo” is provided in the server info structure for use by interceptors that need to know which method is being called, such as for logging or access control purposes.

func _RobotCtl_GetValue_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(GetValueRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(RobotCtlServer).GetValue(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: “/goalgo.RobotCtl/GetValue”,
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(RobotCtlServer).GetValue(ctx, req.(*GetValueRequest))
	}
	return interceptor(ctx, in, info, handler)
}

_RobotCtl_GetValue_Handler is the internal handler function for the GetValue RPC method. This handler processes requests from robots to retrieve stored values from the broker’s key-value persistence store, enabling stateful trading strategies that can maintain data across restarts.

The handler deserializes the request into a GetValueRequest which contains the robot ID and key to look up. The actual value retrieval is performed by the server implementation which may store data in a database, file system, or other persistent storage. Like all handlers, this function supports interceptor chains for adding cross-cutting concerns like caching or rate limiting.

func _RobotCtl_SetValue_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(SetValueRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(RobotCtlServer).SetValue(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: “/goalgo.RobotCtl/SetValue”,
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(RobotCtlServer).SetValue(ctx, req.(*SetValueRequest))
	}
	return interceptor(ctx, in, info, handler)
}

_RobotCtl_SetValue_Handler is the internal handler function for the SetValue RPC method. This handler processes requests from robots to store values in the broker’s key-value persistence store, allowing robots to checkpoint their state for recovery after restarts or failures.

The handler deserializes the request into a SetValueRequest which contains the robot ID, key, and value bytes to store. The server implementation is responsible for persisting this data durably. This method, combined with GetValue, provides the foundation for stateful trading strategies in the goalgo framework.

func _RobotCtl_Log_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(LogRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(RobotCtlServer).Log(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: “/goalgo.RobotCtl/Log”,
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(RobotCtlServer).Log(ctx, req.(*LogRequest))
	}
	return interceptor(ctx, in, info, handler)
}

_RobotCtl_Log_Handler is the internal handler function for the Log RPC method. This handler processes incoming log messages from trading robots, enabling centralized log aggregation and monitoring across the entire trading system.

The handler deserializes the request into a LogRequest which contains session information, timestamp, log level, and the message content. The server implementation can store these logs in a database, forward them to a log aggregation service, or display them in a monitoring dashboard. Centralized logging is critical for debugging and monitoring production trading systems where multiple robots may be running simultaneously.

func _RobotCtl_UpdateStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(UpdateStatusRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(RobotCtlServer).UpdateStatus(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: “/goalgo.RobotCtl/UpdateStatus”,
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(RobotCtlServer).UpdateStatus(ctx, req.(*UpdateStatusRequest))
	}
	return interceptor(ctx, in, info, handler)
}

_RobotCtl_UpdateStatus_Handler is the internal handler function for the UpdateStatus RPC method. This handler processes status updates from trading robots, allowing the central broker to maintain awareness of each robot’s operational state such as running, paused, starting, stopping, or in an error condition.

The handler deserializes the request into an UpdateStatusRequest which contains the robot ID and a numeric status code. The server implementation typically stores this status in a database and may trigger alerts or automated actions when robots enter certain states. This enables operational dashboards to display the health of the entire trading system at a glance.

func _RobotCtl_UpdateStat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(StatRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(RobotCtlServer).UpdateStat(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: “/goalgo.RobotCtl/UpdateStat”,
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(RobotCtlServer).UpdateStat(ctx, req.(*StatRequest))
	}
	return interceptor(ctx, in, info, handler)
}

_RobotCtl_UpdateStat_Handler is the internal handler function for the UpdateStat RPC method. This handler processes statistical data from trading robots, enabling performance monitoring and analytics across the trading system.

The handler deserializes the request into a StatRequest which contains the robot ID, metric name, and metric value as bytes. The server implementation can store these statistics in a time-series database, generate real-time dashboards, or trigger alerts when metrics exceed thresholds. This is essential for monitoring trading performance, profit and loss, and other key performance indicators.

var _RobotCtl_serviceDesc = grpc.ServiceDesc{
	ServiceName: “goalgo.RobotCtl”,
	HandlerType: (*RobotCtlServer)(nil),
	Methods: []grpc.MethodDesc{
		{
			MethodName: “GetRobotOptions”,
			Handler:    _RobotCtl_GetRobotOptions_Handler,
		},
		{
			MethodName: “GetRobotExchangeInfo”,
			Handler:    _RobotCtl_GetRobotExchangeInfo_Handler,
		},
		{
			MethodName: “GetValue”,
			Handler:    _RobotCtl_GetValue_Handler,
		},
		{
			MethodName: “SetValue”,
			Handler:    _RobotCtl_SetValue_Handler,
		},
		{
			MethodName: “Log”,
			Handler:    _RobotCtl_Log_Handler,
		},
		{
			MethodName: “UpdateStatus”,
			Handler:    _RobotCtl_UpdateStatus_Handler,
		},
		{
			MethodName: “UpdateStat”,
			Handler:    _RobotCtl_UpdateStat_Handler,
		},
	},
	Streams:  []grpc.StreamDesc{},
	Metadata: “grpc_broker.proto”,
}

The _RobotCtl_serviceDesc variable contains the complete service descriptor for the RobotCtl gRPC service. This descriptor is the metadata structure that the gRPC runtime uses to route incoming requests to the appropriate handlers and to describe the service to clients.

The ServiceName field contains the fully qualified service name “goalgo.RobotCtl” which matches the namespace used in method paths. The HandlerType field contains a nil pointer to the RobotCtlServer interface which allows the gRPC runtime to verify that registered handlers implement the correct interface.

The Methods slice contains descriptors for each of the seven unary RPC methods. Each method descriptor includes the method name as it appears in the proto file and a reference to the handler function that processes requests for that method. The Streams slice is empty because this service does not define any streaming RPCs. The Metadata field contains the source proto file name for documentation purposes.

File: grpc_client.go

package goalgo

Package goalgo provides a high-level client wrapper for communicating with the RobotCtl gRPC broker server. This file implements the Client type which provides a simplified, application-friendly API for trading robots to interact with the central broker server. It abstracts away the complexity of gRPC connection management, context handling, request/response construction, and serialization.

The Client is implemented as a singleton using Go’s sync.Once pattern, ensuring that only one connection to the broker server is established per process. This design reduces resource usage and simplifies connection lifecycle management. All RPC methods include automatic timeout handling using context deadlines, ensuring that operations do not block indefinitely if the server is unresponsive.

The Client provides methods corresponding to all seven RPC operations defined in the RobotCtl service: retrieving robot options and exchange information, getting and setting persistent values with MessagePack serialization, logging messages, updating robot status, and reporting statistics.

import (
	“bytes”
	“log”
	“sync”
	“time”

	“github.com/vmihailenco/msgpack”

	“errors”

	“golang.org/x/net/context”
	“google.golang.org/grpc”
)

The import block brings in all dependencies required for the Client implementation. The bytes package provides buffer operations for serialization. The log package is used for error logging. The sync package provides the Once type for singleton initialization. The time package is used for timeout durations.

The msgpack package from github.com/vmihailenco/msgpack provides MessagePack binary serialization for the Value type, offering a more compact representation than JSON. The errors package is used for creating error values from server response messages. The context package provides request-scoped timeouts. The grpc package provides the gRPC client connection type.

var (
	instance      *Client
	clientTimeout = time.Second * 5
	once          sync.Once
)

This variable block defines the singleton infrastructure and default configuration for the Client. The instance variable holds the single Client instance that is shared across the entire application. The clientTimeout variable sets the default timeout of 5 seconds for all RPC operations, providing a reasonable balance between allowing operations to complete and detecting unresponsive servers. The once variable is a sync.Once that ensures the client is initialized exactly one time, even when GetClient is called concurrently from multiple goroutines.

type Client struct {
	conn    *grpc.ClientConn
	client  RobotCtlClient
	timeout time.Duration
}

Client is the main type for interacting with the RobotCtl broker server. It wraps a gRPC client connection and the RobotCtlClient interface, adding timeout management and a simplified API. The conn field holds the underlying gRPC connection which must be closed when the client is no longer needed. The client field is the RobotCtlClient that performs the actual RPC calls. The timeout field specifies how long to wait for each RPC operation before timing out.

Trading robot implementations should obtain a Client instance via GetClient and use its methods to communicate with the broker server. The Client handles all the details of context creation, timeout management, request construction, and response unwrapping.

func (c Client) Close() {
	c.conn.Close()
}

Close releases the underlying gRPC connection held by the Client. This method should be called when the Client is no longer needed, typically during application shutdown. Failing to close the connection may result in resource leaks. Note that this method uses a value receiver rather than a pointer receiver, which is unusual but works because it only calls the Close method on the connection pointer.

func (c *Client) GetRobotExchangeInfo(uid string, id string) ([]*RobotExchangeInfo, error) {
	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
	defer cancel()
	request := &RobotExchangeInfoRequest{
		Uid:     uid,
		RobotId: id,
	}
	r, err := c.client.GetRobotExchangeInfo(ctx, request)
	if err != nil {
		log.Printf(”Log: %v”, err)
		return nil, err
	}
	return r.GetExchanges(), nil
}

GetRobotExchangeInfo retrieves the exchange connection information for a specific trading robot from the broker server. This includes exchange labels, names, and API credentials (access key and secret key) needed to connect to trading exchanges.

The method creates a context with the configured timeout, constructs a request with the provided user ID and robot ID, and invokes the RPC. If the call fails, the error is logged and returned. On success, the method extracts and returns the slice of RobotExchangeInfo from the reply. This is typically called during robot initialization to obtain the credentials needed for exchange API connections.

func (c *Client) GetRobotOptions(uid string, id string) ([]*RobotOption, error) {
	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
	defer cancel()
	request := &RobotOptionsRequest{
		Uid:     uid,
		RobotId: id,
	}
	r, err := c.client.GetRobotOptions(ctx, request)
	if err != nil {
		log.Printf(”Log: %v”, err)
		return nil, err
	}
	return r.GetOptions(), nil
}

GetRobotOptions retrieves the configuration options for a specific trading robot from the broker server. Options are key-value pairs that configure robot behavior such as trading parameters, risk limits, and strategy settings.

The method creates a context with the configured timeout, constructs a request with the provided user ID and robot ID, and invokes the RPC. If the call fails, the error is logged and returned. On success, the method extracts and returns the slice of RobotOption from the reply. Robots typically call this during initialization to obtain their operating parameters.

func (c *Client) GetValue(key string) (Value, error) {
	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
	defer cancel()
	request := &GetValueRequest{
		RobotId: id,
		Key:     key,
	}
	r, err := c.client.GetValue(ctx, request)
	if err != nil {
		log.Printf(”Log: %v”, err)
		return Value{}, err
	}

	if string(r.Value) == “” {
		return Value{}, nil
	}

	buff := bytes.NewBuffer(r.Value)
	dec := msgpack.NewDecoder(buff)
	var v Value
	err = dec.Decode(&v)
	if err != nil {
		return Value{}, err
	}
	return v, nil
}

GetValue retrieves a persistent value from the broker server’s key-value store for the current robot. The value is deserialized from MessagePack format into a Value struct, providing a type-safe way to store and retrieve structured data.

The method creates a context with timeout, constructs a request with the robot ID (from a global variable) and the specified key, and invokes the RPC. If the call fails, the error is logged and an empty Value is returned. If the value is empty, an empty Value is returned without error. Otherwise, the method uses MessagePack to decode the byte slice into a Value struct. This method is essential for robots that need to maintain state across restarts, such as tracking positions or accumulated metrics.

func (c *Client) SetValue(key string, value Value) error {
	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
	defer cancel()

	writer := bytes.NewBuffer(nil)
	enc := msgpack.NewEncoder(writer)
	var err error
	err = enc.Encode(&value)
	if err != nil {
		return err
	}

	data := writer.Bytes()

	request := &SetValueRequest{
		RobotId: id,
		Key:     key,
		Value:   data,
	}
	_, err = c.client.SetValue(ctx, request)
	if err != nil {
		log.Printf(”Log: %v”, err)
		return err
	}
	return nil
}

SetValue stores a value in the broker server’s key-value store for the current robot. The Value struct is serialized to MessagePack format before transmission, providing efficient binary encoding of structured data.

The method creates a context with timeout, serializes the Value using MessagePack into a byte buffer, and constructs a request with the robot ID (from a global variable), key, and serialized value bytes. If serialization fails, the error is returned immediately. The RPC is then invoked, and any errors are logged and returned. This method is the counterpart to GetValue and together they provide the persistence layer for stateful trading strategies.

func (c *Client) Log(sid int, id uint64, tm int64, level int32, message string) error {
	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
	defer cancel()
	request := &LogRequest{
		Sid:     int32(sid),
		Id:      id,
		Time:    tm,
		Level:   level,
		Message: message,
	}
	r, err := c.client.Log(ctx, request)
	if err != nil {
		log.Printf(”Log: %v”, err)
		return err
	}
	if r.Success {
		return nil
	}
	return errors.New(r.Message)
}

Log sends a log message from the trading robot to the central broker server. This enables centralized log aggregation where operators can monitor all robots from a single location rather than checking individual robot logs.

The method creates a context with timeout and constructs a LogRequest with the session ID, unique log ID, Unix timestamp, log level, and message content. The RPC is invoked, and if it fails, the error is logged locally and returned. If the server indicates failure in its response (Success is false), an error is created from the server’s message. This provides feedback when log storage fails on the server side.

func (c *Client) UpdateStatus(robotID string, status RobotStatus) error {
	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
	defer cancel()
	request := &UpdateStatusRequest{
		RobotId: robotID,
		Status:  int32(status),
	}
	r, err := c.client.UpdateStatus(ctx, request)
	if err != nil {
		log.Printf(”Log: %v”, err)
		return err
	}
	if r.Success {
		return nil
	}
	return errors.New(r.Message)
}

UpdateStatus reports the current operational status of the trading robot to the broker server. This allows the central system to track whether robots are running, paused, in error states, or in other defined operational modes.

The method creates a context with timeout and constructs an UpdateStatusRequest with the robot ID and numeric status code cast from the RobotStatus type. The RPC is invoked, and if it fails, the error is logged and returned. If the server indicates failure in its response, an error is created from the server’s message. Robots should call this method when their operational state changes to keep the monitoring system informed.

func (c *Client) UpdateStat(name string, value []byte) error {
	ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
	defer cancel()
	request := &StatRequest{
		RobotId: id,
		Name:    name,
		Value:   value,
	}
	r, err := c.client.UpdateStat(ctx, request)
	if err != nil {
		log.Printf(”Log: %v”, err)
		return err
	}
	if r.Success {
		return nil
	}
	return errors.New(r.Message)
}

UpdateStat sends statistical data from the trading robot to the broker server for performance monitoring and analytics. Statistics can include metrics like profit and loss, trade counts, win rates, or any other quantitative measures.

The method creates a context with timeout and constructs a StatRequest with the robot ID (from a global variable), metric name, and value as raw bytes. The RPC is invoked, and if it fails, the error is logged and returned. If the server indicates failure in its response, an error is created from the server’s message. Robots typically call this method periodically or after completing trading cycles to report their performance metrics.

func GetClient() *Client {
	once.Do(func() {
		instance = newClient()
	})
	return instance
}

GetClient returns the singleton Client instance, creating it on first call. This function uses sync.Once to ensure that the client is initialized exactly once, even when called concurrently from multiple goroutines. Subsequent calls return the same instance without reinitializing.

The singleton pattern is used here to ensure only one gRPC connection is established to the broker server per process, reducing resource usage and simplifying connection lifecycle management. All trading robot code should obtain the Client through this function rather than creating instances directly.

func newClient() *Client {
	conn, err := grpc.Dial(address, grpc.WithInsecure())
	if err != nil {
		log.Fatalf(”did not connect: %v”, err)
	}
	c := NewRobotCtlClient(conn)
	return &Client{client: c, conn: conn, timeout: clientTimeout}
}

newClient creates a new Client instance by establishing a gRPC connection to the broker server and wrapping it with the RobotCtlClient interface. This function is called only once by GetClient via sync.Once.

The function dials the broker server at the configured address using an insecure connection (no TLS). If the connection fails, the function logs a fatal error and terminates the program, as the robot cannot operate without broker connectivity. On success, it creates a RobotCtlClient from the connection and returns a new Client instance with the connection, client, and default timeout configured.

File: grpc_log.go

package goalgo

Package goalgo provides a logging adapter that bridges the local logging framework with the centralized gRPC-based logging infrastructure. This file implements the GRPCLog type which forwards log entries from the robot’s local logging system to the broker server for centralized aggregation and monitoring.

The GRPCLog adapter enables trading robots to emit log messages that are collected at a central location, making it easier for operators to monitor multiple robots from a single dashboard. By implementing the logging interface expected by the goalgo/log package, GRPCLog can be plugged into the logging pipeline to transparently redirect all log output to the broker server while still allowing local fallback logging when transmission fails.

import (
	stdlog “log”

	“github.com/frankrap/goalgo/log”
)

The import block brings in the dependencies required for the GRPCLog adapter. The stdlog import (aliased from “log”) provides access to standard Go logging for local fallback output when gRPC transmission fails. The log package from github.com/frankrap/goalgo/log provides the Entry type that represents structured log messages with fields like ID, timestamp, level, and message content.

The alias “stdlog” is used to avoid naming conflicts with the goalgo/log package and makes it clear which logger is being used for local output versus the structured logging framework.

type GRPCLog struct {
	SID int
}

GRPCLog is a logging adapter that implements the log handler interface from the goalgo/log package. It forwards log entries to the broker server via gRPC, enabling centralized log collection across all trading robots. The SID field contains the session identifier which is included with each log entry to help correlate logs from a specific robot session.

Trading robots create a GRPCLog instance with their session ID and register it with the logging framework. When log entries are generated, they are passed to the Log method which transmits them to the broker server.

func (l *GRPCLog) Log(e *log.Entry) error {
	err := GetClient().Log(l.SID, e.ID, e.Time.Unix(), int32(e.Level), e.Message)
	if err != nil {
		stdlog.Println(err)
	}
	return err
}

Log transmits a log entry to the broker server via the gRPC Client. This method implements the logging handler interface from the goalgo/log package, allowing GRPCLog to be registered as a log destination.

The method obtains the singleton Client via GetClient and calls its Log method with the session ID from the GRPCLog instance, the entry’s unique ID, the Unix timestamp of the entry, the log level cast to int32, and the message content. If the gRPC call fails, the error is printed to the standard log output as a fallback so that the failure is visible locally. The error is also returned to allow the calling code to handle logging failures if needed.

This design provides a best-effort logging mechanism where entries are sent to the central server but failures do not prevent the robot from continuing to operate. The fallback to stdlog ensures that error conditions are still visible even when remote logging is unavailable.

File: server.go

package goalgo

Package goalgo provides the main server entry point for hosting trading strategy plugins. This file implements the Serve function which is the primary entry point that trading strategy implementations call to start themselves as gRPC-based plugins. The function handles command-line argument parsing, dependency injection setup, and starts the HashiCorp go-plugin server infrastructure.

The plugin architecture allows trading strategies to run as separate processes that communicate with a host application via gRPC. This design provides isolation between strategies, enables independent strategy updates, and allows the host application to manage multiple strategy processes. The Serve function is called from a strategy’s main function after the strategy type has been instantiated.

import (
	“encoding/json”
	“flag”
	“fmt”
	“os”

	“github.com/facebookgo/inject”

	stdlog “log”

	“github.com/hashicorp/go-plugin”
	“github.com/frankrap/goalgo/log”
)

The import block brings in the dependencies required for the server functionality. The encoding/json package is used for serializing strategy options to JSON format. The flag package handles command-line argument parsing. The fmt package provides formatted output. The os package provides access to operating system functionality for process exit.

The inject package from github.com/facebookgo/inject provides dependency injection to wire up components at runtime. The stdlog alias for the standard log package provides fallback logging. The plugin package from github.com/hashicorp/go-plugin provides the plugin serving infrastructure. The log package from goalgo provides structured logging capabilities.

func Serve(strategy Strategy) {
	flag.StringVar(&id, “id”, “”, “”)
	flag.IntVar(&sid, “sid”, 0, “”)
	flag.StringVar(&address, “address”, “127.0.0.1:9900”, “”)

	flag.Parse()

	strategy.SetSelf(strategy)

	if id == “^” {
		options := strategy.GetOptions()
		data, err := json.Marshal(options)
		if err != nil {
			panic(err)
		}
		fmt.Printf(string(data))
		os.Exit(0)
		return
	}

	stdlog.Printf(”Serve id=%v sid=%v address=%v”, id, sid, address)

	var g inject.Graph

	l := &GRPCLog{SID: sid}
	g.Provide(
		&inject.Object{Value: log.GetLogger()},
		&inject.Object{Value: l},
	)
	if err := g.Populate(); err != nil {
		stdlog.Fatal(err)
	}

	plugins := map[string]plugin.Plugin{
		PluginMapStrategyCtlKey: &StrategyPlugin{Impl: strategy},
	}
	plugin.Serve(&plugin.ServeConfig{
		HandshakeConfig: HandshakeConfig,
		Plugins:         plugins,
	})
}

Serve is the main entry point for trading strategy plugins. This function should be called from a strategy’s main function to start the strategy as a plugin server. It handles all the initialization required to run a strategy including command-line parsing, dependency injection, and starting the gRPC plugin server.

The function first defines and parses three command-line flags: id for the robot identifier, sid for the session identifier, and address for the broker server connection address (defaulting to localhost:9900). These values are stored in package-level variables for use by other components like the Client.

After parsing, the function calls SetSelf on the strategy to enable the strategy to reference itself, which is needed for certain reflection-based operations.

If the id flag is set to the special value “^”, the function enters option discovery mode. In this mode, it calls GetOptions on the strategy to retrieve the available configuration options, serializes them to JSON, prints them to stdout, and exits. This allows the host application to discover what options a strategy supports without actually running the strategy.

For normal operation, the function sets up dependency injection using Facebook’s inject library. It creates a GRPCLog instance with the session ID and provides both the logger and the GRPCLog to the injection graph. The Populate call wires up all dependencies based on struct tags.

Finally, the function creates a plugin map with the strategy wrapped in a StrategyPlugin and starts the HashiCorp go-plugin server with the configured handshake settings. This server will listen for connections from the host application and dispatch calls to the strategy implementation.

File: strategy.go

package goalgo

Package goalgo defines the core interfaces and types that trading strategy implementations must satisfy. This file contains the Strategy interface which is the central contract for all trading strategies in the framework, as well as the ExchangeParams type used to pass exchange connection information to strategies during initialization.

The Strategy interface extends StrategyCtl to inherit control operations and adds lifecycle methods for setup, initialization, and execution. Any trading strategy must implement this interface to be compatible with the goalgo plugin architecture. The design separates concerns by having the host application handle exchange credential retrieval and pass them to the strategy during setup.

type ExchangeParams struct {
	Label     string
	Name      string
	AccessKey string
	SecretKey string
}

ExchangeParams contains the connection parameters for a single trading exchange. This struct is used to pass exchange configuration from the broker server to the trading strategy during initialization. The Label field is a user-friendly name for identifying the exchange in logs and UI. The Name field is the official exchange name used for API selection. The AccessKey and SecretKey fields contain the API credentials required to authenticate with the exchange.

These parameters are retrieved from the broker server via the GetRobotExchangeInfo RPC and passed to the strategy’s Setup method. The strategy uses these credentials to establish connections to the trading exchange APIs.

type Strategy interface {
	StrategyCtl
	SetSelf(self Strategy)
	Setup(params []ExchangeParams) error
	Init() error
	Run() error
}

Strategy is the primary interface that all trading strategies must implement to be compatible with the goalgo framework. It extends StrategyCtl to inherit the control operations used by the host application to manage the strategy’s lifecycle and query its configuration.

The interface defines four methods that represent the complete lifecycle of a trading strategy. SetSelf allows the strategy to maintain a reference to itself, which is needed when the concrete implementation is embedded in a base type and needs to call overridden methods polymorphically. Setup receives the exchange connection parameters and should configure exchange API clients. Init performs any initialization needed before the strategy can run, such as loading historical data or calculating initial indicators. Run is the main execution method that starts the trading logic and typically runs until stopped.

Strategies implementing this interface are wrapped by the StrategyPlugin and hosted via the HashiCorp go-plugin infrastructure. The host application controls the strategy by calling these methods in sequence: SetSelf, Setup with exchange params, Init for initialization, and Run to begin trading.

File: strategyctl.go

package goalgo

Package goalgo provides the HashiCorp go-plugin infrastructure for strategy control operations. This file implements the plugin architecture that enables trading strategies to run as separate processes and be controlled by a host application via RPC. The implementation includes the StrategyCtl interface defining control operations, RPC client and server types for cross-process communication, and the StrategyPlugin type that ties everything together.

The go-plugin framework provides process isolation between the host application and strategy plugins, enabling independent updates, crash isolation, and language-agnostic communication. This file defines both sides of the RPC bridge: the client side used by the host to control strategies, and the server side run within strategy processes to handle incoming control commands.

import (
	“net/rpc”

	“github.com/hashicorp/go-plugin”
)

The import block brings in the dependencies required for the plugin infrastructure. The net/rpc package provides the standard Go RPC mechanism used by go-plugin for communication between the host and plugin processes. The plugin package from github.com/hashicorp/go-plugin provides the plugin hosting framework that handles process management, connection negotiation, and RPC setup.

const (
	PluginMapStrategyCtlKey = “strategyCtl”
)

This constant block defines the plugin identification key used to register and look up the strategy control plugin in the plugin map. The PluginMapStrategyCtlKey constant is used consistently throughout the codebase to identify the strategy control plugin, ensuring that both the host and plugin sides use the same key.

var HandshakeConfig = plugin.HandshakeConfig{
	ProtocolVersion:  plugin.CoreProtocolVersion,
	MagicCookieKey:   “STRATEGY_PLUGIN”,
	MagicCookieValue: “QtMWr4VtAC*r”,
}

HandshakeConfig defines the handshake parameters used to establish connections between the host application and strategy plugins. The handshake ensures that both sides are compatible and prevents accidental connections between unrelated plugins. ProtocolVersion uses the core protocol version from go-plugin. The MagicCookieKey and MagicCookieValue are arbitrary strings that must match between host and plugin to establish a connection, providing a simple security mechanism.

var PluginMap = map[string]plugin.Plugin{
	PluginMapStrategyCtlKey: &StrategyPlugin{},
}

PluginMap is the default plugin map used by the host application to identify available plugins. It maps plugin names to their implementations. The single entry maps the strategy control key to an empty StrategyPlugin which will be populated when a plugin connection is established. The host uses this map during plugin connection to know what plugins to expect.

type StrategyCtl interface {
	GetState() RobotStatus
	GetOptions() (optionMap map[string]*OptionInfo)
	SetOptions(options map[string]interface{}) plugin.BasicError
	QueueCommand(command string) plugin.BasicError
	Start() plugin.BasicError
	Stop() plugin.BasicError
	Pause() plugin.BasicError
}

StrategyCtl defines the interface for controlling trading strategies remotely. This interface is implemented by strategies and exposed via RPC, allowing the host application to query strategy state, configure options, send commands, and control the strategy lifecycle.

GetState returns the current operational status of the strategy as a RobotStatus. GetOptions returns a map of available configuration options with their metadata. SetOptions applies configuration values to the strategy. QueueCommand sends a command string to be processed by the strategy. Start, Stop, and Pause control the strategy’s execution state. All mutating operations return a plugin.BasicError to indicate success or provide error details.

type StrategyRPC struct{ client *rpc.Client }

StrategyRPC is the RPC client implementation of StrategyCtl. It is used by the host application to communicate with strategy plugins. Each method makes an RPC call to the corresponding Plugin.* method on the strategy server, marshaling arguments and unmarshaling responses. The client field holds the RPC client connection established by go-plugin during plugin initialization.

func (g *StrategyRPC) GetState() RobotStatus {
	var resp RobotStatus
	err := g.client.Call(”Plugin.GetState”, new(interface{}), &resp)
	if err != nil {
		return RobotStatusDisabled
	}

	return resp
}

GetState retrieves the current operational status of the strategy via RPC. The method calls Plugin.GetState on the strategy server with an empty argument and receives a RobotStatus response. If the RPC call fails, the method returns RobotStatusDisabled as a safe default, indicating the strategy is not operational. This graceful degradation prevents errors from propagating and allows the host to handle unresponsive plugins.

func (g *StrategyRPC) GetOptions() (optionMap map[string]*OptionInfo) {
	var resp map[string]*OptionInfo
	err := g.client.Call(”Plugin.GetOptions”, new(interface{}), &resp)
	if err != nil {
		return map[string]*OptionInfo{}
	}

	return resp
}

GetOptions retrieves the available configuration options from the strategy via RPC. The method calls Plugin.GetOptions on the strategy server and returns a map of option names to OptionInfo structs containing metadata about each option. If the RPC call fails, an empty map is returned to allow the host to continue operating without crashing. Options are typically retrieved when displaying a configuration interface.

func (g *StrategyRPC) SetOptions(options map[string]interface{}) plugin.BasicError {
	var resp plugin.BasicError
	err := g.client.Call(”Plugin.SetOptions”, options, &resp)
	if err != nil {
		return plugin.BasicError{Message: err.Error()}
	}

	return resp
}

SetOptions applies configuration values to the strategy via RPC. The method sends the options map to Plugin.SetOptions on the strategy server. If the RPC call fails, a BasicError containing the error message is returned. On success, the server’s response is returned which may indicate validation errors or success.

func (g *StrategyRPC) QueueCommand(command string) plugin.BasicError {
	var resp plugin.BasicError
	err := g.client.Call(”Plugin.QueueCommand”, command, &resp)
	if err != nil {
		return plugin.BasicError{Message: err.Error()}
	}

	return resp
}

QueueCommand sends a command string to the strategy for execution via RPC. Commands are strategy-specific and may trigger actions like placing orders, adjusting parameters, or performing analysis. The method calls Plugin.QueueCommand on the strategy server and returns a BasicError indicating the result.

func (g *StrategyRPC) Start() plugin.BasicError {
	var resp plugin.BasicError
	err := g.client.Call(”Plugin.Start”, new(interface{}), &resp)
	if err != nil {
		return plugin.BasicError{Message: err.Error()}
	}

	return resp
}

Start instructs the strategy to begin execution via RPC. This transitions the strategy from an initialized or paused state to an active running state. The method calls Plugin.Start on the strategy server with an empty argument and returns a BasicError indicating success or failure. If the RPC call itself fails, the error is wrapped in a BasicError.

func (g *StrategyRPC) Stop() plugin.BasicError {
	var resp plugin.BasicError
	err := g.client.Call(”Plugin.Stop”, new(interface{}), &resp)
	if err != nil {
		return plugin.BasicError{Message: err.Error()}
	}

	return resp
}

Stop instructs the strategy to cease execution via RPC. This transitions the strategy from a running state to a stopped state. The method calls Plugin.Stop on the strategy server with an empty argument and returns a BasicError indicating success or failure. Stopped strategies typically cannot be restarted without reinitializing.

func (g *StrategyRPC) Pause() plugin.BasicError {
	var resp plugin.BasicError
	err := g.client.Call(”Plugin.Pause”, new(interface{}), &resp)
	if err != nil {
		return plugin.BasicError{Message: err.Error()}
	}

	return resp
}

Pause instructs the strategy to temporarily suspend execution via RPC. This transitions the strategy from a running state to a paused state where it remains initialized but does not actively trade. The method calls Plugin.Pause on the strategy server and returns a BasicError. Paused strategies can typically be resumed with Start.

type StrategyRPCServer struct {
	Impl StrategyCtl
}

StrategyRPCServer is the RPC server implementation that runs within the strategy plugin process. It receives RPC calls from the host application and delegates them to the actual StrategyCtl implementation. The Impl field holds a reference to the strategy that implements the StrategyCtl interface.

func (s *StrategyRPCServer) GetState(args interface{}, resp *RobotStatus) error {
	*resp = s.Impl.GetState()
	return nil
}

GetState handles incoming RPC requests to retrieve strategy state. It calls GetState on the implementation and writes the result to the response pointer. The method returns nil to indicate the RPC completed without transport errors.

func (s *StrategyRPCServer) GetOptions(args interface{}, resp *map[string]*OptionInfo) error {
	*resp = s.Impl.GetOptions()
	return nil
}

GetOptions handles incoming RPC requests to retrieve available options. It calls GetOptions on the implementation and writes the resulting map to the response pointer.

func (s *StrategyRPCServer) SetOptions(args map[string]interface{}, resp *plugin.BasicError) error {
	*resp = s.Impl.SetOptions(args)
	return nil
}

SetOptions handles incoming RPC requests to apply configuration options. It calls SetOptions on the implementation with the provided options map and writes the result to the response pointer.

func (s *StrategyRPCServer) QueueCommand(args string, resp *plugin.BasicError) error {
	*resp = s.Impl.QueueCommand(args)
	return nil
}

QueueCommand handles incoming RPC requests to queue a command. It calls QueueCommand on the implementation with the command string and writes the result to the response.

func (s *StrategyRPCServer) Start(args interface{}, resp *plugin.BasicError) error {
	*resp = s.Impl.Start()
	return nil
}

Start handles incoming RPC requests to start the strategy. It calls Start on the implementation and writes the result to the response pointer.

func (s *StrategyRPCServer) Stop(args interface{}, resp *plugin.BasicError) error {
	*resp = s.Impl.Stop()
	return nil
}

Stop handles incoming RPC requests to stop the strategy. It calls Stop on the implementation and writes the result to the response pointer.

func (s *StrategyRPCServer) Pause(args interface{}, resp *plugin.BasicError) error {
	*resp = s.Impl.Pause()
	return nil
}

Pause handles incoming RPC requests to pause the strategy. It calls Pause on the implementation and writes the result to the response pointer.

type StrategyPlugin struct {
	Impl StrategyCtl
}

StrategyPlugin implements the plugin.Plugin interface from go-plugin, tying together the RPC client and server implementations. This type is registered in the plugin map and handles the creation of appropriate RPC endpoints based on whether the code is running as a host or a plugin. The Impl field holds the actual strategy implementation when running as a plugin server.

func (p *StrategyPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
	return &StrategyRPCServer{Impl: p.Impl}, nil
}

Server creates and returns the RPC server instance for the strategy plugin. This method is called by go-plugin when the code is running as a plugin server. It creates a StrategyRPCServer wrapping the strategy implementation and returns it. The MuxBroker parameter allows for multiplexed connections but is not used here.

func (StrategyPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
	return &StrategyRPC{client: c}, nil
}

Client creates and returns the RPC client instance for communicating with a strategy plugin. This method is called by go-plugin when the code is running as the host application. It creates a StrategyRPC client wrapper around the provided RPC client connection and returns it. The MuxBroker parameter allows for multiplexed connections.

File: value.go

package goalgo

Package goalgo provides this value.go file which defines the Value type and a comprehensive suite of type conversion utilities. The Value type is a central data structure in the goalgo trading robot framework, serving as a typed container that can hold values of various Go primitive types while preserving type information for correct serialization and deserialization.

The primary purpose of this file is to provide a flexible, serializable value container that can be transmitted between trading robots and the broker server over gRPC, and stored in the server’s key-value persistence store. Trading robots need to store and retrieve various types of data such as position sizes (integers), profit and loss (floating-point), configuration flags (booleans), and identifiers (strings). The Value type supports all these use cases with a single unified type that knows how to serialize itself using MessagePack binary encoding.

The file is organized into several logical sections. First, it defines the ValueKind enumeration that discriminates between different value types. Second, it defines the Value struct itself with its Kind and Value fields. Third, it implements the msgpack.CustomEncoder and CustomDecoder interfaces for binary serialization. Fourth, it provides methods on the Value type for type conversion. Fifth, it provides constructor functions for creating Value instances from Go primitives. Finally, it provides standalone utility functions for converting arbitrary interface values to specific Go types, which are used both by the Value methods and available for general use throughout the framework.

The design follows a tagged union pattern where the Kind field indicates how to interpret the Value field. This is necessary because Go’s interface{} type loses type information at runtime in certain scenarios, particularly when values are deserialized from binary formats. By explicitly storing the type tag, the Value type ensures that numeric types are not inadvertently widened or narrowed during serialization round-trips.

import (
	“fmt”
	“log”
	“math”
	“strconv”
	“github.com/vmihailenco/msgpack”
)

The import block brings in the dependencies required for type conversion and serialization. The fmt package provides formatting functions used for string conversion of values. The log package is used for logging unexpected value types during SetValue operations. The math package provides constants for integer bounds checking during type conversions. The strconv package provides string-to-number parsing for converting string representations to numeric types.

The msgpack package from github.com/vmihailenco/msgpack provides MessagePack binary serialization. MessagePack is a compact binary format that is more efficient than JSON for transmitting structured data over the network. The Value type implements custom encoding and decoding methods that work with this package to control exactly how Value instances are serialized.

type ValueKind uint32

ValueKind is an enumeration type that identifies the specific Go type stored within a Value instance. This type tag is essential for correct serialization and deserialization because Go’s interface{} type can lose precision type information when values pass through binary encoding. For example, an int32 might be deserialized as an int64 without the explicit tag.

The enumeration uses iota to assign sequential integer values starting from zero. ValueNil represents the absence of a value or an uninitialized Value. ValueBoolean represents bool values. ValueString represents string values. The integer types ValueInt32 and ValueInt64 represent signed integers of their respective bit widths. ValueUint32 and ValueUint64 represent unsigned integers. ValueFloat32 and ValueFloat64 represent IEEE floating-point numbers. The underlying type is uint32 to ensure consistent size across platforms and compatibility with MessagePack encoding.

const (
	ValueNil ValueKind = iota
	ValueBoolean
	ValueString
	ValueInt32
	ValueInt64
	ValueUint32
	ValueUint64
	ValueFloat32
	ValueFloat64
)

This constant block defines the actual enumeration values for ValueKind using Go’s iota mechanism. Each constant represents a distinct value type that the Value struct can hold. The constants are ordered with ValueNil as zero so that the zero value of ValueKind correctly represents an uninitialized or nil value. This ordering is intentional and should not be changed as it may affect serialized data compatibility with existing stored values.

type Value struct {
	Kind  ValueKind
	Value interface{}
}

Value is the core data structure for storing typed values that need to be serialized and transmitted between components of the goalgo framework. It implements a tagged union pattern where the Kind field indicates the type of the data stored in the Value field.

The Kind field stores a ValueKind enumeration value that identifies the Go type of the stored value. The Value field is an interface{} that holds the actual data. When creating or setting values, both fields must be kept in sync so that the Kind accurately reflects what is stored in Value. The methods on this type maintain this invariant.

This struct is used extensively throughout the framework. The Client type in grpc_client.go uses Value for the GetValue and SetValue RPC operations. The value_api.go file provides convenience functions that wrap these operations. Trading strategies store and retrieve their persistent state using Value instances.

var (
	_ msgpack.CustomEncoder = &Value{}
	_ msgpack.CustomDecoder = &Value{}
)

This variable block contains compile-time assertions that verify the Value type correctly implements the msgpack.CustomEncoder and msgpack.CustomDecoder interfaces. The blank identifier assignments attempt to assign a *Value pointer to variables of the interface types. If Value does not implement the required methods, this code will fail to compile.

This pattern is a common Go idiom for ensuring interface compliance at compile time rather than discovering the problem at runtime. It provides documentation that Value is intended to be used with the msgpack package and guarantees that the EncodeMsgpack and DecodeMsgpack methods are present with the correct signatures.

func (r *Value) EncodeMsgpack(enc *msgpack.Encoder) error {
	return enc.EncodeMulti(r.Kind, r.Value)
}

EncodeMsgpack implements the msgpack.CustomEncoder interface for the Value type, enabling custom control over how Value instances are serialized to MessagePack binary format. This method is called automatically by the msgpack.Encoder when encoding a Value instance.

The method works by using the encoder’s EncodeMulti function to write both the Kind field and the Value field as separate MessagePack elements. This produces a compact binary representation where the type tag precedes the actual value data. The EncodeMulti function handles the details of encoding each field according to its type.

This custom encoding is essential for preserving type information across serialization round-trips. Without it, the msgpack package would encode the Value struct using its default behavior, which might not correctly preserve the relationship between the Kind tag and the stored value, especially for numeric types that could be coerced during decoding.

func (r *Value) DecodeMsgpack(enc *msgpack.Decoder) error {
	return enc.DecodeMulti(&r.Kind, &r.Value)
}

DecodeMsgpack implements the msgpack.CustomDecoder interface for the Value type, enabling custom control over how Value instances are deserialized from MessagePack binary format. This method is called automatically by the msgpack.Decoder when decoding into a Value instance.

The method works by using the decoder’s DecodeMulti function to read both the Kind field and the Value field from the binary stream. The DecodeMulti function reads elements in the same order they were written by EncodeMsgpack, first populating the Kind field and then the Value field. Pointers to both fields are passed so that DecodeMulti can modify them directly.

This custom decoding ensures that the type tag is correctly restored along with the value data, maintaining the invariant that Kind accurately describes what is stored in Value. The decoded Value instance will have both fields populated and ready for use with the type conversion methods like ToBool, ToString, ToInt, and ToFloat64.

func (value *Value) ToBool() bool {
	return ToBool(value.Value)
}

ToBool is a method on the Value type that converts the stored value to a boolean. This method delegates to the standalone ToBool function, passing the Value field as the input to be converted. It provides a convenient way to extract a boolean interpretation of any stored value type.

The conversion follows the truthiness rules implemented in the standalone ToBool function. Boolean values are returned directly. Strings are false if empty or equal to “false”, true otherwise. Numeric values are false if zero, true otherwise. This method is useful when a Value may contain various types but needs to be interpreted as a boolean condition.

func (value *Value) ToString() string {
	if value.Kind == ValueString {
		return value.Value.(string)
	}
	if v, ok := value.Value.(string); ok {
		return v
	}
	if value.Value == nil {
		return “”
	}
	return fmt.Sprintf(”%v”, value.Value)
}

ToString is a method on the Value type that converts the stored value to a string. This method provides intelligent string conversion that handles different scenarios based on the Kind field and the actual type of the stored value.

The method first checks if the Kind field is ValueString, and if so, performs a direct type assertion to extract the string. This fast path avoids unnecessary processing for the common case of string values. If the Kind is not ValueString, the method attempts a type assertion to string anyway, in case the Value field happens to contain a string despite the Kind field indicating otherwise. If the Value field is nil, an empty string is returned.

For all other cases, the method uses fmt.Sprintf with the %v verb to produce a string representation of the value. This handles numeric types, booleans, and any other types by using their default string formatting. This method is essential for displaying values in logs, user interfaces, or when constructing string-based messages.

func (value *Value) ToInt() int {
	return ToInt(value.Value)
}

ToInt is a method on the Value type that converts the stored value to an int. This method delegates to the standalone ToInt function, passing the Value field as the input to be converted. It provides a convenient way to extract an integer interpretation of any stored value type.

The conversion follows the rules implemented in the standalone ToInt function, which handles a wide variety of input types including all integer sizes, floating-point numbers, booleans, and strings. Values that exceed the range of int32 are converted to zero as a safety measure. This method is commonly used when extracting configuration values or counters from stored data.

func (value *Value) ToFloat64() float64 {
	return ToFloat(value.Value)
}

ToFloat64 is a method on the Value type that converts the stored value to a float64. This method delegates to the standalone ToFloat function, passing the Value field as the input to be converted. It provides a convenient way to extract a floating-point interpretation of any stored value type.

The conversion follows the rules implemented in the standalone ToFloat function, which handles input types including booleans, integers, floating-point numbers, and strings. This method is commonly used when extracting price data, percentages, or other decimal values from stored data in trading strategies.

func (value *Value) SetValue(val interface{}) {
	switch v := val.(type) {
	case bool:
		value.Kind = ValueBoolean
		value.Value = v
	case string:
		 
		value.Kind = ValueString
		value.Value = v
	case int:
		value.Kind = ValueInt32
		value.Value = int32(v)
	case int32:
		value.Kind = ValueInt32
		value.Value = v
	case int64:
		value.Kind = ValueInt64
		value.Value = v
	case float32:
		value.Kind = ValueFloat32
		value.Value = v
	case float64:
		value.Kind = ValueFloat64
		value.Value = v
	default:
		log.Printf(”%#v”, val)
		value.Value = “”
	}
}

SetValue is a method on the Value type that sets both the Kind and Value fields based on the Go type of the provided input. This method uses a type switch to examine the input type and set both fields appropriately, maintaining the invariant that Kind always correctly describes what is stored in Value.

The method handles boolean, string, int, int32, int64, float32, and float64 types. For the int type specifically, the value is converted to int32 before storage, and the Kind is set to ValueInt32. This normalization ensures consistent behavior across different platforms where the size of int may vary. Each recognized type sets the Kind field to the corresponding ValueKind constant and stores the value directly or after conversion.

For unrecognized types, the method logs the value using log.Printf for debugging purposes and sets the Value field to an empty string. This fallback behavior prevents panics but indicates a programming error where an unsupported type was passed. Callers should ensure they only pass supported types to avoid this fallback.

func BoolValue(value bool) Value {
	return Value{Value: value, Kind: ValueBoolean}
}

BoolValue is a constructor function that creates a new Value instance containing a boolean. The function sets both the Value and Kind fields appropriately, returning a Value that is ready for serialization or use with the Value methods.

This function and the other type-specific constructor functions provide a clean, readable way to create Value instances without manually setting both fields. They are used throughout the framework when creating values to store or transmit. The returned Value has Kind set to ValueBoolean and the Value field set to the input boolean.

func StringValue(value string) Value {
	return Value{Value: value, Kind: ValueString}
}

StringValue is a constructor function that creates a new Value instance containing a string. The function sets both the Value and Kind fields appropriately, returning a Value with Kind set to ValueString and the Value field containing the input string.

This constructor is commonly used when creating configuration values or identifiers that need to be stored persistently or transmitted to the broker server.

func IntValue(value int) Value {
	return Value{Value: int32(value), Kind: ValueInt32}
}

IntValue is a constructor function that creates a new Value instance containing an integer. The function converts the input int to int32 before storage to ensure consistent behavior across platforms where int may have different sizes. The returned Value has Kind set to ValueInt32 and the Value field containing the converted int32.

This normalization to int32 matches the behavior of the SetValue method and ensures that integer values are consistently represented regardless of how they were created.

func Int32Value(value int32) Value {
	return Value{Value: value, Kind: ValueInt32}
}

Int32Value is a constructor function that creates a new Value instance containing a 32-bit signed integer. The function sets the Kind to ValueInt32 and stores the int32 value directly without any conversion. This is the most direct way to create an integer Value when the source is already int32.

func Int64Value(value int64) Value {
	return Value{Value: value, Kind: ValueInt64}
}

Int64Value is a constructor function that creates a new Value instance containing a 64-bit signed integer. The function sets the Kind to ValueInt64 and stores the int64 value directly. This constructor is used when the full range of int64 is needed, such as for Unix timestamps or large counters that exceed the int32 range.

func Float32Value(value float32) Value {
	return Value{Value: value, Kind: ValueFloat32}
}

Float32Value is a constructor function that creates a new Value instance containing a 32-bit floating-point number. The function sets the Kind to ValueFloat32 and stores the float32 value directly. This constructor is used when single-precision floating-point is sufficient and memory or bandwidth efficiency is desired.

func Float64Value(value float64) Value {
	return Value{Value: value, Kind: ValueFloat64}
}

Float64Value is a constructor function that creates a new Value instance containing a 64-bit floating-point number. The function sets the Kind to ValueFloat64 and stores the float64 value directly. This is the most commonly used floating-point constructor as float64 provides the precision needed for financial calculations in trading strategies.

func ToBool(value interface{}) bool {
	switch value := value.(type) {
	case bool:
		return value
	case *bool:
		return *value
	case string:
		switch value {
		case “”, “false”:
			return false
		}
		return true
	case *string:
		return ToBool(*value)
	case float64:
		if value != 0 {
			return true
		}
		return false
	case *float64:
		return ToBool(*value)
	case float32:
		if value != 0 {
			return true
		}
		return false
	case *float32:
		return ToBool(*value)
	case int:
		if value != 0 {
			return true
		}
		return false
	case *int:
		return ToBool(*value)
	}
	return false
}

ToBool is a standalone utility function that converts an arbitrary interface{} value to a boolean using Go-like truthiness rules. This function is used by the Value.ToBool method and is also available for general use throughout the framework when boolean interpretation of various types is needed.

The function uses a type switch to examine the input and apply type-specific conversion rules. Boolean values and pointers to booleans are returned directly or dereferenced. String values return false if empty or equal to the literal string “false”, and true for any other non-empty string. Pointer types are dereferenced and recursively converted.

Numeric types including float64, float32, and int return false if the value is zero and true for any non-zero value. This follows the common convention in many programming languages where zero is falsy and non-zero is truthy. For pointer types to these numeric values, the function dereferences the pointer and recursively calls itself.

For any unrecognized type, the function returns false as a safe default. This ensures that the function never panics and always produces a valid boolean result.

func ToInt64(value interface{}) int64 {
	i := ToInt(value)
	return int64(i)
}

ToInt64 is a utility function that converts an arbitrary interface{} value to an int64. The function delegates to the ToInt function and then widens the result to int64. This provides a simple way to get a 64-bit integer from any supported input type.

Note that because this function uses ToInt internally, values that exceed the int32 range will first be converted to zero by ToInt, and then that zero will be widened to int64. This means the function does not truly support the full int64 range for all input types.

func ToUint64(value interface{}) uint64 {
	i := ToInt(value)
	return uint64(i)
}

ToUint64 is a utility function that converts an arbitrary interface{} value to a uint64. The function delegates to the ToInt function and then converts the result to uint64. This provides a simple way to get an unsigned 64-bit integer from any supported input type.

Note that because this function uses ToInt internally, negative values will be converted to their unsigned representation according to Go’s type conversion rules, and values that exceed the int32 range will first be converted to zero by ToInt. Care should be taken when using this function with potentially large or negative values.

func ToInt(value interface{}) int {
	switch value := value.(type) {
	case bool:
		if value == true {
			return 1
		}
		return 0
	case int:
		if value < int(math.MinInt32) || value > int(math.MaxInt32) {
			return 0
		}
		return value
	case *int:
		return ToInt(*value)
	case int8:
		return int(value)
	case *int8:
		return int(*value)
	case int16:
		return int(value)
	case *int16:
		return int(*value)
	case int32:
		return int(value)
	case *int32:
		return int(*value)
	case int64:
		if value < int64(math.MinInt32) || value > int64(math.MaxInt32) {
			return 0
		}
		return int(value)
	case *int64:
		return ToInt(*value)
	case uint:
		if value > math.MaxInt32 {
			return 0
		}
		return int(value)
	case *uint:
		return ToInt(*value)
	case uint8:
		return int(value)
	case *uint8:
		return int(*value)
	case uint16:
		return int(value)
	case *uint16:
		return int(*value)
	case uint32:
		if value > uint32(math.MaxInt32) {
			return 0
		}
		return int(value)
	case *uint32:
		return ToInt(*value)
	case uint64:
		if value > uint64(math.MaxInt32) {
			return 0
		}
		return int(value)
	case *uint64:
		return ToInt(*value)
	case float32:
		if value < float32(math.MinInt32) || value > float32(math.MaxInt32) {
			return 0
		}
		return int(value)
	case *float32:
		return ToInt(*value)
	case float64:
		if value < float64(math.MinInt32) || value > float64(math.MaxInt32) {
			return 0
		}
		return int(value)
	case *float64:
		return ToInt(*value)
	case string:
		val, err := strconv.ParseFloat(value, 0)
		if err != nil {
			return 0
		}
		return ToInt(val)
	case *string:
		return ToInt(*value)
	}
	return 0
}

ToInt is a comprehensive utility function that converts an arbitrary interface{} value to an int. This function handles a wide variety of input types and is used by the Value.ToInt method as well as being available for general use throughout the framework.

The function uses a type switch to examine the input and apply type-specific conversion logic. Boolean true converts to 1 and false converts to 0. Integer types of all sizes (int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64) are converted to int, with range checking for larger types. If a value exceeds the range of int32 (approximately negative 2 billion to positive 2 billion), the function returns 0 as a safety measure to prevent silent overflow.

Floating-point types (float32 and float64) are truncated to integers after range checking. String values are first parsed as float64 using strconv.ParseFloat and then recursively converted through this function, allowing strings like “42” or “3.14” to be converted to integers. Pointer types are dereferenced and recursively converted.

For any unrecognized type, the function returns 0 as a safe default. The range checking and zero-on-overflow behavior is a deliberate design decision to prevent subtle bugs from silent overflow, though callers should be aware that out-of-range values silently become zero.

func ToFloat32(value interface{}) float32 {
	switch value := value.(type) {
	case bool:
		if value == true {
			return 1.0
		}
		return 0.0
	case *bool:
		return ToFloat32(*value)
	case int:
		return float32(value)
	case *int32:
		return ToFloat32(*value)
	case float32:
		return value
	case *float32:
		return ToFloat32(*value)
	case float64:
		return ToFloat32(fmt.Sprintf(”%v”, value))
	case *float64:
		return ToFloat32(*value)
	case string:
		val, err := strconv.ParseFloat(value, 32)
		if err != nil {
			return 0
		}
		return float32(val)
	case *string:
		return ToFloat32(*value)
	}
	return 0.0
}

ToFloat32 is a utility function that converts an arbitrary interface{} value to a float32. This function handles several input types and provides single-precision floating-point conversion for use cases where float32 is sufficient or required.

The function uses a type switch to examine the input and apply type-specific conversion logic. Boolean true converts to 1.0 and false converts to 0.0. Integer types are directly converted to float32. Float32 values are returned unchanged. Float64 values are converted by first formatting to a string and then parsing as float32, which provides a form of precision-aware conversion rather than direct truncation.

String values are parsed using strconv.ParseFloat with a bit size of 32, and the result is cast to float32. If parsing fails, the function returns 0. Pointer types are dereferenced and recursively converted. For any unrecognized type, the function returns 0.0 as a safe default.

func ToFloat(value interface{}) float64 {
	switch value := value.(type) {
	case bool:
		if value == true {
			return 1.0
		}
		return 0.0
	case *bool:
		return ToFloat(*value)
	case int:
		return float64(value)
	case *int32:
		return ToFloat(*value)
	case float32:
		return ToFloat(fmt.Sprintf(”%v”, value))
	case *float32:
		return ToFloat(*value)
	case float64:
		return value
	case *float64:
		return ToFloat(*value)
	case string:
		val, err := strconv.ParseFloat(value, 0)
		if err != nil {
			return 0
		}
		return val
	case *string:
		return ToFloat(*value)
	}
	return 0.0
}

ToFloat is a utility function that converts an arbitrary interface{} value to a float64. This function is used by the Value.ToFloat64 method and is also available for general use throughout the framework when double-precision floating-point interpretation is needed.

The function uses a type switch to examine the input and apply type-specific conversion logic. Boolean true converts to 1.0 and false converts to 0.0. Integer types are directly converted to float64 with full precision. Float64 values are returned unchanged. Float32 values are converted by first formatting to a string and then parsing as float64, which preserves the original precision of the float32 value rather than introducing artifacts from direct type conversion.

String values are parsed using strconv.ParseFloat with automatic bit size detection, and the parsed value is returned. If parsing fails, the function returns 0. Pointer types are dereferenced and recursively converted. For any unrecognized type, the function returns 0.0 as a safe default.

This function is essential for trading strategies that work with price data, percentages, and other decimal values that require the precision of float64 for accurate financial calculations.

File: value_api.go

package goalgo

Package goalgo provides this value_api.go file as a high-level, convenience API layer for persistent value storage operations. This file sits above the lower-level Client methods defined in grpc_client.go and provides a simplified interface for trading robots to store and retrieve structured data across restarts.

The primary purpose of this file is to implement a facade pattern that hides the complexity of client singleton management from callers. Instead of requiring users to call GetClient() and then invoke methods on the returned pointer, this file exports package-level functions that internally obtain the client and delegate to its methods. This results in cleaner, more readable code at call sites throughout the trading robot implementations.

The value persistence mechanism is essential for stateful trading strategies that need to maintain information such as position sizes, accumulated profit and loss, trade history summaries, or any other data that must survive robot restarts. The underlying storage is managed by the central broker server, and values are serialized using MessagePack format for efficient binary encoding before transmission over gRPC.

Data flow in this file follows a simple pattern: each function obtains the singleton Client instance via GetClient(), delegates the actual work to the corresponding Client method, and then applies any additional error handling or transformation logic before returning the result to the caller. The SetValue function passes through results directly, while GetValue includes special handling for the ErrNotFound case to provide a more convenient return value.

import (
	“errors”
)

The import block brings in the errors package from the Go standard library. This package provides functionality for creating error values with descriptive messages. The errors package is used here to define the sentinel error ErrNotFound, which represents the condition when a requested key does not exist in the persistent value store.

No other imports are required in this file because all the complex functionality such as gRPC communication, MessagePack serialization, context management, and timeout handling is encapsulated within the Client type and its methods defined in grpc_client.go. This file only needs to create error values and delegate to the Client.

var (
	 
	ErrNotFound = errors.New(”not found”)
)

This variable block defines package-level error values used by the value API functions. The ErrNotFound variable is a sentinel error created using errors.New that indicates when a requested key does not exist in the broker server’s key-value store.

Sentinel errors like ErrNotFound follow a common Go pattern where specific error conditions are represented by package-level variables that callers can compare against using the == operator or errors.Is function. This allows callers to distinguish between different error conditions and handle them appropriately. For example, a caller might want to treat a “not found” condition differently from a network failure or serialization error.

In the context of this file, ErrNotFound is used in the GetValue function to detect when the underlying Client.GetValue method returns this specific error condition. When detected, GetValue returns an empty Value struct with a nil error, effectively treating a missing key as a request for a default empty value rather than an error condition. This design choice simplifies caller code by eliminating the need to check for “not found” errors in the common case where an empty default is acceptable.

func GetValue(key string) (Value, error) {
	value, err := GetClient().GetValue(key)
	if err != nil && err == ErrNotFound {
		return Value{}, nil
	}
	return value, err
}

GetValue retrieves a persistent value from the broker server’s key-value store using the specified key. This function provides a high-level, convenient interface for value retrieval that automatically manages the client singleton and applies special handling for the “not found” error condition.

The function works by first obtaining the singleton Client instance through the GetClient function, which ensures that only one gRPC connection is maintained per process. It then calls the Client.GetValue method, passing the provided key. The Client.GetValue method handles all the details of context creation with timeout, gRPC request construction with the robot ID and key, RPC invocation, and MessagePack deserialization of the response bytes into a Value struct.

The key design decision in this function is the special handling of the ErrNotFound error. When the underlying Client.GetValue returns ErrNotFound, this function catches that specific error and returns an empty Value struct with a nil error instead of propagating the error to the caller. This design treats a missing key as equivalent to an empty value rather than an error condition, which simplifies caller code in the common scenario where an empty default is acceptable for missing keys. For example, a strategy checking for a saved position size can simply use the returned Value without first checking for a “not found” error.

All other errors from Client.GetValue, such as network failures, timeout errors, or MessagePack deserialization errors, are propagated to the caller unchanged along with the value (which may be empty or partially filled depending on when the error occurred). This allows callers to distinguish between transient infrastructure issues that may require retry logic and normal operation with missing or present values.

The function connects to the broader project by providing trading robot strategies with a simple way to access their persistent state. Strategies such as those defined in the algo/ subdirectory can call GetValue with a key like “position” or “lastTradeTime” to retrieve previously saved state, enabling them to resume operation correctly after restarts.

func SetValue(key string, value Value) error {
	return GetClient().SetValue(key, value)
}

SetValue stores a value in the broker server’s key-value store under the specified key. This function provides a high-level, convenient interface for value storage that automatically manages the client singleton and delegates all the complex work to the underlying Client.

The function works by first obtaining the singleton Client instance through the GetClient function, ensuring that only one gRPC connection is maintained throughout the application lifetime. It then calls the Client.SetValue method, passing both the key string and the Value struct. The Client.SetValue method handles all the implementation details including context creation with timeout, MessagePack serialization of the Value struct into bytes, gRPC request construction with the robot ID, key, and serialized value, RPC invocation, and error logging.

This function is implemented as a simple pass-through that returns whatever error the Client.SetValue method returns. Unlike GetValue, there is no special error handling here because all errors from a set operation are meaningful and should be considered by the caller. A nil return indicates successful storage, while any error indicates that the value may not have been persisted and the caller should take appropriate action such as retrying the operation or logging the failure.

The function connects to the broader project by providing trading robot strategies with a simple way to persist their state. Strategies can call SetValue to save information like current position sizes, accumulated metrics, trade identifiers, or any other data that must survive restarts. The Value type defined in value.go supports multiple data types including booleans, strings, integers of various sizes, and floating-point numbers, giving strategies flexibility in what they store.

Together with GetValue, this function forms the complete persistence API that enables stateful trading strategy implementations. The key-value design allows strategies to store multiple independent pieces of state under different keys, and the MessagePack serialization ensures efficient binary encoding for network transmission.

File: value_test.go

package goalgo

Package goalgo contains this test file which provides unit tests for the Value type defined in value.go. The Value type is a central data structure in the goalgo trading robot framework, used to store and transmit typed data between trading robots and the broker server. These tests verify that the Value type correctly handles type assignment, type conversion, and serialization round-trips through MessagePack encoding.

The test file contains two test functions that together exercise the core functionality of the Value type. TestValueBase verifies the basic operations of setting values of different types and retrieving them through the type conversion methods. TestValue verifies that Value instances can be serialized to MessagePack format and deserialized back without data loss, which is essential for the persistence and RPC transmission functionality that the Value type supports throughout the framework.

These tests are designed to be run using the standard Go testing framework via the go test command. They validate that the Value type works correctly before it is used in the higher-level APIs like value_api.go and the Client methods in grpc_client.go. Failures in these tests would indicate fundamental problems with how the framework stores and transmits typed data.

import (
	“bytes”
	“testing”
	“github.com/vmihailenco/msgpack”
)

The import block brings in the dependencies required for testing the Value type. The bytes package provides the Buffer type which is used as an in-memory writer and reader for MessagePack encoding and decoding operations. The testing package is the standard Go testing framework that provides the T type for test assertions and error reporting.

The msgpack package from github.com/vmihailenco/msgpack provides the MessagePack binary serialization implementation. This is the same package used in the production code in grpc_client.go for serializing Value structs before transmission over gRPC, so testing with this exact package ensures compatibility between the test and production code paths.

func TestValueBase(t *testing.T) {
	v := Value{}
	v.SetValue(”a”)
	if v.ToString() != “a” {
		t.Errorf(”%v”, v)
		return
	}
	v.SetValue(”b”)
	if v.ToString() != “b” {
		t.Errorf(”%v”, v)
		return
	}
	v.SetValue(int(1))
	if v.ToInt() != 1 {
		t.Errorf(”%v”, v)
		return
	}
	v.SetValue(int32(1))
	if v.ToInt() != 1 {
		t.Errorf(”%v”, v)
		return
	}
	v.SetValue(float32(1.5))
	if v.ToFloat64() != 1.5 {
		t.Errorf(”%v”, v)
		return
	}
	v.SetValue(float64(3.5))
	if v.ToFloat64() != 3.5 {
		t.Errorf(”%v”, v)
		return
	}
}

TestValueBase is a unit test function that verifies the fundamental operations of the Value type, specifically the SetValue method and the type conversion getter methods ToString, ToInt, and ToFloat64. This test ensures that values of different Go types can be stored in a Value struct and accurately retrieved through the appropriate conversion method.

The test works by creating an empty Value struct and then exercising it through a series of set-and-verify cycles. First, it sets a string value “a” and verifies that ToString returns the exact string. It then changes the value to another string “b” to verify that values can be updated and the new value is returned correctly. This tests the string type handling and the mutability of Value structs.

The test continues by setting integer values using both int and int32 types, verifying each time that ToInt returns the correct numeric value. This tests the integer type handling and specifically the conversion from int to int32 that occurs in SetValue for int inputs, as defined in value.go. Both int and int32 inputs should result in the same stored representation and return the same value through ToInt.

Finally, the test sets floating-point values using both float32 and float64 types, verifying that ToFloat64 returns the correct values. The float32 test uses 1.5 and the float64 test uses 3.5, with each being verified for exact equality. This tests the floating-point type handling, though notably using exact equality for floating-point comparisons which works for these simple values but might not for all floating-point operations.

The test uses t.Errorf to report failures with the current value for debugging, and returns early on each failure to prevent cascading errors from confusing the test output. This design makes it clear which specific value assignment or retrieval failed when a test does not pass. This test is foundational because if these basic operations fail, all higher-level functionality that depends on Value storage and retrieval would also be broken.

func TestValue(t *testing.T) {
	v := Float64Value(1.2)
	writer := bytes.NewBuffer(nil)
	enc := msgpack.NewEncoder(writer)
	var err error
	err = enc.Encode(&v)
	if err != nil {
		t.Error(err)
		return
	}
	data := writer.Bytes()
	r := bytes.NewBuffer(data)
	dec := msgpack.NewDecoder(r)
	var v1 Value
	err = dec.Decode(&v1)
	if err != nil {
		t.Error(err)
		return
	}
	s := v1.ToString()
	if s != “1.2” {
		t.Errorf(”%v”, s)
		return
	}
	f1 := v1.ToFloat64()
	if f1 != 1.2 {
		t.Errorf(”%v”, f1)
		return
	}
}

TestValue is a unit test function that verifies the MessagePack serialization and deserialization round-trip for the Value type. This test is critical because the goalgo framework relies on MessagePack encoding to transmit Value structs between trading robots and the broker server over gRPC, and to persist values in the server’s key-value store.

The test works by first creating a Value containing a float64 value of 1.2 using the Float64Value constructor function. It then creates a bytes.Buffer as an in-memory write target and wraps it with a msgpack.Encoder. The Value is encoded into the buffer using the Encode method, and if encoding fails, the test reports the error and returns early.

After successful encoding, the test extracts the raw bytes from the buffer and creates a new bytes.Buffer around them for reading. A msgpack.Decoder is created around this read buffer, and a new empty Value struct is declared to receive the decoded data. The Decode method populates this new Value from the serialized bytes, and if decoding fails, the test reports the error and returns early.

The test then verifies that the decoded Value contains the correct data by calling both ToString and ToFloat64 on the decoded value. The ToString result is compared against the string “1.2” to verify that the Value can be correctly converted to its string representation after a serialization round-trip. The ToFloat64 result is compared against the original 1.2 to verify that the numeric value survives encoding and decoding without precision loss.

This test validates that the EncodeMsgpack and DecodeMsgpack methods defined on the Value type in value.go work correctly with the msgpack package. These methods implement the CustomEncoder and CustomDecoder interfaces respectively, enabling Value structs to control their own serialization format. The test uses float64 because it is one of the more complex numeric types that could potentially have issues with binary serialization, and floating-point round-trip accuracy is important for financial calculations in trading robots.

The success of this test is essential for the correct operation of Client.GetValue and Client.SetValue in grpc_client.go, as well as the convenience functions in value_api.go, all of which depend on MessagePack serialization to store and retrieve Value structs.

File: algo/bitmexrstrategy.go

package algo

Package algo defines trading strategy implementations that work with the goalgo trading framework. This package contains concrete strategy types that interact with various cryptocurrency exchanges, providing the necessary setup and initialization logic to establish connections and manage exchange instances. The strategies in this package serve as building blocks for algorithmic trading systems, encapsulating exchange-specific configuration and connection management while following a common interface pattern established by the goalgo framework.

import (
	stdlog “log”
	“github.com/frankrap/bitmex-api”
	“github.com/frankrap/goalgo”
	“github.com/frankrap/goalgo/log”
)

The import section brings in the necessary dependencies for BitMEX exchange integration. The standard log package is imported with an alias to avoid conflicts and provides basic logging capabilities for debugging and monitoring. The bitmex-api package from frankrap provides the official BitMEX API client implementation, enabling interaction with the BitMEX cryptocurrency derivatives exchange. The goalgo package provides the framework types and interfaces that this strategy must implement, while the log package from goalgo offers more sophisticated logging facilities with error handling and formatting capabilities.

type BitMEXRStrategy struct {
	goalgo.BaseStrategy
	Exchange  *bitmex.BitMEX
	Exchanges []*bitmex.BitMEX
}

BitMEXRStrategy represents a trading strategy specifically designed to work with the BitMEX cryptocurrency derivatives exchange. This type embeds the BaseStrategy from the goalgo framework, inheriting common strategy functionality and establishing the contract that all strategies must fulfill. The Exchange field holds a reference to the primary BitMEX connection that will be used for trading operations, while the Exchanges slice maintains a collection of all configured BitMEX instances, allowing for scenarios where multiple accounts or environments need to be managed simultaneously. This design enables the strategy to support both single and multi-exchange configurations, with the first exchange in the collection designated as the primary trading interface. The structure participates in the broader goalgo architecture by implementing the required strategy interface methods, making it compatible with the framework’s execution engine.

func (s *BitMEXRStrategy) Setup(params []goalgo.ExchangeParams) error {
	stdlog.Printf(”BitMEXRStrategy Setup”)
	s.Exchanges = []*bitmex.BitMEX{}
	for _, p := range params {
		stdlog.Print(p)
		var ex *bitmex.BitMEX
		switch p.Name {
		case “bitmex”:
			ex = bitmex.New(nil, bitmex.HostReal, p.AccessKey, p.SecretKey, false)
		case “bitmex_test”:
			ex = bitmex.New(nil, bitmex.HostTestnet, p.AccessKey, p.SecretKey, false)
		default:
			log.Errorf(”交易所设置错误 %v”, p.Name)
		}
		s.Exchanges = append(s.Exchanges, ex)
		if ex == nil {
			log.Errorf(”创建交易所失败 ex == nil”)
			continue
		}
	}
	if len(s.Exchanges) > 0 {
		s.Exchange = s.Exchanges[0]
	}
	return nil
}

Setup initializes the BitMEX strategy by processing a list of exchange parameters and creating the corresponding BitMEX API client instances. This method is called by the goalgo framework during strategy initialization and is responsible for establishing connections to one or more BitMEX environments. The function accepts a slice of ExchangeParams, each containing the necessary configuration details such as exchange name, API credentials, and environment settings. For each parameter set, the method determines whether to connect to the live BitMEX production environment or the testnet sandbox based on the provided name field. The production environment is accessed when the name is “bitmex”, while “bitmex_test” triggers connection to the testnet, which is valuable for testing strategies without risking real funds. Each successfully created exchange client is appended to the Exchanges slice, building up a collection of available connections. If any exchange creation fails, an error is logged and the setup continues with the remaining parameters, ensuring partial failures do not halt the entire initialization process. After all exchanges are processed, the first exchange in the collection, if present, is assigned to the Exchange field to serve as the default trading interface. This method returns nil to indicate successful completion, as the framework expects this signature and handles errors through logging rather than return values. The Setup method plays a critical role in the strategy lifecycle by transforming configuration data into live, authenticated connections that the strategy will use for market data retrieval and order execution.

File: algo/goexstrategy.go

package algo

Package algo defines trading strategy implementations that work with the goalgo trading framework. This package contains concrete strategy types that interact with various cryptocurrency exchanges, providing the necessary setup and initialization logic to establish connections and manage exchange instances. The strategies in this package serve as building blocks for algorithmic trading systems, encapsulating exchange-specific configuration and connection management while following a common interface pattern established by the goalgo framework.

import (
	“log”
	“github.com/frankrap/goalgo”
	“github.com/nntaoli-project/goex”
	“github.com/nntaoli-project/goex/builder”
)

The import section brings in the dependencies required for multi-exchange support through the GoEx library. The standard log package provides basic logging capabilities for monitoring strategy initialization and debugging connection issues. The goalgo package provides the framework types and interfaces that define the strategy contract. The goex package and its builder subpackage together provide a unified API abstraction layer that supports multiple cryptocurrency exchanges through a single, consistent interface. This builder pattern allows the strategy to instantiate exchange connections dynamically based on configuration parameters, enabling the same strategy code to work seamlessly across different trading platforms without exchange-specific conditional logic scattered throughout the implementation.

var (
	goExExchangeNameMap = map[string]string{
		“okex”:     “okex.com”,
		“huobi”:    “huobi.pro”,
		“bitstamp”: “bitstamp.net”,
		“kraken”:   “kraken.com”,
		“zb”:       “zb.com”,
		“bitfinex”: “bitfinex.com”,
		“binance”:  “binance.com”,
		“poloniex”: “poloniex.com”,
		“coinex”:   “coinex.com”,
		“bithumb”:  “bithumb.com”,
		“gate”:     “gate.io”,
		“bittrex”:  “bittrex.com”,
		“gdax”:     “gdax.com”,
		“wex”:      “wex.nz”,
		“big.one”:  “big.one”,
		“58coin”:   “58coin.com”,
		“fcoin”:    “fcoin.com”,
		“hitbtc”:   “hitbtc.com”,
	}
)

The goExExchangeNameMap variable establishes a mapping between simplified exchange identifiers and their canonical domain names as expected by the GoEx library. This map serves as a translation layer in the strategy initialization process, allowing configuration files and parameters to use short, human-readable exchange names like “okex” or “binance” while internally resolving them to the fully qualified domain names needed by the GoEx API builder. Each entry pairs a convenient alias with the corresponding exchange’s primary domain, supporting a wide range of cryptocurrency trading platforms including major exchanges like Binance, Huobi, Kraken, and Bitfinex, as well as smaller platforms like CoinEx, Gate.io, and Bittrex. This architecture decision centralizes the name resolution logic, making it easy to add support for new exchanges by simply adding entries to this map without modifying the core exchange creation logic. The mapping plays a critical role in the createExchange method, which uses it to validate exchange names and construct the appropriate API clients.

type GoExStrategy struct {
	goalgo.BaseStrategy
	Exchange  goex.API
	Exchanges []goex.API
}

GoExStrategy represents a trading strategy designed to work with multiple cryptocurrency exchanges through the unified GoEx API abstraction. This type embeds the BaseStrategy from the goalgo framework, inheriting common strategy functionality and establishing the contract that all strategies must fulfill. The Exchange field holds a reference to the primary exchange connection that will be used for trading operations, while the Exchanges slice maintains a collection of all configured exchange instances. This design allows the strategy to support both single-exchange and multi-exchange trading scenarios, such as arbitrage strategies that need to monitor and trade across multiple platforms simultaneously. By using the goex.API interface type rather than concrete exchange implementations, the strategy achieves platform independence, allowing the same strategy logic to execute trades on any supported exchange without modification. The structure participates in the broader goalgo architecture by implementing the required strategy interface methods, making it compatible with the framework’s execution engine and lifecycle management.

func (s *GoExStrategy) Setup(params []goalgo.ExchangeParams) error {
	log.Printf(”GoExStrategy Setup”)
	s.Exchanges = []goex.API{}
	for _, p := range params {
		log.Print(p)
		s.Exchanges = append(s.Exchanges, s.createExchange(p))
	}
	if len(s.Exchanges) > 0 {
		s.Exchange = s.Exchanges[0]
	}
	return nil
}

Setup initializes the GoEx strategy by processing a list of exchange parameters and creating the corresponding exchange API client instances through the builder pattern. This method is called by the goalgo framework during strategy initialization and is responsible for establishing connections to one or more cryptocurrency exchanges. The function accepts a slice of ExchangeParams, each containing configuration details such as the exchange name and API credentials. For each parameter set, the method delegates to the createExchange helper function, which handles the actual instantiation of the exchange client using the GoEx builder. Each successfully created exchange client is appended to the Exchanges slice, building up a collection of available trading connections that the strategy can use for market operations. After processing all parameters, if at least one exchange was successfully configured, the first exchange in the collection is assigned to the Exchange field to serve as the default trading interface. This method returns nil to indicate successful completion, following the framework’s expected signature where errors are typically handled through logging rather than return values. The Setup method plays a critical role in the strategy lifecycle by transforming declarative configuration into live, authenticated API connections that enable the strategy to retrieve market data, place orders, and manage positions across multiple exchanges.

func (s *GoExStrategy) createExchange(params goalgo.ExchangeParams) goex.API {
	b := builder.NewAPIBuilder()
	exName, ok := goExExchangeNameMap[params.Name]
	if !ok {
		return nil
	}
	return b.APIKey(params.AccessKey).APISecretkey(params.SecretKey).Build(exName)
}

createExchange constructs and returns a GoEx API client instance configured for a specific exchange using the builder pattern. This private helper method takes an ExchangeParams structure containing the exchange identifier and authentication credentials, then uses the GoEx APIBuilder to construct a properly configured client. The method first creates a new builder instance, then looks up the canonical exchange domain name in the goExExchangeNameMap using the provided exchange name from the parameters. If the exchange name is not found in the mapping, indicating an unsupported or mistyped exchange identifier, the method returns nil rather than attempting to create an invalid client. For valid exchange names, the builder is configured with the API key and secret key from the parameters, establishing the authentication context needed for private API calls such as trading and account management. Finally, the Build method is invoked with the resolved exchange domain name, which internally instantiates the appropriate exchange-specific client implementation and returns it as a goex.API interface. This architecture allows the strategy to work with any exchange supported by the GoEx library without needing to know the specific implementation details, relying instead on the builder to handle the complexity of exchange-specific client construction and configuration.

File: log/entry.go

package log
import (
	“time”
)

Package log provides a structured logging system designed to capture, format, and persist application events. It defines the core data structures and interfaces necessary for creating log entries with metadata such as timestamps, unique identifiers, and severity levels. This package aims to offer a flexible and extensible logging foundation that can be integrated with various logging backends and formatters, facilitating consistency and observability across the entire application.

type Entry struct {
	ID      uint64    `json:”id”`
	Time    time.Time `json:”time”`
	Level   Level     `json:”level”`
	Message string    `json:”message”`
}

The Entry struct encapsulates all the essential information for a single log record. It serves as the fundamental unit of data within the logging system, carrying not only the log message itself but also metadata such as a unique identifier, the creation timestamp, and the severity level. This structure allows the logging backend to process and format logs in a structured manner, suitable for both text-based output and structured storage systems like JSON-based log aggregators.

func NewEntry(id uint64, level Level, message string) *Entry {
	return &Entry{
		ID:      id,
		Time:    time.Now(),
		Level:   level,
		Message: message,
	}
}

NewEntry creates and initializes a new Entry instance with a unique ID, a specific logging level, and a descriptive message. It captures the current system time at the moment of creation, ensuring that the log entry has an accurate timestamp. This function is typically called by the internal logging logic when a log function like Info or Error is invoked, serving as the factory method for converting raw log data into a structured Entry object ready for processing by the configured Logger.

File: log/id.go

package log
import “github.com/sony/sonyflake”

Package log’s ID generation component. This file is responsible for setting up the unique ID generation mechanism for log entries. It utilizes the Sonyflake algorithm to produce 64-bit unique identifiers that are k-ordered, meaning they are roughly sorted by creation time. This capability is crucial for systems that need to merge logs from multiple sources while maintaining a relative temporal order without relying solely on timestamps, which can be imprecise or duplicate.

var (
	sf *sonyflake.Sonyflake
)

This block declares the global sonyflake instance used for ID generation. The sonyflake variable, sf, is a pointer to the Sonyflake structure which encapsulates the logic and state required to generate unique, distributed IDs. It is initialized during the package initialization phase and is subsequently used by the logging functions to assign a unique identifier to every log entry, ensuring that logs can be historically ordered and distinct even across distributed systems.

func init() {
	var st sonyflake.Settings
	st.MachineID = machineID
	sf = sonyflake.NewSonyflake(st)
	if sf == nil {
		panic(”sonyflake not created”)
	}
}

init is the package initialization function for the log package’s ID generation subsystem. It is automatically executed when the package is loaded. Its primary responsibility is to configure and instantiate the Sonyflake ID generator. It sets up the machine ID configuration, attempts to create a new Sonyflake instance, and performs a critical check to ensure the generator was successfully created. If ID generation cannot be established, the function will panic, as the logging system relies on unique IDs for correct operation.

func machineID() (uint16, error) {
	return 0, nil
}

machineID is a helper function designed to return a unique identifier for the machine or node running the application. This ID is used by the Sonyflake algorithm to allow multiple instances of the application to generate non-colliding IDs concurrently. In this implementation, it currently returns a fixed value (0) and no error. In a distributed environment, this should be updated to return a real unique identifier derived from the environment (like an IP address or configuration) to ensure global uniqueness.

File: log/levels.go

package log

Package log’s level definitions. This file defines the hierarchy of logging levels used to categorize log entries by their severity. These levels control which logs are processed and output based on the configured logging threshold. This separation allows developers to filter noise during normal operation while retaining the ability to enable detailed diagnostics when needed.

type Level int

Level is a custom integer type representing the severity of a log entry. It allows for type-safe handling of log levels throughout the package. By defining Level as a distinct type, the code ensures that only valid predefined constants are used where a log severity is expected, preventing accidental misuse of raw integer values.

const (
	InvalidLevel Level = iota - 1
	DebugLevel
	InfoLevel
	WarnLevel
	ErrorLevel
	FatalLevel
)

This block defines the standard log levels available in the system. The levels are ordered by severity, starting from InvalidLevel.

  • InvalidLevel is used as a sentinel value for uninitialized or incorrect levels.

  • DebugLevel is for verbose information useful for development and debugging.

  • InfoLevel is for general operational events that show the system is working.

  • WarnLevel is for non-critical issues that might need attention.

  • ErrorLevel is for runtime errors that should be investigated.

  • FatalLevel is for severe errors that cause the program to crash or exit.

File: log/log.go

package log

Package log acts as the central entry point for the logging system. It exposes a simple, static API for logging messages at various severity levels. Under the hood, it delegates the actual log processing to a configured Logger instance. This file manages the global logging state, including the current log level and the active logger instance, providing a convenient and thread-safe way for other parts of the application to emit logs without needing to manage logger instances directly.

import (
	 
	“fmt”
	slog “github.com/sirupsen/logrus”
)

This import block brings in the necessary dependencies for the logging package.

  • fmt: Used for string formatting when creating log messages with dynamic data.

  • logrus: An external structured logger (aliased here as slog) which is used as the underlying backend for writing logs to the standard output or files.

var (
	logger   Logger
	logLevel = InfoLevel
)

These global variables maintain the internal state of the logging package.

  • logger: The active Logger instance that handles the dispatching of log entries. It is exposed via GetLogger for configuration or inspection.

  • logLevel: The global logging threshold. Only logs with a severity equal to or higher than this level will be processed. It defaults to InfoLevel.

func init() {
	slog.SetFormatter(&slog.TextFormatter{})
}

init initializes the underlying logging backend. It configures the logrus logger to use a plain text formatter by default. This ensures that even if no further configuration is done, the application will produce readable text logs suitable for console output or simple log files.

func GetLogger() *Logger {
	return &logger
}

GetLogger returns a pointer to the global Logger instance. This allows external components or configuration routines to access and potentially modify the logger’s behavior, such as setting up a new log handler or inspecting its current state.

func GetLevel() Level {
	return logLevel
}

GetLevel returns the current global logging threshold. This can be used to check if a certain level of logging is enabled before performing expensive log message construction, although the log functions themselves perform this check as well.

func SetLevel(level Level) {
	logLevel = level
}

SetLevel updates the global logging threshold to the specified level. Changing the level allows the application to control the verbosity of its output dynamically at runtime, for example, to enable debug logging during troubleshooting sessions without restarting the application.

func Debug(msg string) {
	log(DebugLevel, msg)
}

Debug logs a message at the DebugLevel. These logs are typically verbose and contain detailed information helpful for developers during the debugging process. They are usually disabled in production environments to save resources and reduce noise.

func Info(msg string) {
	log(InfoLevel, msg)
}

Info logs a message at the InfoLevel. These logs describe general operational events, such as startup, shutdown, or successful completion of major tasks. They confirm that the application is working as expected.

func Warn(msg string) {
	log(WarnLevel, msg)
}

Warn logs a message at the WarnLevel. These logs indicate potential issues or non-critical errors that do not prevent the application from running but might require attention to prevent future problems.

func Error(msg string) {
	log(ErrorLevel, msg)
}

Error logs a message at the ErrorLevel. These logs report runtime errors or failure conditions that affect the proper functioning of a specific operation or request. They signal that something went wrong and should be investigated.

func Fatal(msg string) {
	log(FatalLevel, msg)
}

Fatal logs a message at the FatalLevel. These logs indicate severe errors that define a critical system failure. While the implementation here just logs the message, intrinsically Fatal logs often imply that the application cannot continue safely.

func Debugf(msg string, v ...interface{}) {
	log(DebugLevel, fmt.Sprintf(msg, v...))
}

Debugf logs a formatted message at the DebugLevel. It accepts a format string and arguments, similar to fmt.Printf, allowing for dynamic construction of detailed debug messages.

func Infof(msg string, v ...interface{}) {
	log(InfoLevel, fmt.Sprintf(msg, v...))
}

Infof logs a formatted message at the InfoLevel. It facilitates the creation of informational logs with dynamic content, such as status updates including IDs or counts.

func Warnf(msg string, v ...interface{}) {
	log(WarnLevel, fmt.Sprintf(msg, v...))
}

Warnf logs a formatted message at the WarnLevel. It enables the reporting of warning conditions with specific context details embedded in the message.

func Errorf(msg string, v ...interface{}) {
	log(ErrorLevel, fmt.Sprintf(msg, v...))
}

Errorf logs a formatted message at the ErrorLevel. It allows for error reporting that includes dynamic error details or contextual data to help in diagnosis.

func Fatalf(msg string, v ...interface{}) {
	log(FatalLevel, fmt.Sprintf(msg, v...))
}

Fatalf logs a formatted message at the FatalLevel. It is used for critical error messages that require dynamic formatting to assume full context before a likely system exit.

func log(level Level, message string) {
	if level < logLevel {
		return
	}
	id, _ := sf.NextID()
	e := NewEntry(id, level, message)
	if logger.Log == nil {
		switch level {
		case DebugLevel:
			slog.Debug(message)
		case InfoLevel:
			slog.Info(message)
		case WarnLevel:
			slog.Warn(message)
		case ErrorLevel:
			slog.Error(message)
		case FatalLevel:
			slog.Fatal(message)
		}
		return
	}
	logger.Log.Log(e)
}

log is the internal core function that orchestrates the logging process. It serves as the funnel for all log requests. Its responsibilities include:

  1. Checking the message severity against the global logLevel threshold.

  2. Generating a new unique log Entry with a timestamp and ID.

  3. Dispatching the entry to the configured Logger instance if one exists.

  4. If no custom Logger is configured, it falls back to using the default logrus implementation to output the message, ensuring logs are always handled.

File: log/logger.go

package log

Package log’s core abstraction for logging backends. This file defines the Logger struct and the ILog interface, which together provide the mechanism for injecting different logging implementations. By coding against these abstractions, the main logging logic remains decoupled from any specific output format or storage system, allowing for easy swapping of loggers (e.g., file logger, console logger, network logger) without changing the core logging calls.

type Logger struct {
	Log ILog `inject:”“`
}

Logger acts as a wrapper for the actual logging implementation. It holds a reference to an object satisfying the ILog interface. This struct is intended to be used as a singleton or a shared instance where the actual log handling logic is injected at runtime. The inject:"" tag suggests potential integration with a dependency injection framework to automatically populate the Log field.

type ILog interface {
	Log(e *Entry) error
}

ILog defines the contract that any logging backend must fulfill. Implementers of this interface are responsible for taking a fully formed Entry (containing ID, timestamp, level, and message) and persisting it or displaying it. This abstraction allows the log package to support multiple logging destinations or formats purely by providing different implementations of this interface.

File: math2/math.go

package math2

Package math2 provides a collection of extended mathematical utility functions that are not available in the standard Go math package. It focuses primarily on specialized rounding operations, type conversions, and precision handling useful for financial calculations and data processing where specific rounding behaviors (like rounding to even, or rounding to specific tick sizes) are required.

import (
	“math”
	“strconv”
)

This import block includes standard libraries needed for the package’s operations.

  • math: Provides basic mathematical primitives like Abs, Trunc, Pow, and Copysign.

  • strconv: Used for converting string representations of numbers into floating-point values.

func ToFloat64(x string) float64 {
	v, _ := strconv.ParseFloat(x, 64)
	return v
}

ToFloat64 converts a string representation of a number into a float64. It wraps the standard strconv.ParseFloat function but suppresses the error return, returning the parsed value directly. If parsing fails, it typically returns 0 (or whatever ParseFloat returns on error, though the error is ignored here). This helper is useful in contexts where the input is guaranteed to be valid or where defaulting to zero on failure is acceptable behavior.

func Round(x float64) float64 {
	t := math.Trunc(x)
	if math.Abs(x-t) >= 0.5 {
		return t + math.Copysign(1, x)
	}
	return t
}

Round returns the nearest integer value to x, strictly rounding half-integers away from zero. For example, 3.5 rounds to 4, and -3.5 rounds to -4. It calculates the integer part (truncation) and checks if the fractional part is greater than or equal to 0.5. If so, it adjusts the result by adding 1 (or -1) to the truncated value; otherwise, it returns the truncated value as is.

func RoundToEven(x float64) float64 {
	t := math.Trunc(x)
	odd := math.Remainder(t, 2) != 0
	if d := math.Abs(x - t); d > 0.5 || (d == 0.5 && odd) {
		return t + math.Copysign(1, x)
	}
	return t
}

RoundToEven calculates the nearest integer value to x, implementing the “round half to even” rule (also known as Banker’s rounding). This method minimizes cumulative error in summation by rounding x.5 to the nearest even integer.

  1. It extracts the integer part and determines if it is odd.

  2. It calculates the fractional difference.

  3. If the difference is greater than 0.5, or exactly 0.5 and the integer part is odd, it rounds away from zero.

  4. Otherwise, it keeps the truncated (even) integer.

func Round4BitMEX(x float64, precision int) float64 {
	if precision == 0 {
		return round4BitMEX(x)
	}
	p := math.Pow(10, float64(precision))
	y := float64(round4BitMEX(x*p)) / p
	return y
}

Round4BitMEX rounds a floating-point number to a specified precision, effectively quantizing it. It supports a special mode for BitMEX-style rounding where specific tick sizes (like 0.5) might be assumed if precision is 0.

  • If precision is 0, it delegates to round4BitMEX (which rounds to the nearest 0.5).

  • If precision is non-zero, it scales the number by 10^precision, applies the 0.5-rounding logic to the scaled value, and then scales it back. This allows for rounding to decimal places while using the underlying half-step logic.

func round4BitMEX(x float64) float64 {
	t := math.Trunc(x)
	if x > t+0.5 {
		t += 0.5
	}
	if d := math.Abs(x - t); d > 0.25 {
		return t + math.Copysign(0.5, x)
	}
	return t
}

round4BitMEX is the internal implementation that rounds a value to the nearest 0.5 step. It works by:

  1. Truncating the value to its integer part.

  2. If the value exceeds the integer + 0.5, it shifts the base for comparison up by 0.5.

  3. It keeps the base if the difference is small, or rounds up to the next 0.5 step if the remainder is significant (greater than 0.25). This effectively snaps values to a grid of 0.0, 0.5, 1.0, etc.

func RoundToEven5(x float64) float64 {
	t := math.Trunc(x)
	if x > t+0.5 {
		t += 0.5
	}
	if d := math.Abs(x - t); d > 0.25 {
		return t + math.Copysign(0.5, x)
	}
	return t
}

RoundToEven5 appears to be a duplicate or alias of the logic found in round4BitMEX, designed to round a number to the nearest 0.5 increment. It repeats the same logic:

  1. Use truncation and a 0.5 threshold to determine the base.

  2. Check the deviation from this base; if the deviation exceeds 0.25, round to the next step. This function ensures that prices or values adhere to interactions requiring a 0.5 tick size.

func round(num float64) int {
	return int(num + math.Copysign(0.5, num))
}

round is a helper function that rounds a single float64 to the nearest integer using “round half away from zero” logic. It adds 0.5 (with the same sign as num) to the input and then casts to int, effectively truncating the result. This converts, for instance, 1.5 to 2 and 1.4 to 1.

func ToFixed(num float64, precision int) float64 {
	output := math.Pow(10, float64(precision))
	return float64(round(num*output)) / output
}

ToFixed rounds a number to a specific decimal precision. It multiplies the number by 10 raised to the power of the desired precision to shift the significant digits into the integer range, rounds it to the nearest integer using the helper round function, and then divides it back by the same factor. This produces a floating-point result with the specified number of decimal places (within floating-point accuracy limits).

File: proxy/socks5.go

package proxy

Package proxy provides network proxying utilities. It currently supports creating HTTP clients that route traffic through a SOCKS5 proxy, enabling applications to bypass network restrictions or anonymize their traffic source by tunneling requests through an intermediate server.

import (
	“context”
	“golang.org/x/net/proxy”
	“log”
	“net”
	“net/http”
)

This import block brings in the necessary networking and context management packages.

  • context: Used for managing request lifecycles and timeouts during connection dialing.

  • golang.org/x/net/proxy: The generic network proxy package used to create the SOCKS5 dialer.

  • log: Used for fatal logging in case of proxy initialization failure.

  • net: Provides low-level networking primitives like conn and dialers.

  • net/http: used to construct the custom HTTP client and transport.

func SOCKS5Client(socks5Proxy string) *http.Client {
	 
	dialer, err := proxy.SOCKS5(”tcp”, socks5Proxy, nil, proxy.Direct)
	if err != nil {
		log.Fatal(”Error creating dialer, aborting.”)
	}
	dialFunc := func(ctx context.Context, network, addr string) (net.Conn, error) {
		return dialer.Dial(network, addr)
	}
	tr := &http.Transport{DialContext: dialFunc}  
	httpClient := &http.Client{Transport: tr}
	return httpClient
}

SOCKS5Client creates and configures a new HTTP client that routes all requests through a specified SOCKS5 proxy server. It sets up a custom dialer using the standard go proxy library, which handles the SOCKS5 handshake and connection establishment. The function then overrides the DialContext of the HTTP transport to use this custom dialer. If the proxy address is invalid or the dialer cannot be created, the function will log a fatal error and exit the application, as it assumes connectivity is mandatory for the application’s operation.

File: strutil/string.go

package strutil

Package strutil provides a suite of string manipulation utility functions. These utilities extend the capabilities of the standard strings package by offering common formatting and inspection operations, such as padding strings to a fixed length or checking for the existence of a string within a slice. This package is designed to simplify repetitive string operations throughout the application.

import “strings”

This import block includes the standard “strings” package. It provides the core string manipulation functions, such as Repeat, which are utilized by this package to efficiently generate padding strings.

func PadRight(s string, padStr string, overallLen int) string {
	var padCountInt = 1 + ((overallLen - len(padStr)) / len(padStr))
	var retStr = s + strings.Repeat(padStr, padCountInt)
	return retStr[:overallLen]
}

PadRight formats a string by appending specific padding characters to its right side until the string reaches the specified overall length. It calculates the necessary number of padding repetitions required to fill the remaining space and appends them to the original string. Use this function to ensure text alignment or to meet fixed-width format requirements when the content should be left-aligned.

func PadLeft(s string, padStr string, overallLen int) string {
	var padCountInt = 1 + ((overallLen - len(padStr)) / len(padStr))
	var retStr = strings.Repeat(padStr, padCountInt) + s
	return retStr[(len(retStr) - overallLen):]
}

PadLeft formats a string by pre-pending specific padding characters to its left side until the string reaches the specified overall length. It determines the required padding length, generates the padding string, and concatenates it before the original string. This is commonly used for right-aligning text or numbers in a fixed-width display.

func StringInSlice(list []string, a string) bool {
	for _, b := range list {
		if b == a {
			return true
		}
	}
	return false
}

StringInSlice checks if a specific target string exists within a given slice of strings. It iterates through the provided slice and compares each element with the target string. If a match is found, it returns true immediately; otherwise, it returns false after checking all elements. This helper replaces the need for writing repetitive loops for simple existence checks.

Use the button below to download the source code:

User's avatar

Continue reading this post for free, courtesy of Onepagecode.

Or purchase a paid subscription.
© 2026 Onepagecode · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture