mirror of
https://github.com/saymrwulf/alpha-arena.git
synced 2026-07-22 19:11:33 +00:00
A comprehensive autonomous trading system for Polymarket prediction markets featuring multi-LLM provider support, a native macOS menu bar app, and a web-based control dashboard. Key features: - Multi-agent trading system (Research, Risk, Execution, Reflection agents) - LLM provider flexibility (Anthropic, OpenAI, Google, xAI, Local models) - Automatic provider fallback chain for resilience - Native Swift/SwiftUI macOS menu bar application - FastAPI web dashboard with real-time WebSocket updates - Risk management with kill switch - Technical indicators and market analysis
362 lines
9.9 KiB
Swift
362 lines
9.9 KiB
Swift
import SwiftUI
|
|
import AppKit
|
|
|
|
/// Main menu bar content view
|
|
struct MenuBarView: View {
|
|
@EnvironmentObject var service: AlphaArenaService
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
// Header
|
|
headerSection
|
|
|
|
Divider()
|
|
.padding(.vertical, 4)
|
|
|
|
// Dashboard summary
|
|
if service.serverState.isRunning {
|
|
dashboardSection
|
|
|
|
Divider()
|
|
.padding(.vertical, 4)
|
|
}
|
|
|
|
// Server controls
|
|
serverControlsSection
|
|
|
|
Divider()
|
|
.padding(.vertical, 4)
|
|
|
|
// Quick actions
|
|
quickActionsSection
|
|
|
|
Divider()
|
|
.padding(.vertical, 4)
|
|
|
|
// Network mode
|
|
networkModeSection
|
|
|
|
Divider()
|
|
.padding(.vertical, 4)
|
|
|
|
// Footer
|
|
footerSection
|
|
}
|
|
.padding(.vertical, 8)
|
|
.frame(width: 280)
|
|
}
|
|
|
|
// MARK: - Header Section
|
|
|
|
private var headerSection: some View {
|
|
HStack {
|
|
Circle()
|
|
.fill(statusColor)
|
|
.frame(width: 10, height: 10)
|
|
|
|
Text(statusText)
|
|
.font(.headline)
|
|
|
|
Spacer()
|
|
|
|
if service.isLoading {
|
|
ProgressView()
|
|
.scaleEffect(0.6)
|
|
}
|
|
}
|
|
.padding(.horizontal, 12)
|
|
.padding(.vertical, 4)
|
|
}
|
|
|
|
private var statusColor: Color {
|
|
switch service.serverState.status {
|
|
case .running:
|
|
return .green
|
|
case .stopped:
|
|
return .gray
|
|
case .starting, .stopping:
|
|
return .orange
|
|
case .error:
|
|
return .red
|
|
}
|
|
}
|
|
|
|
private var statusText: String {
|
|
switch service.serverState.status {
|
|
case .running:
|
|
return "Server Running"
|
|
case .stopped:
|
|
return "Server Stopped"
|
|
case .starting:
|
|
return "Starting..."
|
|
case .stopping:
|
|
return "Stopping..."
|
|
case .error:
|
|
return "Error"
|
|
}
|
|
}
|
|
|
|
// MARK: - Dashboard Section
|
|
|
|
private var dashboardSection: some View {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
// URL
|
|
HStack {
|
|
Image(systemName: "link")
|
|
.foregroundColor(.secondary)
|
|
.frame(width: 16)
|
|
Text(service.serverState.displayURL)
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
|
|
// P&L
|
|
HStack {
|
|
Image(systemName: "chart.line.uptrend.xyaxis")
|
|
.foregroundColor(.secondary)
|
|
.frame(width: 16)
|
|
Text("P&L: \(service.tradingStatus.pnlFormatted)")
|
|
.font(.system(.caption, design: .monospaced))
|
|
.foregroundColor(service.tradingStatus.totalPnL >= 0 ? .green : .red)
|
|
|
|
Spacer()
|
|
|
|
Text("\(service.tradingStatus.openPositions) positions")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
|
|
// Trading status
|
|
HStack {
|
|
Image(systemName: service.tradingStatus.isActive ? "play.fill" : "stop.fill")
|
|
.foregroundColor(service.tradingStatus.isActive ? .green : .secondary)
|
|
.frame(width: 16)
|
|
Text(service.tradingStatus.statusText)
|
|
.font(.caption)
|
|
.foregroundColor(service.tradingStatus.isActive ? .primary : .secondary)
|
|
|
|
if service.tradingStatus.isActive {
|
|
Text("(\(service.tradingStatus.mode.rawValue))")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
}
|
|
.padding(.horizontal, 12)
|
|
}
|
|
|
|
// MARK: - Server Controls Section
|
|
|
|
private var serverControlsSection: some View {
|
|
VStack(spacing: 2) {
|
|
if service.serverState.isRunning {
|
|
MenuButton(
|
|
title: "Stop Server",
|
|
icon: "stop.fill",
|
|
shortcut: "S",
|
|
modifiers: [.command, .shift]
|
|
) {
|
|
Task {
|
|
await service.stopServer()
|
|
}
|
|
}
|
|
.disabled(service.isLoading)
|
|
} else {
|
|
MenuButton(
|
|
title: "Start Server",
|
|
icon: "play.fill",
|
|
shortcut: "S",
|
|
modifiers: [.command]
|
|
) {
|
|
Task {
|
|
await service.startServer()
|
|
}
|
|
}
|
|
.disabled(service.isLoading)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Quick Actions Section
|
|
|
|
private var quickActionsSection: some View {
|
|
VStack(spacing: 2) {
|
|
MenuButton(
|
|
title: "Open Dashboard",
|
|
icon: "globe",
|
|
shortcut: "D",
|
|
modifiers: [.command]
|
|
) {
|
|
service.openDashboard()
|
|
}
|
|
.disabled(!service.serverState.isRunning)
|
|
|
|
MenuButton(
|
|
title: "Copy Server URL",
|
|
icon: "doc.on.doc",
|
|
shortcut: "C",
|
|
modifiers: [.command]
|
|
) {
|
|
service.copyURL()
|
|
}
|
|
|
|
MenuButton(
|
|
title: "View Logs",
|
|
icon: "terminal",
|
|
shortcut: "L",
|
|
modifiers: [.command]
|
|
) {
|
|
service.openLogs()
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Network Mode Section
|
|
|
|
private var networkModeSection: some View {
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text("Network Mode")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
.padding(.horizontal, 12)
|
|
.padding(.bottom, 4)
|
|
|
|
ForEach(NetworkMode.allCases, id: \.rawValue) { mode in
|
|
NetworkModeButton(
|
|
mode: mode,
|
|
isSelected: service.settings.networkMode == mode
|
|
) {
|
|
service.settings.networkMode = mode
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Footer Section
|
|
|
|
private var footerSection: some View {
|
|
VStack(spacing: 2) {
|
|
MenuButton(
|
|
title: "Preferences...",
|
|
icon: "gear",
|
|
shortcut: ",",
|
|
modifiers: [.command]
|
|
) {
|
|
openPreferences()
|
|
}
|
|
|
|
Divider()
|
|
.padding(.vertical, 4)
|
|
|
|
MenuButton(
|
|
title: "Quit Alpha Arena",
|
|
icon: "power",
|
|
shortcut: "Q",
|
|
modifiers: [.command]
|
|
) {
|
|
NSApplication.shared.terminate(nil)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Menu Button Component
|
|
|
|
struct MenuButton: View {
|
|
let title: String
|
|
let icon: String
|
|
var shortcut: String? = nil
|
|
var modifiers: EventModifiers = []
|
|
let action: () -> Void
|
|
|
|
@State private var isHovering = false
|
|
|
|
var body: some View {
|
|
Button(action: action) {
|
|
HStack {
|
|
Image(systemName: icon)
|
|
.frame(width: 20)
|
|
Text(title)
|
|
Spacer()
|
|
if let shortcut = shortcut {
|
|
Text(shortcutText)
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
.padding(.horizontal, 12)
|
|
.padding(.vertical, 6)
|
|
.background(isHovering ? Color.accentColor.opacity(0.1) : Color.clear)
|
|
.cornerRadius(4)
|
|
}
|
|
.buttonStyle(.plain)
|
|
.onHover { hovering in
|
|
isHovering = hovering
|
|
}
|
|
}
|
|
|
|
private var shortcutText: String {
|
|
var parts: [String] = []
|
|
if modifiers.contains(.command) { parts.append("⌘") }
|
|
if modifiers.contains(.shift) { parts.append("⇧") }
|
|
if modifiers.contains(.option) { parts.append("⌥") }
|
|
if modifiers.contains(.control) { parts.append("⌃") }
|
|
if let shortcut = shortcut { parts.append(shortcut) }
|
|
return parts.joined()
|
|
}
|
|
}
|
|
|
|
// MARK: - Network Mode Button
|
|
|
|
struct NetworkModeButton: View {
|
|
let mode: NetworkMode
|
|
let isSelected: Bool
|
|
let action: () -> Void
|
|
|
|
@State private var isHovering = false
|
|
|
|
var body: some View {
|
|
Button(action: action) {
|
|
HStack {
|
|
Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
|
|
.foregroundColor(isSelected ? .accentColor : .secondary)
|
|
.frame(width: 20)
|
|
|
|
VStack(alignment: .leading, spacing: 1) {
|
|
Text(mode.displayName)
|
|
.font(.caption)
|
|
Text(mode.rawValue)
|
|
.font(.caption2)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
.padding(.horizontal, 12)
|
|
.padding(.vertical, 4)
|
|
.background(isHovering ? Color.accentColor.opacity(0.1) : Color.clear)
|
|
.cornerRadius(4)
|
|
}
|
|
.buttonStyle(.plain)
|
|
.onHover { hovering in
|
|
isHovering = hovering
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Preferences Helper
|
|
|
|
private func openPreferences() {
|
|
// Close the menu bar popover first
|
|
NSApp.keyWindow?.close()
|
|
|
|
// Small delay to allow menu to close, then open preferences window
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
PreferencesWindowController.shared.showPreferences()
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
MenuBarView()
|
|
.environmentObject(AlphaArenaService())
|
|
}
|