AI分析系统-原生安卓

6 阅读4分钟

📁 完整项目代码结构 text GoldAI/ ├── app/ │ ├── src/ │ │ ├── main/ │ │ │ ├── java/com/goldai/ │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── data/ │ │ │ │ │ ├── model/ │ │ │ │ │ │ ├── Tick.kt │ │ │ │ │ │ ├── Kline.kt │ │ │ │ │ │ ├── Signal.kt │ │ │ │ │ │ └── StrategyConfig.kt │ │ │ │ │ ├── network/ │ │ │ │ │ │ ├── AllTickWebSocket.kt │ │ │ │ │ │ ├── ItickApi.kt │ │ │ │ │ │ └── NetworkModule.kt │ │ │ │ │ ├── repository/ │ │ │ │ │ │ └── MarketRepository.kt │ │ │ │ │ └── database/ │ │ │ │ │ ├── AppDatabase.kt │ │ │ │ │ └── SettingDao.kt │ │ │ │ ├── domain/ │ │ │ │ │ ├── strategy/ │ │ │ │ │ │ └── StrategyCalculator.kt │ │ │ │ │ └── usecase/ │ │ │ │ │ └── GetSignalsUseCase.kt │ │ │ │ ├── presentation/ │ │ │ │ │ ├── theme/ │ │ │ │ │ │ ├── Color.kt │ │ │ │ │ │ ├── Theme.kt │ │ │ │ │ │ └── Type.kt │ │ │ │ │ ├── screens/ │ │ │ │ │ │ ├── DashboardScreen.kt │ │ │ │ │ │ ├── StrategiesScreen.kt │ │ │ │ │ │ ├── SimulationScreen.kt │ │ │ │ │ │ ├── BacktestScreen.kt │ │ │ │ │ │ ├── AnalysisScreen.kt │ │ │ │ │ │ └── SettingsScreen.kt │ │ │ │ │ ├── components/ │ │ │ │ │ │ ├── PriceCard.kt │ │ │ │ │ │ ├── SignalCard.kt │ │ │ │ │ │ └── Tooltip.kt │ │ │ │ │ └── viewmodel/ │ │ │ │ │ ├── DashboardViewModel.kt │ │ │ │ │ ├── StrategiesViewModel.kt │ │ │ │ │ └── SettingsViewModel.kt │ │ │ │ └── utils/ │ │ │ │ ├── Constants.kt │ │ │ │ └── Extensions.kt │ │ │ └── res/ │ │ │ ├── values/ │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ └── themes.xml │ │ │ └── drawable/ (可忽略) │ │ └── AndroidManifest.xml │ └── build.gradle.kts └── build.gradle.kts

  1. 项目根 build.gradle.kts kotlin // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { id("com.android.application") version "8.1.0" apply false id("org.jetbrains.kotlin.android") version "1.9.0" apply false id("com.google.dagger.hilt.android") version "2.48" apply false id("org.jetbrains.kotlin.kapt") version "1.9.0" apply false }
  2. app/build.gradle.kts kotlin plugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("com.google.dagger.hilt.android") id("org.jetbrains.kotlin.kapt") }

android { namespace = "com.goldai" compileSdk = 34

defaultConfig {
    applicationId = "com.goldai"
    minSdk = 24
    targetSdk = 34
    versionCode = 1
    versionName = "2.0"

    testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    vectorDrawables {
        useSupportLibrary = true
    }
}

buildTypes {
    release {
        isMinifyEnabled = false
        proguardFiles(
            getDefaultProguardFile("proguard-android-optimize.txt"),
            "proguard-rules.pro"
        )
    }
}
compileOptions {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
    jvmTarget = "1.8"
}
buildFeatures {
    compose = true
}
composeOptions {
    kotlinCompilerExtensionVersion = "1.5.4"
}
packaging {
    resources {
        excludes += "/META-INF/{AL2.0,LGPL2.1}"
    }
}

}

dependencies { implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") implementation("androidx.activity:activity-compose:1.8.0") implementation(platform("androidx.compose:compose-bom:2023.10.01")) implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.material3:material3") implementation("androidx.navigation:navigation-compose:2.7.5")

// ViewModel
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")

// Coroutines
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")

// Networking
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
implementation("io.ktor:ktor-client-core:2.3.5")
implementation("io.ktor:ktor-client-cio:2.3.5")
implementation("io.ktor:ktor-client-websockets:2.3.5")

// Dependency Injection
implementation("com.google.dagger:hilt-android:2.48")
kapt("com.google.dagger:hilt-compiler:2.48")
implementation("androidx.hilt:hilt-navigation-compose:1.1.0")

// Database
implementation("androidx.room:room-runtime:2.6.0")
kapt("androidx.room:room-compiler:2.6.0")
implementation("androidx.room:room-ktx:2.6.0")

// Charts
implementation("com.github.PhilJay:MPAndroidChart:v3.1.0")

// Preferences DataStore
implementation("androidx.datastore:datastore-preferences:1.0.0")

testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.10.01"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")

} 3. AndroidManifest.xml xml

<uses-permission android:name="android.permission.INTERNET" />
<!-- 如果需要悬浮窗,后续添加 -->
<!-- <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> -->

<application
    android:allowBackup="true"
    android:dataExtractionRules="@xml/data_extraction_rules"
    android:fullBackupContent="@xml/backup_rules"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme.GoldAI"
    tools:targetApi="31">
    <activity
        android:name=".MainActivity"
        android:exported="true"
        android:label="@string/app_name"
        android:theme="@style/Theme.GoldAI">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>
4. MainActivity.kt kotlin package com.goldai

import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.goldai.presentation.screens.* import com.goldai.presentation.theme.GoldAITheme import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { GoldAITheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { val navController = rememberNavController() NavHost(navController = navController, startDestination = "dashboard") { composable("dashboard") { DashboardScreen(navController) } composable("strategies") { StrategiesScreen(navController) } composable("simulation") { SimulationScreen(navController) } composable("backtest") { BacktestScreen(navController) } composable("analysis") { AnalysisScreen(navController) } composable("settings") { SettingsScreen(navController) } } } } } } }

  1. data/model/Tick.kt kotlin package com.goldai.data.model

data class Tick( val timestamp: Long, val price: Double, val volume: Double ) 6. data/model/Kline.kt kotlin package com.goldai.data.model

data class Kline( val time: Long, val open: Double, val high: Double, val low: Double, val close: Double, val volume: Double ) 7. data/model/Signal.kt kotlin package com.goldai.data.model

enum class SignalType { MID, SHORT, ULTRA, DIVE, BOUNCE, SURGE_BUY, SURGE_SELL } enum class Action { BUY, SELL, WAIT }

data class Signal( val type: SignalType, val action: Action, val color: String, // "red", "green", "gray" val reason: String, val timestamp: Long ) 8. data/model/StrategyConfig.kt kotlin package com.goldai.data.model

data class StrategyConfig( val midMa5Period: Int = 5, val midMa20Period: Int = 20, val midRsiThreshold: Int = 50, val shortBbPeriod: Int = 20, val shortBbDev: Double = 2.0, val shortRsiOverbought: Int = 70, val shortRsiOversold: Int = 30, val ultraKdjPeriod: Int = 9, val diveDropThreshold: Double = 1.0, val diveTimeWindow: Int = 5, val diveVolumeMultiple: Double = 1.2, val bounceThreshold: Double = 0.3, val bounceRsiThreshold: Int = 35, val surgeBuyRiseThreshold: Double = 0.5, val surgeBuyTimeWindow: Int = 2, val surgeBuyVolumeMultiple: Double = 1.5, val surgeSellPullbackThreshold: Double = 0.3, val surgeSellTimeWindow: Int = 3 ) 9. data/network/AllTickWebSocket.kt kotlin package com.goldai.data.network

import android.util.Log import com.goldai.data.model.Tick import io.ktor.client.* import io.ktor.client.engine.cio.* import io.ktor.client.plugins.websocket.* import io.ktor.websocket.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import kotlinx.serialization.json.*

class AllTickWebSocket( private val token: String, private val onTick: (Tick) -> Unit ) { private val client = HttpClient(CIO) { install(WebSockets) } private var job: Job? = null private val _connectionState = MutableStateFlow(false) val connectionState: StateFlow = _connectionState.asStateFlow()

fun connect() {
    if (_connectionState.value) return
    job = CoroutineScope(Dispatchers.IO).launch {
        var retryCount = 0
        while (isActive) {
            try {
                client.webSocket(
                    method = HttpMethod.Get,
                    host = "quote.alltick.io",
                    path = "/quote-b-ws-api"
                ) {
                    Log.d("AllTickWS", "Connected")
                    _connectionState.value = true
                    retryCount = 0

                    // 发送订阅
                    send(Json.encodeToString(mapOf(
                        "cmd" to "subscribe",
                        "args" to listOf("XAUUSD")
                    )))

                    for (frame in incoming) {
                        when (frame) {
                            is Frame.Text -> {
                                val text = frame.readText()
                                val json = Json.parseToJsonElement(text)
                                val data = json.jsonObject["data"]?.jsonArray?.get(0)?.jsonArray
                                if (data != null) {
                                    val timestamp = data[0].jsonPrimitive.long
                                    val price = data[4].jsonPrimitive.double
                                    val volume = data[5].jsonPrimitive.long / 10000.0
                                    onTick(Tick(timestamp, price, volume))
                                }
                            }
                        }
                    }
                }
            } catch (e: Exception) {
                Log.e("AllTickWS", "Connection error: ${e.message}")
                _connectionState.value = false
                retryCount++
                val delayTime = (5000 * retryCount).coerceAtMost(30000)
                delay(delayTime.toLong())
            }
        }
    }
}

fun disconnect() {
    job?.cancel()
    client.close()
    _connectionState.value = false
}

} 10. data/network/ItickApi.kt kotlin package com.goldai.data.network

import com.goldai.data.model.Kline import retrofit2.http.*

interface ItickApi { @GET("forex/kline") suspend fun getKline( @Query("region") region: String = "gb", @Query("code") code: String = "XAUUSD", @Query("kType") kType: Int = 1 ): ItickResponse }

data class ItickResponse( val code: Int?, val data: List? )

data class ItickKline( val t: Long, val o: Double, val h: Double, val l: Double, val c: Double, val v: Double )

fun ItickKline.toKline(): Kline = Kline(t, o, h, l, c, v) 11. data/network/NetworkModule.kt kotlin package com.goldai.data.network

import com.google.gson.GsonBuilder import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import java.util.concurrent.TimeUnit import javax.inject.Singleton

@Module @InstallIn(SingletonComponent::class) object NetworkModule {

private const val BASE_URL = "https://api.itick.org/"

@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
    val logging = HttpLoggingInterceptor().apply {
        level = HttpLoggingInterceptor.Level.BODY
    }
    return OkHttpClient.Builder()
        .addInterceptor(logging)
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .writeTimeout(30, TimeUnit.SECONDS)
        .build()
}

@Provides
@Singleton
fun provideRetrofit(client: OkHttpClient): Retrofit {
    val gson = GsonBuilder()
        .setLenient()
        .create()
    return Retrofit.Builder()
        .baseUrl(BASE_URL)
        .client(client)
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build()
}

@Provides
@Singleton
fun provideItickApi(retrofit: Retrofit): ItickApi {
    return retrofit.create(ItickApi::class.java)
}

} 12. data/repository/MarketRepository.kt kotlin package com.goldai.data.repository

import android.util.Log import com.goldai.data.model.Signal import com.goldai.data.model.Tick import com.goldai.data.network.AllTickWebSocket import com.goldai.data.network.ItickApi import com.goldai.domain.strategy.StrategyCalculator import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Singleton

@Singleton class MarketRepository @Inject constructor( private val itickApi: ItickApi, private val strategyCalculator: StrategyCalculator ) { private lateinit var allTickWebSocket: AllTickWebSocket private val _tickFlow = MutableSharedFlow() val tickFlow: SharedFlow = _tickFlow.asSharedFlow()

private val _signals = MutableStateFlow<List<Signal>>(emptyList())
val signals: StateFlow<List<Signal>> = _signals.asStateFlow()

private val _priceHistory = MutableStateFlow<List<Double>>(emptyList())
val priceHistory: StateFlow<List<Double>> = _priceHistory.asStateFlow()

private val _volumeHistory = MutableStateFlow<List<Double>>(emptyList())
val volumeHistory: StateFlow<List<Double>> = _volumeHistory.asStateFlow()

private val viewModelScope = CoroutineScope(Dispatchers.Default)

fun initAllTick(token: String) {
    allTickWebSocket = AllTickWebSocket(token) { tick ->
        viewModelScope.launch {
            _tickFlow.emit(tick)
            updateHistory(tick.price, tick.volume)
            calculateSignals()
        }
    }
}

fun startAllTick() {
    if (::allTickWebSocket.isInitialized) {
        allTickWebSocket.connect()
    }
}

fun stopAllTick() {
    if (::allTickWebSocket.isInitialized) {
        allTickWebSocket.disconnect()
    }
}

private fun updateHistory(price: Double, volume: Double) {
    val newPrices = _priceHistory.value.toMutableList().apply {
        add(price)
        if (size > 100) removeAt(0)
    }
    val newVolumes = _volumeHistory.value.toMutableList().apply {
        add(volume)
        if (size > 100) removeAt(0)
    }
    _priceHistory.value = newPrices
    _volumeHistory.value = newVolumes
}

private fun calculateSignals() {
    val prices = _priceHistory.value
    val volumes = _volumeHistory.value
    if (prices.size < 20) return

    val avgVolume = if (volumes.size >= 20) volumes.takeLast(20).average() else 1.0
    val currentVolume = volumes.lastOrNull() ?: 0.0
    val rsi = strategyCalculator.calculateRSI(prices)
    val ma5 = strategyCalculator.calculateMA(prices, 5)
    val ma10 = strategyCalculator.calculateMA(prices, 10)
    val ma20 = strategyCalculator.calculateMA(prices, 20)
    val ma50 = strategyCalculator.calculateMA(prices, 50)
    val ma200 = strategyCalculator.calculateMA(prices, 200)

    val signals = listOf(
        strategyCalculator.calculateMidSignal(prices, rsi),
        strategyCalculator.calculateShortSignal(prices, rsi),
        strategyCalculator.calculateUltraSignal(prices, currentVolume, avgVolume),
        strategyCalculator.detectDive(prices, volumes, avgVolume),
        strategyCalculator.detectBounce(prices, rsi),
        strategyCalculator.calculateSurgeBuySignal(prices, currentVolume, avgVolume),
        strategyCalculator.calculateSurgeSellSignal(prices)
    )
    _signals.value = signals
}

suspend fun fetchItickKline(): List<com.goldai.data.model.Kline> {
    return try {
        val response = itickApi.getKline()
        response.data?.map { it.toKline() } ?: emptyList()
    } catch (e: Exception) {
        Log.e("MarketRepository", "iTick fetch failed", e)
        emptyList()
    }
}

} 13. domain/strategy/StrategyCalculator.kt kotlin package com.goldai.domain.strategy

import com.goldai.data.model.* import javax.inject.Inject import javax.inject.Singleton

@Singleton class StrategyCalculator @Inject constructor() {

fun calculateMA(prices: List<Double>, period: Int): Double {
    if (prices.size < period) return prices.lastOrNull() ?: 0.0
    return prices.takeLast(period).average()
}

fun calculateRSI(prices: List<Double>, period: Int = 14): Double {
    if (prices.size < period + 1) return 50.0
    var gains = 0.0
    var losses = 0.0
    for (i in prices.size - period until prices.size) {
        val diff = prices[i] - prices[i - 1]
        if (diff > 0) gains += diff else losses -= diff
    }
    val avgGain = gains / period
    val avgLoss = losses / period
    if (avgLoss == 0.0) return 100.0
    val rs = avgGain / avgLoss
    return 100.0 - 100.0 / (1 + rs)
}

fun calculateMidSignal(prices: List<Double>, rsi: Double, config: StrategyConfig = StrategyConfig()): Signal {
    if (prices.size < 20) return Signal(SignalType.MID, Action.WAIT, "gray", "数据不足", System.currentTimeMillis())
    val ma5 = calculateMA(prices, config.midMa5Period)
    val ma20 = calculateMA(prices, config.midMa20Period)
    return when {
        ma5 > ma20 && rsi > config.midRsiThreshold -> Signal(SignalType.MID, Action.BUY, "red", "MA5(${ma5.toInt()})上穿MA20(${ma20.toInt()}),RSI偏强", System.currentTimeMillis())
        ma5 < ma20 && rsi < config.midRsiThreshold -> Signal(SignalType.MID, Action.SELL, "green", "MA5下穿MA20,RSI偏弱", System.currentTimeMillis())
        else -> Signal(SignalType.MID, Action.WAIT, "gray", "均线纠缠,RSI中性", System.currentTimeMillis())
    }
}

fun calculateShortSignal(prices: List<Double>, rsi: Double, config: StrategyConfig = StrategyConfig()): Signal {
    if (prices.size < 20) return Signal(SignalType.SHORT, Action.WAIT, "gray", "数据不足", System.currentTimeMillis())
    val ma5 = calculateMA(prices, 5)
    val ma10 = calculateMA(prices, 10)
    val ma20 = calculateMA(prices, 20)
    val upper = ma20 * (1 + config.shortBbDev / 100)
    val lower = ma20 * (1 - config.shortBbDev / 100)
    val currentPrice = prices.last()
    return when {
        currentPrice > upper && rsi > config.shortRsiOverbought -> Signal(SignalType.SHORT, Action.SELL, "green", "突破布林上轨(${upper.toInt()}),RSI超买", System.currentTimeMillis())
        currentPrice < lower && rsi < config.shortRsiOversold -> Signal(SignalType.SHORT, Action.BUY, "red", "跌破布林下轨(${lower.toInt()}),RSI超卖", System.currentTimeMillis())
        currentPrice > ma5 && ma5 > ma10 -> Signal(SignalType.SHORT, Action.BUY, "red", "短期均线多头排列", System.currentTimeMillis())
        currentPrice < ma5 && ma5 < ma10 -> Signal(SignalType.SHORT, Action.SELL, "green", "短期均线空头排列", System.currentTimeMillis())
        else -> Signal(SignalType.SHORT, Action.WAIT, "gray", "价格在中轨附近", System.currentTimeMillis())
    }
}

fun calculateUltraSignal(prices: List<Double>, volume: Double, avgVolume: Double, config: StrategyConfig = StrategyConfig()): Signal {
    if (prices.size < 5) return Signal(SignalType.ULTRA, Action.WAIT, "gray", "数据不足", System.currentTimeMillis())
    val ma5 = calculateMA(prices, 5)
    val currentPrice = prices.last()
    return when {
        currentPrice > ma5 * 1.002 && volume > avgVolume * 1.2 -> Signal(SignalType.ULTRA, Action.BUY, "red", "价格突破MA5,成交量放大", System.currentTimeMillis())
        currentPrice < ma5 * 0.998 && volume > avgVolume * 1.2 -> Signal(SignalType.ULTRA, Action.SELL, "green", "价格跌破MA5,成交量放大", System.currentTimeMillis())
        else -> Signal(SignalType.ULTRA, Action.WAIT, "gray", "价格在MA5附近", System.currentTimeMillis())
    }
}

fun detectDive(prices: List<Double>, volumes: List<Double>, avgVolume: Double, config: StrategyConfig = StrategyConfig()): Signal {
    if (prices.size < config.diveTimeWindow) return Signal(SignalType.DIVE, Action.WAIT, "gray", "数据不足", System.currentTimeMillis())
    val startPrice = prices[prices.size - config.diveTimeWindow]
    val endPrice = prices.last()
    val dropPct = (startPrice - endPrice) / startPrice * 100
    val currentVolume = volumes.lastOrNull() ?: 0.0
    val detected = dropPct >= config.diveDropThreshold && currentVolume > avgVolume * config.diveVolumeMultiple
    val reason = if (detected) "${config.diveTimeWindow}分钟内下跌${"%.2f".format(dropPct)}%,成交量放大" else "未检测到跳水"
    return Signal(SignalType.DIVE, Action.WAIT, if (detected) "yellow" else "gray", reason, System.currentTimeMillis())
}

fun detectBounce(prices: List<Double>, rsi: Double, config: StrategyConfig = StrategyConfig()): Signal {
    if (prices.size < 2) return Signal(SignalType.BOUNCE, Action.WAIT, "gray", "数据不足", System.currentTimeMillis())
    val currentPrice = prices.last()
    val prevPrice = prices[prices.size - 2]
    val bouncePct = (currentPrice - prevPrice) / prevPrice * 100
    val buySignal = bouncePct >= config.bounceThreshold && rsi < config.bounceRsiThreshold
    val reason = if (buySignal) "反弹${"%.2f".format(bouncePct)}%,RSI超卖" else "无有效反弹"
    return Signal(SignalType.BOUNCE, if (buySignal) Action.BUY else Action.WAIT, if (buySignal) "red" else "gray", reason, System.currentTimeMillis())
}

fun calculateSurgeBuySignal(prices: List<Double>, volume: Double, avgVolume: Double, config: StrategyConfig = StrategyConfig()): Signal {
    if (prices.size < config.surgeBuyTimeWindow + 1) return Signal(SignalType.SURGE_BUY, Action.WAIT, "gray", "数据不足", System.currentTimeMillis())
    val startPrice = prices[prices.size - config.surgeBuyTimeWindow - 1]
    val endPrice = prices.last()
    val risePct = (endPrice - startPrice) / startPrice * 100
    val triggered = risePct >= config.surgeBuyRiseThreshold && volume > avgVolume * config.surgeBuyVolumeMultiple
    val reason = if (triggered) "${config.surgeBuyTimeWindow}分钟内上涨${"%.2f".format(risePct)}%,成交量放大" else "未达爆拉阈值"
    return Signal(SignalType.SURGE_BUY, if (triggered) Action.BUY else Action.WAIT, if (triggered) "red" else "gray", reason, System.currentTimeMillis())
}

fun calculateSurgeSellSignal(prices: List<Double>, config: StrategyConfig = StrategyConfig()): Signal {
    if (prices.size < config.surgeSellTimeWindow + 1) return Signal(SignalType.SURGE_SELL, Action.WAIT, "gray", "数据不足", System.currentTimeMillis())
    val recentHigh = prices.takeLast(config.surgeSellTimeWindow + 1).maxOrNull() ?: 0.0
    val currentPrice = prices.last()
    val pullbackPct = (recentHigh - currentPrice) / recentHigh * 100
    val triggered = pullbackPct >= config.surgeSellPullbackThreshold
    val reason = if (triggered) "从高点回调${"%.2f".format(pullbackPct)}%" else "未达回调阈值"
    return Signal(SignalType.SURGE_SELL, if (triggered) Action.SELL else Action.WAIT, if (triggered) "green" else "gray", reason, System.currentTimeMillis())
}

fun calculateAllSignals(
    prices: List<Double>,
    volumes: List<Double>,
    rsi: Double,
    currentVolume: Double,
    avgVolume: Double,
    config: StrategyConfig
): List<Signal> {
    return listOf(
        calculateMidSignal(prices, rsi, config),
        calculateShortSignal(prices, rsi, config),
        calculateUltraSignal(prices, currentVolume, avgVolume, config),
        detectDive(prices, volumes, avgVolume, config),
        detectBounce(prices, rsi, config),
        calculateSurgeBuySignal(prices, currentVolume, avgVolume, config),
        calculateSurgeSellSignal(prices, config)
    )
}

}

  1. presentation/theme/Color.kt kotlin package com.goldai.presentation.theme

import androidx.compose.ui.graphics.Color

val Purple80 = Color(0xFFD0BCFF) val PurpleGrey80 = Color(0xFFCCC2DC) val Pink80 = Color(0xFFEFB8C8)

val Purple40 = Color(0xFF6650a4) val PurpleGrey40 = Color(0xFF625b71) val Pink40 = Color(0xFF7D5260)

// 自定义颜色 val RedBuy = Color(0xFFEF4444) val GreenSell = Color(0xFF10B981) val GrayNeutral = Color(0xFF6B7280) 15. presentation/theme/Theme.kt kotlin package com.goldai.presentation.theme

import android.app.Activity import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.core.view.WindowCompat

private val DarkColorScheme = darkColorScheme( primary = Purple80, secondary = PurpleGrey80, tertiary = Pink80 )

private val LightColorScheme = lightColorScheme( primary = Purple40, secondary = PurpleGrey40, tertiary = Pink40

/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/

)

@Composable fun GoldAITheme( darkTheme: Boolean = isSystemInDarkTheme(), // Dynamic color is available on Android 12+ dynamicColor: Boolean = true, content: @Composable () -> Unit ) { val colorScheme = when { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { val context = LocalContext.current if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } darkTheme -> DarkColorScheme else -> LightColorScheme } val view = LocalView.current if (!view.isInEditMode) { SideEffect { val window = (view.context as Activity).window window.statusBarColor = colorScheme.primary.toArgb() WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme } }

MaterialTheme(
    colorScheme = colorScheme,
    typography = Typography(),
    content = content
)

} 16. presentation/theme/Type.kt kotlin package com.goldai.presentation.theme

import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp

// Set of Material typography styles to start with val Typography = Typography( bodyLarge = TextStyle( fontFamily = FontFamily.Default, fontWeight = FontWeight.Normal, fontSize = 16.sp, lineHeight = 24.sp, letterSpacing = 0.5.sp ) /* Other default text styles to override titleLarge = TextStyle( fontFamily = FontFamily.Default, fontWeight = FontWeight.Normal, fontSize = 22.sp, lineHeight = 28.sp, letterSpacing = 0.sp ), labelSmall = TextStyle( fontFamily = FontFamily.Default, fontWeight = FontWeight.Medium, fontSize = 11.sp, lineHeight = 16.sp, letterSpacing = 0.5.sp ) */ ) 17. presentation/components/PriceCard.kt kotlin package com.goldai.presentation.components

import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.goldai.presentation.theme.GreenSell import com.goldai.presentation.theme.RedBuy

@Composable fun PriceCard( price: Double, changePercent: String, lastUpdate: String, modifier: Modifier = Modifier ) { Card( modifier = modifier.fillMaxWidth(), elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) ) { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Text( text = "实时金价", style = MaterialTheme.typography.titleMedium ) Spacer(modifier = Modifier.height(8.dp)) Text( text = "$${String.format("%.2f", price)}", fontSize = 32.sp, fontWeight = FontWeight.Bold ) val change = changePercent.toDoubleOrNull() ?: 0.0 val changeColor = if (change >= 0) RedBuy else GreenSell Text( text = if (change >= 0) "+changePercentchangePercent%" else "changePercent%", color = changeColor, fontSize = 18.sp, fontWeight = FontWeight.Medium ) Spacer(modifier = Modifier.height(4.dp)) Text( text = "更新于: $lastUpdate", fontSize = 12.sp, color = Color.Gray ) } } } 18. presentation/components/SignalCard.kt kotlin package com.goldai.presentation.components

import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.goldai.data.model.Signal import com.goldai.presentation.theme.GreenSell import com.goldai.presentation.theme.RedBuy

@Composable fun SignalCard( signal: Signal, modifier: Modifier = Modifier, onVerifyClick: () -> Unit = {} ) { val signalColor = when (signal.color) { "red" -> RedBuy "green" -> GreenSell else -> Color.Gray }

Card(
    modifier = modifier.fillMaxWidth(),
    elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(12.dp)
    ) {
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceBetween
        ) {
            Text(
                text = signal.type.name,
                style = MaterialTheme.typography.titleSmall
            )
            Text(
                text = signal.action.name,
                color = signalColor,
                fontWeight = FontWeight.Bold
            )
        }
        Spacer(modifier = Modifier.height(4.dp))
        Text(
            text = signal.reason,
            style = MaterialTheme.typography.bodySmall,
            color = Color.Gray
        )
        Spacer(modifier = Modifier.height(8.dp))
        Button(
            onClick = onVerifyClick,
            modifier = Modifier.align(Alignment.End)
        ) {
            Text("AI验证")
        }
    }
}

} 19. presentation/components/Tooltip.kt kotlin package com.goldai.presentation.components

import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Info import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp

@Composable fun Tooltip( term: String, definition: String, modifier: Modifier = Modifier ) { var showDialog by remember { mutableStateOf(false) }

Box(modifier = modifier) {
    IconButton(onClick = { showDialog = true }) {
        Icon(
            imageVector = Icons.Default.Info,
            contentDescription = "Info",
            tint = MaterialTheme.colorScheme.primary
        )
    }
}

if (showDialog) {
    AlertDialog(
        onDismissRequest = { showDialog = false },
        title = { Text(term) },
        text = { Text(definition) },
        confirmButton = {
            TextButton(onClick = { showDialog = false }) {
                Text("我知道了")
            }
        }
    )
}

} 20. presentation/screens/DashboardScreen.kt kotlin package com.goldai.presentation.screens

import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.goldai.presentation.components.PriceCard import com.goldai.presentation.components.SignalCard import com.goldai.presentation.viewmodel.DashboardViewModel

@OptIn(ExperimentalMaterial3Api::class) @Composable fun DashboardScreen( navController: NavController, viewModel: DashboardViewModel = hiltViewModel() ) { val uiState by viewModel.uiState.collectAsState()

Scaffold(
    topBar = {
        TopAppBar(
            title = { Text("黄金AI交易系统") }
        )
    }
) { paddingValues ->
    LazyColumn(
        modifier = Modifier
            .fillMaxSize()
            .padding(paddingValues)
            .padding(horizontal = 16.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        item {
            PriceCard(
                price = uiState.currentPrice,
                changePercent = uiState.changePercent,
                lastUpdate = uiState.lastUpdate
            )
        }

        item {
            Card(
                modifier = Modifier.fillMaxWidth(),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
            ) {
                Column(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp)
                ) {
                    Text(
                        text = "市场快照",
                        style = MaterialTheme.typography.titleMedium
                    )
                    Spacer(modifier = Modifier.height(8.dp))
                    // 简化的市场快照,可后续扩展
                    Text("ETF持仓: ${uiState.etfHolding}吨")
                    Text("COMEX: ${uiState.comexPosition}手")
                    Text("伦敦金: ${uiState.londonFlow}M")
                    Text("交易量: ${uiState.volume}万手")
                }
            }
        }

        item {
            Text(
                text = "实时信号",
                style = MaterialTheme.typography.titleLarge
            )
        }

        items(uiState.signals) { signal ->
            SignalCard(
                signal = signal,
                onVerifyClick = { /* 暂时弹窗,后续可接入AI验证 */ }
            )
        }
    }
}

} 21. presentation/viewmodel/DashboardViewModel.kt kotlin package com.goldai.presentation.viewmodel

import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.goldai.data.repository.MarketRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import java.text.SimpleDateFormat import java.util.* import javax.inject.Inject

data class DashboardUiState( val currentPrice: Double = 0.0, val changePercent: String = "0.00", val volume: Double = 0.0, val etfHolding: Double = 0.0, val comexPosition: Long = 0, val londonFlow: Double = 0.0, val signals: List<com.goldai.data.model.Signal> = emptyList(), val lastUpdate: String = "", val isLoading: Boolean = false )

@HiltViewModel class DashboardViewModel @Inject constructor( private val repository: MarketRepository ) : ViewModel() {

private val _uiState = MutableStateFlow(DashboardUiState())
val uiState: StateFlow<DashboardUiState> = _uiState.asStateFlow()

private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())

init {
    viewModelScope.launch {
        repository.tickFlow.collect { tick ->
            _uiState.update { current ->
                val prevClose = current.currentPrice
                val change = if (prevClose > 0) (tick.price - prevClose) / prevClose * 100 else 0.0
                current.copy(
                    currentPrice = tick.price,
                    changePercent = String.format("%.2f", change),
                    volume = tick.volume,
                    lastUpdate = dateFormat.format(Date(tick.timestamp))
                )
            }
        }
    }

    viewModelScope.launch {
        repository.signals.collect { signals ->
            _uiState.update { it.copy(signals = signals) }
        }
    }

    // 初始化数据源(假设已设置token)
    repository.initAllTick("253274119b5cf2ee7cb9a375fdddca17-c-app")
    repository.startAllTick()
}

override fun onCleared() {
    repository.stopAllTick()
    super.onCleared()
}

}

  1. presentation/screens/StrategiesScreen.kt kotlin package com.goldai.presentation.screens

import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.goldai.presentation.components.SignalCard import com.goldai.presentation.viewmodel.StrategiesViewModel

@OptIn(ExperimentalMaterial3Api::class) @Composable fun StrategiesScreen( navController: NavController, viewModel: StrategiesViewModel = hiltViewModel() ) { val uiState by viewModel.uiState.collectAsState()

Scaffold(
    topBar = {
        TopAppBar(
            title = { Text("策略中心") }
        )
    }
) { paddingValues ->
    LazyColumn(
        modifier = Modifier
            .fillMaxSize()
            .padding(paddingValues)
            .padding(horizontal = 16.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        items(uiState.signals) { signal ->
            SignalCard(
                signal = signal,
                onVerifyClick = { /* TODO: 接入AI验证 */ }
            )
        }
    }
}

} 23. presentation/viewmodel/StrategiesViewModel.kt kotlin package com.goldai.presentation.viewmodel

import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.goldai.data.model.Signal import com.goldai.data.repository.MarketRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject

data class StrategiesUiState( val signals: List = emptyList() )

@HiltViewModel class StrategiesViewModel @Inject constructor( private val repository: MarketRepository ) : ViewModel() {

private val _uiState = MutableStateFlow(StrategiesUiState())
val uiState: StateFlow<StrategiesUiState> = _uiState.asStateFlow()

init {
    viewModelScope.launch {
        repository.signals.collect { signals ->
            _uiState.update { it.copy(signals = signals) }
        }
    }
}

} 24. presentation/screens/SimulationScreen.kt kotlin package com.goldai.presentation.screens

import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.goldai.presentation.viewmodel.SimulationViewModel

@OptIn(ExperimentalMaterial3Api::class) @Composable fun SimulationScreen( navController: NavController, viewModel: SimulationViewModel = hiltViewModel() ) { val uiState by viewModel.uiState.collectAsState()

Scaffold(
    topBar = {
        TopAppBar(
            title = { Text("模拟交易") }
        )
    }
) { paddingValues ->
    LazyColumn(
        modifier = Modifier
            .fillMaxSize()
            .padding(paddingValues)
            .padding(horizontal = 16.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        item {
            Card(
                modifier = Modifier.fillMaxWidth(),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
            ) {
                Column(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp)
                ) {
                    Text(
                        text = "用户账号",
                        style = MaterialTheme.typography.titleMedium
                    )
                    Spacer(modifier = Modifier.height(8.dp))
                    Text("余额: $${String.format("%.2f", uiState.balance)}")
                    Text("当前金价: $${String.format("%.2f", uiState.currentPrice)}")
                    // 简单买入按钮(后续扩展)
                    Button(
                        onClick = { /* TODO: 买入逻辑 */ },
                        modifier = Modifier.align(Alignment.End)
                    ) {
                        Text("买入")
                    }
                }
            }
        }
        item {
            Card(
                modifier = Modifier.fillMaxWidth(),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
            ) {
                Column(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp)
                ) {
                    Text(
                        text = "当前持仓",
                        style = MaterialTheme.typography.titleMedium
                    )
                    Spacer(modifier = Modifier.height(8.dp))
                    if (uiState.positions.isEmpty()) {
                        Text("暂无持仓")
                    } else {
                        // 暂不实现持仓列表
                    }
                }
            }
        }
    }
}

} 25. presentation/viewmodel/SimulationViewModel.kt kotlin package com.goldai.presentation.viewmodel

import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.goldai.data.repository.MarketRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject

data class SimulationUiState( val balance: Double = 100000.0, val currentPrice: Double = 0.0, val positions: List = emptyList() )

@HiltViewModel class SimulationViewModel @Inject constructor( private val repository: MarketRepository ) : ViewModel() {

private val _uiState = MutableStateFlow(SimulationUiState())
val uiState: StateFlow<SimulationUiState> = _uiState.asStateFlow()

init {
    viewModelScope.launch {
        repository.tickFlow.collect { tick ->
            _uiState.update { it.copy(currentPrice = tick.price) }
        }
    }
}

} 26. presentation/screens/BacktestScreen.kt kotlin package com.goldai.presentation.screens

import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.navigation.NavController

@OptIn(ExperimentalMaterial3Api::class) @Composable fun BacktestScreen(navController: NavController) { Scaffold( topBar = { TopAppBar( title = { Text("回测") } ) } ) { paddingValues -> Column( modifier = Modifier .fillMaxSize() .padding(paddingValues) .padding(16.dp) ) { Card( modifier = Modifier.fillMaxWidth(), elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) ) { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Text( text = "回测功能", style = MaterialTheme.typography.titleMedium ) Spacer(modifier = Modifier.height(8.dp)) Text("后续版本将提供策略回测功能") } } } } } 27. presentation/screens/AnalysisScreen.kt kotlin package com.goldai.presentation.screens

import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.navigation.NavController

@OptIn(ExperimentalMaterial3Api::class) @Composable fun AnalysisScreen(navController: NavController) { Scaffold( topBar = { TopAppBar( title = { Text("AI分析") } ) } ) { paddingValues -> Column( modifier = Modifier .fillMaxSize() .padding(paddingValues) .padding(16.dp) ) { Card( modifier = Modifier.fillMaxWidth(), elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) ) { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Text( text = "AI市场分析", style = MaterialTheme.typography.titleMedium ) Spacer(modifier = Modifier.height(8.dp)) Text("后续版本将接入DeepSeek AI进行实时分析") Spacer(modifier = Modifier.height(16.dp)) Button(onClick = { /* TODO: 调用AI验证 */ }) { Text("AI验证") } } } } } } 28. presentation/screens/SettingsScreen.kt kotlin package com.goldai.presentation.screens

import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.goldai.presentation.viewmodel.SettingsViewModel

@OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsScreen( navController: NavController, viewModel: SettingsViewModel = hiltViewModel() ) { val uiState by viewModel.uiState.collectAsState()

Scaffold(
    topBar = {
        TopAppBar(
            title = { Text("设置") }
        )
    }
) { paddingValues ->
    LazyColumn(
        modifier = Modifier
            .fillMaxSize()
            .padding(paddingValues)
            .padding(horizontal = 16.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        item {
            Card(
                modifier = Modifier.fillMaxWidth(),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
            ) {
                Column(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp)
                ) {
                    Text(
                        text = "数据源选择",
                        style = MaterialTheme.typography.titleMedium
                    )
                    Spacer(modifier = Modifier.height(8.dp))
                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.spacedBy(8.dp)
                    ) {
                        FilterChip(
                            selected = uiState.dataSource == "alltick",
                            onClick = { viewModel.setDataSource("alltick") },
                            label = { Text("AllTick") }
                        )
                        FilterChip(
                            selected = uiState.dataSource == "itick",
                            onClick = { viewModel.setDataSource("itick") },
                            label = { Text("iTick") }
                        )
                        FilterChip(
                            selected = uiState.dataSource == "mock",
                            onClick = { viewModel.setDataSource("mock") },
                            label = { Text("模拟") }
                        )
                    }
                    Spacer(modifier = Modifier.height(4.dp))
                    Text(
                        text = "当前: ${uiState.dataSource}",
                        style = MaterialTheme.typography.bodySmall,
                        color = MaterialTheme.colorScheme.primary
                    )
                }
            }
        }

        item {
            Card(
                modifier = Modifier.fillMaxWidth(),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
            ) {
                Column(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp)
                ) {
                    Text(
                        text = "AllTick配置",
                        style = MaterialTheme.typography.titleMedium
                    )
                    Spacer(modifier = Modifier.height(8.dp))
                    OutlinedTextField(
                        value = uiState.alltickToken,
                        onValueChange = { viewModel.setAlltickToken(it) },
                        label = { Text("API密钥") },
                        singleLine = true,
                        modifier = Modifier.fillMaxWidth()
                    )
                }
            }
        }

        item {
            Card(
                modifier = Modifier.fillMaxWidth(),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
            ) {
                Column(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp)
                ) {
                    Text(
                        text = "iTick配置",
                        style = MaterialTheme.typography.titleMedium
                    )
                    Spacer(modifier = Modifier.height(8.dp))
                    OutlinedTextField(
                        value = uiState.itickToken,
                        onValueChange = { viewModel.setItickToken(it) },
                        label = { Text("API密钥") },
                        singleLine = true,
                        modifier = Modifier.fillMaxWidth()
                    )
                    Spacer(modifier = Modifier.height(4.dp))
                    Text(
                        text = "免费套餐有效期至2026-03-17,到期前请续期",
                        style = MaterialTheme.typography.bodySmall,
                        color = MaterialTheme.colorScheme.error
                    )
                }
            }
        }

        item {
            Card(
                modifier = Modifier.fillMaxWidth(),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
            ) {
                Column(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp)
                ) {
                    Text(
                        text = "交易方式",
                        style = MaterialTheme.typography.titleMedium
                    )
                    Spacer(modifier = Modifier.height(8.dp))
                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.spacedBy(8.dp)
                    ) {
                        FilterChip(
                            selected = uiState.tradeMode == "long_only",
                            onClick = { viewModel.setTradeMode("long_only") },
                            label = { Text("只做多") }
                        )
                        FilterChip(
                            selected = uiState.tradeMode == "short_only",
                            onClick = { viewModel.setTradeMode("short_only") },
                            label = { Text("只做空") }
                        )
                        FilterChip(
                            selected = uiState.tradeMode == "both",
                            onClick = { viewModel.setTradeMode("both") },
                            label = { Text("多空都做") }
                        )
                    }
                }
            }
        }

        item {
            Card(
                modifier = Modifier.fillMaxWidth(),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
            ) {
                Column(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp)
                ) {
                    Text(
                        text = "外观设置",
                        style = MaterialTheme.typography.titleMedium
                    )
                    Spacer(modifier = Modifier.height(8.dp))
                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.SpaceBetween,
                        verticalAlignment = Alignment.CenterVertically
                    ) {
                        Text("深色模式")
                        Switch(
                            checked = uiState.darkTheme,
                            onCheckedChange = { viewModel.setDarkTheme(it) }
                        )
                    }
                }
            }
        }

        item {
            Card(
                modifier = Modifier.fillMaxWidth(),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
            ) {
                Column(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp)
                ) {
                    Text(
                        text = "关于",
                        style = MaterialTheme.typography.titleMedium
                    )
                    Spacer(modifier = Modifier.height(8.dp))
                    Text("黄金AI交易系统 v2.0")
                    Text("数据源: AllTick / iTick / 模拟")
                }
            }
        }
    }
}

} 29. presentation/viewmodel/SettingsViewModel.kt kotlin package com.goldai.presentation.viewmodel

import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.goldai.data.repository.MarketRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject

data class SettingsUiState( val dataSource: String = "alltick", val alltickToken: String = "253274119b5cf2ee7cb9a375fdddca17-c-app", val itickToken: String = "da9087a07eb644e9b65ae81d2bb04673c88d8b7ac63f4477b830cfc43f68f533", val tradeMode: String = "long_only", val darkTheme: Boolean = false )

@HiltViewModel class SettingsViewModel @Inject constructor( private val repository: MarketRepository ) : ViewModel() {

private val _uiState = MutableStateFlow(SettingsUiState())
val uiState: StateFlow<SettingsUiState> = _uiState.asStateFlow()

fun setDataSource(source: String) {
    _uiState.update { it.copy(dataSource = source) }
    // 通知仓库切换数据源(需在仓库中实现切换逻辑)
    // 简化起见,先不实现
}

fun setAlltickToken(token: String) {
    _uiState.update { it.copy(alltickToken = token) }
}

fun setItickToken(token: String) {
    _uiState.update { it.copy(itickToken = token) }
}

fun setTradeMode(mode: String) {
    _uiState.update { it.copy(tradeMode = mode) }
}

fun setDarkTheme(enabled: Boolean) {
    _uiState.update { it.copy(darkTheme = enabled) }
}

} 30. utils/Constants.kt kotlin package com.goldai.utils

object Constants { const val ALLTICK_WS_URL = "wss://quote.alltick.io/quote-b-ws-api" const val DEFAULT_ALLTICK_TOKEN = "253274119b5cf2ee7cb9a375fdddca17-c-app" const val DEFAULT_ITICK_TOKEN = "da9087a07eb644e9b65ae81d2bb04673c88d8b7ac63f4477b830cfc43f68f533" const val ITICK_BASE_URL = "api.itick.org/" } 31. utils/Extensions.kt kotlin package com.goldai.utils

import java.text.SimpleDateFormat import java.util.*

fun Long.formatTime(pattern: String = "yyyy-MM-dd HH:mm:ss"): String { val date = Date(this) val format = SimpleDateFormat(pattern, Locale.getDefault()) return format.format(date) }

fun Double.format(digits: Int = 2): String = String.format("%.${digits}f", this) 32. res/values/colors.xml xml

#FFBB86FC #FF6200EE #FF3700B3 #FF03DAC5 #FF018786 #FF000000 #FFFFFFFF 33. res/values/strings.xml xml 黄金AI交易系统 34. res/values/themes.xml xml <item name="android:statusBarColor">@color/purple_700</item>