mirror of
https://github.com/saymrwulf/alpha-arena.git
synced 2026-07-21 19:08:43 +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
282 lines
8.2 KiB
Swift
282 lines
8.2 KiB
Swift
import Foundation
|
|
import UserNotifications
|
|
import AppKit
|
|
|
|
/// Notification categories
|
|
enum NotificationCategory: String {
|
|
case serverStatus = "SERVER_STATUS"
|
|
case tradingAlert = "TRADING_ALERT"
|
|
case criticalAlert = "CRITICAL_ALERT"
|
|
}
|
|
|
|
/// Notification actions
|
|
enum NotificationAction: String {
|
|
case openDashboard = "OPEN_DASHBOARD"
|
|
case viewLogs = "VIEW_LOGS"
|
|
case dismiss = "DISMISS"
|
|
}
|
|
|
|
/// Manages macOS notifications for Alpha Arena
|
|
class NotificationManager: NSObject, UNUserNotificationCenterDelegate {
|
|
private var isAuthorized = false
|
|
private let center = UNUserNotificationCenter.current()
|
|
|
|
override init() {
|
|
super.init()
|
|
center.delegate = self
|
|
setupCategories()
|
|
requestPermission()
|
|
}
|
|
|
|
// MARK: - Permission
|
|
|
|
/// Request notification permission
|
|
func requestPermission() {
|
|
center.requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, error in
|
|
DispatchQueue.main.async {
|
|
self?.isAuthorized = granted
|
|
if let error = error {
|
|
print("Notification permission error: \(error)")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Categories Setup
|
|
|
|
private func setupCategories() {
|
|
// Server status actions
|
|
let openDashboard = UNNotificationAction(
|
|
identifier: NotificationAction.openDashboard.rawValue,
|
|
title: "Open Dashboard",
|
|
options: .foreground
|
|
)
|
|
|
|
let viewLogs = UNNotificationAction(
|
|
identifier: NotificationAction.viewLogs.rawValue,
|
|
title: "View Logs",
|
|
options: .foreground
|
|
)
|
|
|
|
let dismiss = UNNotificationAction(
|
|
identifier: NotificationAction.dismiss.rawValue,
|
|
title: "Dismiss",
|
|
options: .destructive
|
|
)
|
|
|
|
// Server status category
|
|
let serverCategory = UNNotificationCategory(
|
|
identifier: NotificationCategory.serverStatus.rawValue,
|
|
actions: [openDashboard, viewLogs, dismiss],
|
|
intentIdentifiers: [],
|
|
options: .customDismissAction
|
|
)
|
|
|
|
// Trading alert category
|
|
let tradingCategory = UNNotificationCategory(
|
|
identifier: NotificationCategory.tradingAlert.rawValue,
|
|
actions: [openDashboard, dismiss],
|
|
intentIdentifiers: [],
|
|
options: .customDismissAction
|
|
)
|
|
|
|
// Critical alert category
|
|
let criticalCategory = UNNotificationCategory(
|
|
identifier: NotificationCategory.criticalAlert.rawValue,
|
|
actions: [openDashboard, viewLogs],
|
|
intentIdentifiers: [],
|
|
options: [.customDismissAction]
|
|
)
|
|
|
|
center.setNotificationCategories([serverCategory, tradingCategory, criticalCategory])
|
|
}
|
|
|
|
// MARK: - Send Notifications
|
|
|
|
/// Send server started notification
|
|
func sendServerStartedNotification(url: String) {
|
|
guard AppSettings.shared.showNotifications else { return }
|
|
|
|
send(
|
|
title: "Alpha Arena Started",
|
|
body: "Server is running at \(url)",
|
|
category: .serverStatus,
|
|
userInfo: ["url": url]
|
|
)
|
|
}
|
|
|
|
/// Send server stopped notification
|
|
func sendServerStoppedNotification() {
|
|
guard AppSettings.shared.showNotifications else { return }
|
|
|
|
send(
|
|
title: "Alpha Arena Stopped",
|
|
body: "Server has been stopped",
|
|
category: .serverStatus
|
|
)
|
|
}
|
|
|
|
/// Send error notification
|
|
func sendErrorNotification(message: String) {
|
|
guard AppSettings.shared.showNotifications else { return }
|
|
|
|
send(
|
|
title: "Alpha Arena Error",
|
|
body: message,
|
|
category: .criticalAlert,
|
|
sound: .defaultCritical
|
|
)
|
|
}
|
|
|
|
/// Send trading started notification
|
|
func sendTradingStartedNotification(mode: TradingMode) {
|
|
guard AppSettings.shared.showNotifications else { return }
|
|
|
|
send(
|
|
title: "Trading Started",
|
|
body: "Trading is now active in \(mode.rawValue) mode",
|
|
category: .tradingAlert
|
|
)
|
|
}
|
|
|
|
/// Send trading stopped notification
|
|
func sendTradingStoppedNotification() {
|
|
guard AppSettings.shared.showNotifications else { return }
|
|
|
|
send(
|
|
title: "Trading Stopped",
|
|
body: "Trading has been stopped",
|
|
category: .tradingAlert
|
|
)
|
|
}
|
|
|
|
/// Send kill switch notification
|
|
func sendKillSwitchNotification(reason: String) {
|
|
send(
|
|
title: "Kill Switch Activated",
|
|
body: reason,
|
|
category: .criticalAlert,
|
|
sound: .defaultCritical
|
|
)
|
|
}
|
|
|
|
/// Send P&L update notification
|
|
func sendPnLNotification(pnl: Double, positions: Int) {
|
|
guard AppSettings.shared.showNotifications else { return }
|
|
|
|
let sign = pnl >= 0 ? "+" : ""
|
|
let body = "P&L: \(sign)$\(String(format: "%.2f", pnl)) | \(positions) positions"
|
|
|
|
send(
|
|
title: "Alpha Arena Update",
|
|
body: body,
|
|
category: .tradingAlert
|
|
)
|
|
}
|
|
|
|
// MARK: - Private Methods
|
|
|
|
private func send(
|
|
title: String,
|
|
body: String,
|
|
category: NotificationCategory,
|
|
sound: UNNotificationSound = .default,
|
|
userInfo: [String: Any] = [:]
|
|
) {
|
|
guard isAuthorized else {
|
|
// Fall back to simple alert if not authorized
|
|
showFallbackAlert(title: title, body: body)
|
|
return
|
|
}
|
|
|
|
let content = UNMutableNotificationContent()
|
|
content.title = title
|
|
content.body = body
|
|
content.sound = sound
|
|
content.categoryIdentifier = category.rawValue
|
|
content.userInfo = userInfo
|
|
|
|
let request = UNNotificationRequest(
|
|
identifier: UUID().uuidString,
|
|
content: content,
|
|
trigger: nil // Deliver immediately
|
|
)
|
|
|
|
center.add(request) { error in
|
|
if let error = error {
|
|
print("Failed to send notification: \(error)")
|
|
}
|
|
}
|
|
}
|
|
|
|
private func showFallbackAlert(title: String, body: String) {
|
|
DispatchQueue.main.async {
|
|
let alert = NSAlert()
|
|
alert.messageText = title
|
|
alert.informativeText = body
|
|
alert.alertStyle = .informational
|
|
alert.addButton(withTitle: "OK")
|
|
alert.runModal()
|
|
}
|
|
}
|
|
|
|
// MARK: - UNUserNotificationCenterDelegate
|
|
|
|
func userNotificationCenter(
|
|
_ center: UNUserNotificationCenter,
|
|
willPresent notification: UNNotification,
|
|
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
|
|
) {
|
|
// Show notification even when app is in foreground
|
|
completionHandler([.banner, .sound])
|
|
}
|
|
|
|
func userNotificationCenter(
|
|
_ center: UNUserNotificationCenter,
|
|
didReceive response: UNNotificationResponse,
|
|
withCompletionHandler completionHandler: @escaping () -> Void
|
|
) {
|
|
let actionIdentifier = response.actionIdentifier
|
|
|
|
switch actionIdentifier {
|
|
case NotificationAction.openDashboard.rawValue, UNNotificationDefaultActionIdentifier:
|
|
openDashboard()
|
|
|
|
case NotificationAction.viewLogs.rawValue:
|
|
openLogs()
|
|
|
|
default:
|
|
break
|
|
}
|
|
|
|
completionHandler()
|
|
}
|
|
|
|
// MARK: - Actions
|
|
|
|
private func openDashboard() {
|
|
let settings = AppSettings.shared
|
|
let host = settings.networkMode == .lan ? (getLocalIPAddress() ?? "127.0.0.1") : "127.0.0.1"
|
|
let url = URL(string: "http://\(host):\(settings.serverPort)")!
|
|
NSWorkspace.shared.open(url)
|
|
}
|
|
|
|
private func openLogs() {
|
|
let settings = AppSettings.shared
|
|
let projectPath = settings.projectPath
|
|
|
|
guard !projectPath.isEmpty else { return }
|
|
|
|
let script = """
|
|
tell application "Terminal"
|
|
do script "cd '\(projectPath)' && ./alpha logs"
|
|
activate
|
|
end tell
|
|
"""
|
|
|
|
var error: NSDictionary?
|
|
if let scriptObject = NSAppleScript(source: script) {
|
|
scriptObject.executeAndReturnError(&error)
|
|
}
|
|
}
|
|
}
|