跳至主要内容

控制台视图

控制台视图提供了一个实时窗口,用于观察 StableFX 交易引擎的运行。它流式传输日志条目,显示交易执行、池重新平衡、代理活动和系统事件。本页介绍 useForexSimulation hook、forexSimulationEngine 服务以及用于日志流的类型。

核心类型

ForexLogType

import type { ForexLogType } from '@one_deploy/sdk';

type ForexLogType =
| 'TRADE_SUBMITTED'
| 'TRADE_QUOTED'
| 'TRADE_MATCHED'
| 'TRADE_SETTLED'
| 'TRADE_FAILED'
| 'POOL_REBALANCE'
| 'POOL_DEPOSIT'
| 'POOL_WITHDRAWAL'
| 'AGENT_ACTION'
| 'SYSTEM_INFO'
| 'SYSTEM_WARNING'
| 'SYSTEM_ERROR';

ForexLogEntry

import type { ForexLogEntry } from '@one_deploy/sdk';

interface ForexLogEntry {
/** Unique log entry identifier. */
id: string;

/** Log category. */
type: ForexLogType;

/** Human-readable log message. */
message: string;

/** ISO-8601 timestamp. */
timestamp: string;

/** Structured data associated with this log entry. */
metadata: Record<string, unknown>;

/** The agent that produced this log, if applicable. */
agentId?: string;

/** Related trade ID, if this log is trade-related. */
tradeId?: string;

/** Related pool ID, if this log is pool-related. */
poolId?: string;

/** Severity level for display. */
severity: 'info' | 'warning' | 'error' | 'success';
}

ForexAgent

import type { ForexAgent } from '@one_deploy/sdk';

interface ForexAgent {
/** Unique agent identifier. */
id: string;

/** Human-readable agent name. */
name: string;

/** Agent role in the trading system. */
role: 'market_maker' | 'hedger' | 'arbitrageur' | 'risk_manager';

/** Current agent status. */
status: 'active' | 'idle' | 'error';

/** Currency pairs this agent is assigned to. */
assignedPairs: string[];

/** Number of trades executed by this agent. */
tradeCount: number;

/** ISO-8601 timestamp of last activity. */
lastActiveAt: string;
}

useForexSimulation Hook

useForexSimulation hook 连接到 forexSimulationEngine,并提供日志条目和代理状态的响应式流。

Hook 签名

import { useForexSimulation } from '@one_deploy/sdk';

function useForexSimulation(): UseForexSimulationResult;

UseForexSimulationResult

interface UseForexSimulationResult {
/** Live stream of log entries, newest first. */
logs: ForexLogEntry[];

/** All active agents in the simulation. */
agents: ForexAgent[];

/** Whether the simulation is currently running. */
isRunning: boolean;

/** Whether the initial connection is being established. */
isConnecting: boolean;

/** Error object if the connection or simulation failed. */
error: Error | null;

/** Start the simulation log stream. */
start: () => void;

/** Stop the simulation log stream. */
stop: () => void;

/** Clear all accumulated log entries. */
clearLogs: () => void;

/** Filter logs by type. */
filterByType: (types: ForexLogType[]) => ForexLogEntry[];

/** Filter logs by severity. */
filterBySeverity: (severity: ForexLogEntry['severity']) => ForexLogEntry[];
}

forexSimulationEngine 服务

对于非 React 上下文或需要更低级别控制时,可以直接使用 forexSimulationEngine 服务。

import { forexSimulationEngine } from '@one_deploy/sdk';

// Subscribe to log entries
const unsubscribe = forexSimulationEngine.onLog((entry: ForexLogEntry) => {
console.log(`[${entry.type}] ${entry.message}`);
});

// Start the simulation
forexSimulationEngine.start();

// Get current agents
const agents = forexSimulationEngine.getAgents();

// Stop and clean up
forexSimulationEngine.stop();
unsubscribe();

服务 API

interface ForexSimulationEngine {
/** Start the simulation log stream. */
start(): void;

/** Stop the simulation log stream. */
stop(): void;

/** Whether the simulation is currently running. */
isRunning(): boolean;

/** Subscribe to new log entries. Returns an unsubscribe function. */
onLog(callback: (entry: ForexLogEntry) => void): () => void;

/** Get all currently active agents. */
getAgents(): ForexAgent[];

/** Subscribe to agent state changes. Returns an unsubscribe function. */
onAgentUpdate(callback: (agent: ForexAgent) => void): () => void;

/** Get all accumulated log entries. */
getLogs(): ForexLogEntry[];

/** Clear all accumulated log entries. */
clearLogs(): void;
}

构建控制台视图

基础日志流

import React from 'react';
import { View, Text, FlatList, StyleSheet, TouchableOpacity } from 'react-native';
import { useForexSimulation } from '@one_deploy/sdk';
import type { ForexLogEntry } from '@one_deploy/sdk';

function ForexConsole() {
const { logs, isRunning, isConnecting, start, stop, clearLogs } =
useForexSimulation();

return (
<View style={styles.container}>
<View style={styles.toolbar}>
<Text style={styles.title}>StableFX Console</Text>
<View style={styles.controls}>
<TouchableOpacity
onPress={isRunning ? stop : start}
style={[styles.btn, isRunning ? styles.btnStop : styles.btnStart]}
>
<Text style={styles.btnText}>
{isConnecting ? 'Connecting...' : isRunning ? 'Stop' : 'Start'}
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={clearLogs} style={styles.btn}>
<Text style={styles.btnText}>Clear</Text>
</TouchableOpacity>
</View>
</View>

<FlatList
data={logs}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <LogRow entry={item} />}
inverted
style={styles.logList}
/>
</View>
);
}

function LogRow({ entry }: { entry: ForexLogEntry }) {
const color = getSeverityColor(entry.severity);
const time = new Date(entry.timestamp).toLocaleTimeString();

return (
<View style={styles.logRow}>
<Text style={[styles.logTime, { color }]}>{time}</Text>
<Text style={[styles.logType, { color }]}>[{entry.type}]</Text>
<Text style={styles.logMessage} numberOfLines={2}>
{entry.message}
</Text>
</View>
);
}

function getSeverityColor(severity: ForexLogEntry['severity']): string {
switch (severity) {
case 'info': return '#4488ff';
case 'warning': return '#ffaa44';
case 'error': return '#cc4444';
case 'success': return '#44cc88';
default: return '#888888';
}
}

const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0a0a14' },
toolbar: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 12,
borderBottomWidth: 1,
borderColor: '#1a1a2e',
},
title: { fontSize: 16, fontWeight: '700', color: '#fff' },
controls: { flexDirection: 'row', gap: 8 },
btn: {
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 6,
backgroundColor: '#2a2a4e',
},
btnStart: { backgroundColor: '#1a3a2a' },
btnStop: { backgroundColor: '#3a1a1a' },
btnText: { color: '#fff', fontSize: 13, fontWeight: '600' },
logList: { flex: 1, paddingHorizontal: 12 },
logRow: {
flexDirection: 'row',
alignItems: 'flex-start',
paddingVertical: 4,
gap: 8,
},
logTime: { fontSize: 11, fontFamily: 'monospace', width: 75 },
logType: { fontSize: 11, fontFamily: 'monospace', width: 130 },
logMessage: { fontSize: 12, color: '#ccc', flex: 1 },
});

带筛选和代理面板的控制台

import React, { useState } from 'react';
import { View, Text, FlatList, TouchableOpacity, StyleSheet } from 'react-native';
import { useForexSimulation } from '@one_deploy/sdk';
import type { ForexLogType, ForexLogEntry, ForexAgent } from '@one_deploy/sdk';

const LOG_FILTERS: { label: string; types: ForexLogType[] }[] = [
{ label: 'All', types: [] },
{ label: 'Trades', types: ['TRADE_SUBMITTED', 'TRADE_QUOTED', 'TRADE_MATCHED', 'TRADE_SETTLED', 'TRADE_FAILED'] },
{ label: 'Pools', types: ['POOL_REBALANCE', 'POOL_DEPOSIT', 'POOL_WITHDRAWAL'] },
{ label: 'Agents', types: ['AGENT_ACTION'] },
{ label: 'System', types: ['SYSTEM_INFO', 'SYSTEM_WARNING', 'SYSTEM_ERROR'] },
];

function AdvancedConsole() {
const { logs, agents, isRunning, start, stop, clearLogs, filterByType } =
useForexSimulation();
const [activeFilter, setActiveFilter] = useState(0);

const filteredLogs =
activeFilter === 0
? logs
: filterByType(LOG_FILTERS[activeFilter].types);

return (
<View style={styles.container}>
{/* Agent Status Bar */}
<View style={styles.agentBar}>
{agents.map((agent) => (
<AgentBadge key={agent.id} agent={agent} />
))}
</View>

{/* Filter Tabs */}
<View style={styles.filterRow}>
{LOG_FILTERS.map((filter, idx) => (
<TouchableOpacity
key={filter.label}
onPress={() => setActiveFilter(idx)}
style={[
styles.filterTab,
activeFilter === idx && styles.filterTabActive,
]}
>
<Text
style={[
styles.filterText,
activeFilter === idx && styles.filterTextActive,
]}
>
{filter.label}
</Text>
</TouchableOpacity>
))}
</View>

{/* Log Stream */}
<FlatList
data={filteredLogs}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <LogRow entry={item} />}
inverted
style={{ flex: 1 }}
/>

{/* Controls */}
<View style={styles.footer}>
<Text style={styles.footerText}>
{filteredLogs.length} entries | {isRunning ? 'Live' : 'Paused'}
</Text>
<View style={styles.footerControls}>
<TouchableOpacity onPress={clearLogs} style={styles.footerBtn}>
<Text style={styles.footerBtnText}>Clear</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={isRunning ? stop : start}
style={[styles.footerBtn, isRunning ? styles.stopBtn : styles.startBtn]}
>
<Text style={styles.footerBtnText}>
{isRunning ? 'Pause' : 'Resume'}
</Text>
</TouchableOpacity>
</View>
</View>
</View>
);
}

function AgentBadge({ agent }: { agent: ForexAgent }) {
const statusColor =
agent.status === 'active' ? '#44cc88' :
agent.status === 'idle' ? '#ffaa44' : '#cc4444';

return (
<View style={styles.agentBadge}>
<View style={[styles.agentDot, { backgroundColor: statusColor }]} />
<Text style={styles.agentName}>{agent.name}</Text>
<Text style={styles.agentRole}>{agent.role}</Text>
</View>
);
}

const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0a0a14' },
agentBar: {
flexDirection: 'row',
padding: 10,
gap: 10,
borderBottomWidth: 1,
borderColor: '#1a1a2e',
flexWrap: 'wrap',
},
agentBadge: {
flexDirection: 'row',
alignItems: 'center',
gap: 6,
backgroundColor: '#1a1a2e',
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 12,
},
agentDot: { width: 8, height: 8, borderRadius: 4 },
agentName: { color: '#fff', fontSize: 12, fontWeight: '600' },
agentRole: { color: '#888', fontSize: 10 },
filterRow: {
flexDirection: 'row',
padding: 8,
gap: 6,
borderBottomWidth: 1,
borderColor: '#1a1a2e',
},
filterTab: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 14 },
filterTabActive: { backgroundColor: '#2a2a5e' },
filterText: { color: '#888', fontSize: 12 },
filterTextActive: { color: '#4488ff', fontWeight: '600' },
footer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 10,
borderTopWidth: 1,
borderColor: '#1a1a2e',
},
footerText: { color: '#666', fontSize: 12 },
footerControls: { flexDirection: 'row', gap: 8 },
footerBtn: { paddingHorizontal: 14, paddingVertical: 6, borderRadius: 6, backgroundColor: '#2a2a4e' },
footerBtnText: { color: '#fff', fontSize: 12, fontWeight: '600' },
startBtn: { backgroundColor: '#1a3a2a' },
stopBtn: { backgroundColor: '#3a1a1a' },
});

模拟引擎 -- Node.js 用法

forexSimulationEngine 也可以在 React 之外使用,例如在 Node.js 脚本中:

import {
forexSimulationEngine,
setForexAccessToken,
setForexEngineUrl,
} from '@one_deploy/sdk';

// Configure
setForexEngineUrl('https://engine.one23.io');
setForexAccessToken(process.env.FOREX_ACCESS_TOKEN!);

// Listen
forexSimulationEngine.onLog((entry) => {
const time = new Date(entry.timestamp).toISOString();
console.log(`${time} [${entry.severity.toUpperCase()}] ${entry.type}: ${entry.message}`);
});

forexSimulationEngine.onAgentUpdate((agent) => {
console.log(`Agent ${agent.name} (${agent.role}): ${agent.status}`);
});

// Run
forexSimulationEngine.start();

// Stop after 60 seconds
setTimeout(() => {
forexSimulationEngine.stop();
const logs = forexSimulationEngine.getLogs();
console.log(`Collected ${logs.length} log entries.`);
}, 60_000);

后续步骤