This document describes the order confirmation feature for the Live Trading Engine. Order confirmation allows you to review and approve or reject orders before they are sent to the trading provider, providing an additional safety layer for live trading.
When a strategy generates an order via PlaceOrder(), the engine can be configured to:
OnConfirmOrder callback is invoked. The order is only placed if the callback returns true. If it returns false, the order is rejected.This is particularly useful for:
Order confirmation is configured via the LiveTradingEngineConfig struct. The OrderConfirmation field controls the confirmation behavior.
| Field | Type | Default | Description |
|---|---|---|---|
order_confirmation |
OrderConfirmationMode |
"auto" |
"auto" to auto-confirm, "manual" to require callback confirmation |
confirmation_timeout |
duration |
30s |
Maximum time to wait for a confirmation response. If the timeout expires, the order is rejected. Only used when order_confirmation is "manual". |
config := engine.LiveTradingEngineConfig{
MarketDataCacheSize: 1000,
EnableLogging: true,
OrderConfirmation: engine.OrderConfirmationManual, // require manual confirmation
ConfirmationTimeout: 60 * time.Second, // reject if no response within 60s
Prefetch: engine.PrefetchConfig{
Enabled: true,
StartTimeType: "days",
Days: 30,
},
}
engine:
market_data_cache_size: 1000
enable_logging: true
order_confirmation: manual # "auto" (default) or "manual"
confirmation_timeout: 30s # timeout for manual confirmation (default: 30s)
prefetch:
enabled: true
start_time_type: days
days: 30
const (
// OrderConfirmationAuto sends orders to the trading provider immediately.
OrderConfirmationAuto OrderConfirmationMode = "auto"
// OrderConfirmationManual holds orders and invokes the OnConfirmOrder callback.
OrderConfirmationManual OrderConfirmationMode = "manual"
)
When order_confirmation is "auto" (or unset), the order flow is unchanged from the default behavior:
Strategy.ProcessData()
│
▼
PlaceOrder(order)
│
▼
Trading Provider
(order executed)
│
▼
OnOrderPlaced callback
When order_confirmation is "manual", the engine intercepts the order and invokes the OnConfirmOrder callback. If the callback does not respond within confirmation_timeout, the order is automatically rejected:
Strategy.ProcessData()
│
▼
PlaceOrder(order)
│
▼
OnConfirmOrder(order)
│
┌───┼───────┐
│ │ │
true false timeout
│ │ │
▼ ▼ ▼
Trading Order
Provider rejected
(order (not sent to
executed) provider)
│
▼
OnOrderPlaced callback
The OnConfirmOrder callback is part of the LiveTradingCallbacks struct:
// OnConfirmOrderCallback is called when an order requires confirmation.
// Return true to confirm (place the order), or false to reject it.
type OnConfirmOrderCallback func(order types.ExecuteOrder) bool
type LiveTradingCallbacks struct {
// ... existing callbacks ...
// OnConfirmOrder is called when the engine is in manual confirmation mode
// and a strategy wants to place an order.
// Return true to confirm the order, false to reject it.
// If nil and mode is "manual", the order is auto-confirmed.
OnConfirmOrder *OnConfirmOrderCallback
}
Example (Go):
onConfirm := engine.OnConfirmOrderCallback(func(order types.ExecuteOrder) bool {
fmt.Printf("Order pending: %s %s %.4f %s @ %.2f\n",
order.Side, order.Quantity, order.Symbol, order.OrderType, order.Price)
// Custom risk check
if order.Price * order.Quantity > 10000 {
fmt.Println("Order exceeds $10,000 limit — rejected")
return false
}
return true
})
callbacks := engine.LiveTradingCallbacks{
OnConfirmOrder: &onConfirm,
// ... other callbacks ...
}
eng.Run(ctx, callbacks)
In the Swift API, the TradingEngineHelper interface includes an OnConfirmOrder method:
// TradingEngineHelper is the callback interface for live trading lifecycle events.
type TradingEngineHelper interface {
// ... existing methods ...
// OnConfirmOrder is called when manual confirmation is enabled and a strategy
// wants to place an order. Return true to confirm, false to reject.
// orderJSON is the JSON representation of the ExecuteOrder.
OnConfirmOrder(orderJSON string) bool
}
Example (Swift):
class MyTradingHelper: TradingEngineHelper {
// Called when the engine requests order confirmation
func onConfirmOrder(_ orderJSON: String) -> Bool {
// Parse the order JSON
guard let data = orderJSON.data(using: .utf8),
let order = try? JSONDecoder().decode(ExecuteOrder.self, from: data) else {
return false
}
// Example: reject orders above a certain size
if order.price * order.quantity > 10000 {
print("Order too large, rejecting: \(order.symbol)")
return false
}
print("Confirming order: \(order.side) \(order.quantity) \(order.symbol)")
return true
}
// ... other TradingEngineHelper methods ...
}
Using with a UI confirmation dialog (Swift):
class MyTradingHelper: TradingEngineHelper {
func onConfirmOrder(_ orderJSON: String) -> Bool {
// Use a semaphore to wait for user input on the main thread
let semaphore = DispatchSemaphore(value: 0)
var confirmed = false
DispatchQueue.main.async {
// Show confirmation dialog to user
let alert = UIAlertController(
title: "Confirm Order",
message: "Place order: \(orderJSON)?",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "Confirm", style: .default) { _ in
confirmed = true
semaphore.signal()
})
alert.addAction(UIAlertAction(title: "Reject", style: .cancel) { _ in
confirmed = false
semaphore.signal()
})
// Present the alert...
}
semaphore.wait()
return confirmed
}
}
Note: The
OnConfirmOrdercallback is invoked on the engine’s processing thread. For UI-based confirmations, you must dispatch to the main thread and block the engine thread until the user responds, as shown above.
If order_confirmation is not set or is set to "auto", the engine behaves exactly as before — orders are placed immediately with no confirmation step. The OnConfirmOrder callback is never invoked.
If order_confirmation is "manual" but the OnConfirmOrder callback is nil (not provided), the engine falls back to auto-confirm behavior. This ensures backward compatibility.
| Return Value | Behavior |
|---|---|
true |
Order is confirmed and sent to the trading provider |
false |
Order is rejected and not sent to the trading provider |
When order_confirmation is "manual", the engine enforces a timeout on the OnConfirmOrder callback via the confirmation_timeout setting (default: 30s). If the callback does not return within the timeout period, the order is automatically rejected — it will not be sent to the trading provider.
This prevents the engine from stalling indefinitely if the user does not respond (e.g., app in background, UI not visible).
Go configuration example:
config := engine.LiveTradingEngineConfig{
OrderConfirmation: engine.OrderConfirmationManual,
ConfirmationTimeout: 60 * time.Second, // 60-second timeout
}
YAML configuration example:
engine:
order_confirmation: manual
confirmation_timeout: 60s
Note: The timeout is only enforced when
order_confirmationis"manual". In"auto"mode, the timeout setting is ignored.
OnOrderPlaced is only called if the order is confirmed (callback returns true or mode is "auto").OnOrderFilled is only called after a confirmed order is filled by the trading provider.OnOrderPlaced or OnOrderFilled.package main
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/rxtech-lab/argo-trading/internal/trading/engine"
"github.com/rxtech-lab/argo-trading/internal/types"
)
func main() {
eng, err := engine.NewLiveTradingEngineV1()
if err != nil {
log.Fatal(err)
}
config := engine.LiveTradingEngineConfig{
MarketDataCacheSize: 1000,
EnableLogging: true,
OrderConfirmation: engine.OrderConfirmationManual,
ConfirmationTimeout: 30 * time.Second,
Prefetch: engine.PrefetchConfig{
Enabled: true,
StartTimeType: "days",
Days: 30,
},
}
if err := eng.Initialize(config); err != nil {
log.Fatal(err)
}
// ... set providers, load strategy ...
// Define confirmation callback
onConfirm := engine.OnConfirmOrderCallback(func(order types.ExecuteOrder) bool {
fmt.Printf("Confirm? %s %s %.4f @ %.2f\n",
order.Side, order.Symbol, order.Quantity, order.Price)
return true // confirm all orders in this example
})
onPlaced := engine.OnOrderPlacedCallback(func(order types.ExecuteOrder) error {
fmt.Printf("Order placed: %s\n", order.ID)
return nil
})
callbacks := engine.LiveTradingCallbacks{
OnConfirmOrder: &onConfirm,
OnOrderPlaced: &onPlaced,
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigChan
cancel()
}()
if err := eng.Run(ctx, callbacks); err != nil {
log.Fatal(err)
}
}
import Argo
class TradingApp: TradingEngineHelper {
var engine: TradingEngine?
func start() {
engine = try? NewTradingEngine(self)
// Enable manual order confirmation
let config = """
{
"market_data_cache_size": 1000,
"enable_logging": true,
"order_confirmation": "manual",
"confirmation_timeout": "30s",
"prefetch": {
"enabled": true,
"start_time_type": "days",
"days": 30
}
}
"""
try? engine?.initialize(config)
// ... set providers, load strategy ...
try? engine?.run()
}
// MARK: - TradingEngineHelper
func onConfirmOrder(_ orderJSON: String) -> Bool {
// Implement your confirmation logic here
print("Order confirmation requested: \(orderJSON)")
return true
}
func onEngineStart(_ symbols: StringCollection, interval: String, previousDataPath: String) throws {
print("Engine started")
}
func onEngineStop(_ err: Error?) {
print("Engine stopped")
}
func onMarketData(_ runId: String, symbol: String, timestamp: Int64,
open: Double, high: Double, low: Double,
close: Double, volume: Double) throws {
// Handle market data
}
func onOrderPlaced(_ orderJSON: String) throws {
print("Order placed: \(orderJSON)")
}
func onOrderFilled(_ orderJSON: String) throws {
print("Order filled: \(orderJSON)")
}
func onError(_ err: Error) {
print("Error: \(err)")
}
func onStrategyError(_ symbol: String, timestamp: Int64, err: Error) {
print("Strategy error: \(err)")
}
}