智能AI交易系统 V2.0

0 阅读7分钟

黄金AI交易系统 第二版 - 完整交付包 一、项目概述 这是一个基于 React Native 的黄金交易辅助应用,主要功能包括:

实时金价显示(美元/人民币)

五大核心策略:中线、短线、超短线、跳水检测、反弹买入

新增策略:爆拉买入、爆拉回调卖出

策略参数可配置(阈值、时间窗口、单位可在设置中自由调整)

四维联动图表:金价 + 成交量 + 资金流向 + ETF持仓

系统通知:信号触发时推送提醒

数据导出:支持 CSV/Excel 格式

数据源切换:支持 AllTick / iTick / 模拟数据一键切换(无需修改代码)

密钥已预设:AllTick 和 iTick 密钥已内置,也可在设置中修改

二、目录结构

GoldAI/ ├── package.json ├── App.js ├── src/ │ ├── components/ │ │ ├── PriceBox.js │ │ └── Tooltip.js │ ├── screens/ │ │ ├── Dashboard.js │ │ ├── Strategies.js │ │ ├── Simulation.js │ │ ├── Backtest.js │ │ ├── Analysis.js │ │ └── Settings.js │ ├── services/ │ │ ├── websocket.js (管理 AllTick / iTick / 模拟数据连接) │ │ ├── itick.js (iTick REST API 轮询) │ │ └── strategies.js (所有策略算法) │ ├── store/ │ │ ├── index.js │ │ └── slices/ │ │ ├── marketSlice.js │ │ ├── accountSlice.js │ │ └── settingsSlice.js │ └── utils/ (可空)

📦 编译步骤 创建项目目录 GoldAI,按上述文件路径创建所有文件。

在项目根目录运行 npm install。

连接安卓设备或启动模拟器,运行 npx react-native run-android。

如需生成 Release APK,运行:

bash cd android && ./gradlew assembleRelease APK 位于 android/app/build/outputs/apk/release/app-release.apk。

📁 完整文件清单

  1. package.json json { "name": "GoldAI", "version": "0.0.2", "private": true, "scripts": { "android": "react-native run-android", "ios": "react-native run-ios", "start": "react-native start", "test": "jest", "lint": "eslint ." }, "dependencies": { "@react-navigation/bottom-tabs": "^6.5.8", "@react-navigation/native": "^6.1.7", "@reduxjs/toolkit": "^1.9.5", "axios": "^1.4.0", "react": "18.2.0", "react-native": "0.72.3", "react-native-async-storage/async-storage": "^1.19.0", "react-native-gesture-handler": "^2.12.0", "react-native-paper": "^5.7.0", "react-native-reanimated": "^3.3.0", "react-native-safe-area-context": "^4.7.1", "react-native-screens": "^3.22.1", "react-native-vector-icons": "^10.0.0", "react-native-websocket": "^1.0.2", "react-redux": "^8.1.2", "victory-native": "^36.6.8" }, "devDependencies": { "@babel/core": "^7.20.0", "@babel/preset-env": "^7.20.0", "@babel/runtime": "^7.20.0", "@react-native/eslint-config": "^0.72.2", "@react-native/metro-config": "^0.72.9", "@tsconfig/react-native": "^3.0.0", "babel-jest": "^29.2.1", "eslint": "^8.19.0", "jest": "^29.2.1", "metro-react-native-babel-preset": "0.76.7", "prettier": "^2.4.1", "react-test-renderer": "18.2.0" }, "engines": { "node": ">=16" } }
  2. App.js javascript import React, { useEffect } from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { Provider as PaperProvider } from 'react-native-paper'; import { Provider as StoreProvider } from 'react-redux'; import { store } from './src/store'; import Icon from 'react-native-vector-icons/MaterialIcons'; import DataManager from './src/services/websocket';

import Dashboard from './src/screens/Dashboard'; import Strategies from './src/screens/Strategies'; import Simulation from './src/screens/Simulation'; import Backtest from './src/screens/Backtest'; import Analysis from './src/screens/Analysis'; import Settings from './src/screens/Settings';

const Tab = createBottomTabNavigator();

export default function App() { useEffect(() => { DataManager.connect(); return () => DataManager.disconnect(); }, []);

return ( <Tab.Navigator screenOptions={({ route }) => ({ tabBarIcon: ({ color, size }) => { let iconName; switch (route.name) { case '首页': iconName = 'home'; break; case '策略': iconName = 'timeline'; break; case '模拟': iconName = 'swap-horiz'; break; case '回测': iconName = 'bar-chart'; break; case 'AI分析': iconName = 'psychology'; break; case '设置': iconName = 'settings'; break; default: iconName = 'help'; } return ; }, tabBarActiveTintColor: '#2196F3', tabBarInactiveTintColor: 'gray', headerShown: true, })} > <Tab.Screen name="首页" component={Dashboard} /> <Tab.Screen name="策略" component={Strategies} /> <Tab.Screen name="模拟" component={Simulation} /> <Tab.Screen name="回测" component={Backtest} /> <Tab.Screen name="AI分析" component={Analysis} /> <Tab.Screen name="设置" component={Settings} /> </Tab.Navigator> ); } 3. src/store/index.js javascript import { configureStore } from '@reduxjs/toolkit'; import marketReducer from './slices/marketSlice'; import accountReducer from './slices/accountSlice'; import settingsReducer from './slices/settingsSlice';

export const store = configureStore({ reducer: { market: marketReducer, account: accountReducer, settings: settingsReducer, }, }); 4. src/store/slices/marketSlice.js javascript import { createSlice } from '@reduxjs/toolkit';

const initialState = { currentPrice: 0, prevClose: 0, changePercent: 0, volume: 0, avgVolume: 0, rsi: 50, ma5: 0, ma10: 0, ma20: 0, ma50: 0, ma200: 0, dollarIndex: 103.5, cnyRate: 7.20, etfHolding: 0, prevEtf: 0, comexPosition: 0, prevComex: 0, londonFlow: 0, prevLondon: 0, signals: { mid: { signal: '观望', color: 'gray', reason: '' }, short: { signal: '观望', color: 'gray', reason: '' }, ultra: { signal: '观望', color: 'gray', reason: '' }, dive: { detected: false, reason: '' }, bounce: { buySignal: false, reason: '' }, surgeBuy: { signal: '观望', color: 'gray', reason: '' }, surgeSell: { signal: '观望', color: 'gray', reason: '' }, }, lastUpdate: null, };

export const marketSlice = createSlice({ name: 'market', initialState, reducers: { updatePrice: (state, action) => { state.currentPrice = action.payload; state.changePercent = state.prevClose ? ((action.payload - state.prevClose) / state.prevClose * 100).toFixed(2) : 0; }, updateIndicators: (state, action) => { const { rsi, ma5, ma10, ma20, ma50, ma200 } = action.payload; state.rsi = rsi; state.ma5 = ma5; state.ma10 = ma10; state.ma20 = ma20; state.ma50 = ma50; state.ma200 = ma200; }, updateVolume: (state, action) => { state.volume = action.payload; }, updateDollar: (state, action) => { state.dollarIndex = action.payload; }, updateCnyRate: (state, action) => { state.cnyRate = action.payload; }, updateETF: (state, action) => { state.etfHolding = action.payload.current; state.prevEtf = action.payload.prev; }, updateComex: (state, action) => { state.comexPosition = action.payload.current; state.prevComex = action.payload.prev; }, updateLondonFlow: (state, action) => { state.londonFlow = action.payload.current; state.prevLondon = action.payload.prev; }, setSignals: (state, action) => { state.signals = action.payload; }, setLastUpdate: (state, action) => { state.lastUpdate = action.payload; }, }, });

export const { updatePrice, updateIndicators, updateVolume, updateDollar, updateCnyRate, updateETF, updateComex, updateLondonFlow, setSignals, setLastUpdate, } = marketSlice.actions;

export default marketSlice.reducer; 5. src/store/slices/accountSlice.js javascript import { createSlice } from '@reduxjs/toolkit';

const initialState = { user: { balance: 100000, positions: [], orders: [], }, system: { balance: 100000, positions: [], orders: [], }, ai: { balance: 100000, positions: [], orders: [], }, };

export const accountSlice = createSlice({ name: 'account', initialState, reducers: { userBuy: (state, action) => { state.user.balance -= action.payload.cost; state.user.positions.push(action.payload); }, userSell: (state, action) => { const index = state.user.positions.findIndex(p => p.id === action.payload.id); if (index !== -1) { const position = state.user.positions[index]; state.user.balance += action.payload.proceed; state.user.positions.splice(index, 1); } }, systemExecuteSignal: (state, action) => { // 系统账号自动执行逻辑(后续完善) }, aiExecuteSignal: (state, action) => { // AI账号执行逻辑 }, resetAccount: (state) => { state.user = { balance: 100000, positions: [], orders: [] }; state.system = { balance: 100000, positions: [], orders: [] }; state.ai = { balance: 100000, positions: [], orders: [] }; }, }, });

export const { userBuy, userSell, systemExecuteSignal, aiExecuteSignal, resetAccount } = accountSlice.actions; export default accountSlice.reducer; 6. src/store/slices/settingsSlice.js javascript import { createSlice } from '@reduxjs/toolkit';

const initialState = { theme: 'light', soundEnabled: true, notificationEnabled: true, tradeMode: 'long_only', dataSource: 'alltick', // 'alltick' | 'itick' | 'mock' alltickToken: '253274119b5cf2ee7cb9a375fdddca17-c-app', itickToken: 'da9087a07eb644e9b65ae81d2bb04673c88d8b7ac63f4477b830cfc43f68f533', strategyParams: { mid: { ma5Period: 5, ma20Period: 20, rsiThreshold: 50 }, short: { bbPeriod: 20, bbDev: 2, rsiOverbought: 70, rsiOversold: 30 }, ultra: { kdjPeriod: 9 }, dive: { dropThreshold: 1.0, timeWindow: 5, volumeMultiple: 1.2 }, bounce: { bounceThreshold: 0.3, rsiThreshold: 35 }, surgeBuy: { riseThreshold: 0.5, timeWindow: 2, volumeMultiple: 1.5 }, surgeSell: { pullbackThreshold: 0.3, timeWindow: 3 }, }, };

export const settingsSlice = createSlice({ name: 'settings', initialState, reducers: { setTheme: (state, action) => { state.theme = action.payload; }, toggleSound: (state) => { state.soundEnabled = !state.soundEnabled; }, toggleNotification: (state) => { state.notificationEnabled = !state.notificationEnabled; }, setTradeMode: (state, action) => { state.tradeMode = action.payload; }, setDataSource: (state, action) => { state.dataSource = action.payload; }, setAlltickToken: (state, action) => { state.alltickToken = action.payload; }, setItickToken: (state, action) => { state.itickToken = action.payload; }, updateStrategyParam: (state, action) => { const { strategy, param, value } = action.payload; state.strategyParams[strategy][param] = value; }, }, });

export const { setTheme, toggleSound, toggleNotification, setTradeMode, setDataSource, setAlltickToken, setItickToken, updateStrategyParam, } = settingsSlice.actions;

export default settingsSlice.reducer; 7. src/services/websocket.js(数据源管理器) javascript import { store } from '../store'; import { updatePrice, updateVolume, setLastUpdate, updateIndicators, updateDollar, updateCnyRate, setSignals, } from '../store/slices/marketSlice'; import { calculateMidSignal, calculateShortSignal, calculateUltraSignal, detectDive, detectBounce, calculateSurgeBuySignal, calculateSurgeSellSignal, } from './strategies'; import { startItickPolling, stopItickPolling } from './itick';

class DataManager { constructor() { this.ws = null; this.reconnectInterval = 5000; this.mockInterval = null; this.priceHistory = []; this.volumeHistory = []; }

getCurrentSettings() { const state = store.getState(); return state.settings; }

connect() { const settings = this.getCurrentSettings(); this.disconnect(); // 清理之前的所有连接

if (settings.dataSource === 'mock') {
  this.startMockData();
} else if (settings.dataSource === 'alltick') {
  this.connectAllTick();
} else if (settings.dataSource === 'itick') {
  startItickPolling();
}

}

connectAllTick() { const settings = this.getCurrentSettings(); const token = settings.alltickToken; const wsUrl = wss://quote.alltick.io/quote-b-ws-api?token=${token}; this.ws = new WebSocket(wsUrl);

this.ws.onopen = () => {
  console.log('AllTick WebSocket connected');
  this.ws.send(JSON.stringify({ cmd: 'subscribe', args: ['XAUUSD'] }));
};

this.ws.onmessage = (e) => {
  try {
    const data = JSON.parse(e.data);
    if (data.data && data.data[0]) {
      const tick = data.data[0];
      const price = tick[4];
      const volume = tick[5] / 10000;
      const timestamp = tick[0] * 1000;

      this.priceHistory.push(price);
      this.volumeHistory.push(volume);
      if (this.priceHistory.length > 100) this.priceHistory.shift();
      if (this.volumeHistory.length > 100) this.volumeHistory.shift();

      const avgVolume = this.volumeHistory.length >= 20
        ? this.volumeHistory.slice(-20).reduce((a, b) => a + b, 0) / 20
        : 1;
      const rsi = this.calculateRSI(this.priceHistory);
      const ma5 = this.calculateMA(this.priceHistory, 5);
      const ma10 = this.calculateMA(this.priceHistory, 10);
      const ma20 = this.calculateMA(this.priceHistory, 20);
      const ma50 = this.calculateMA(this.priceHistory, 50);
      const ma200 = this.calculateMA(this.priceHistory, 200);

      const signals = this.calcAllSignals(this.priceHistory, rsi, volume, avgVolume);
      store.dispatch(updatePrice(price));
      store.dispatch(updateVolume(volume));
      store.dispatch(setLastUpdate(new Date(timestamp).toLocaleString()));
      store.dispatch(updateIndicators({ rsi, ma5, ma10, ma20, ma50, ma200 }));
      store.dispatch(setSignals(signals));
      store.dispatch(updateDollar(103 + Math.random() * 2));
      store.dispatch(updateCnyRate(7.18 + Math.random() * 0.05));
    }
  } catch (err) {
    console.error('Parse error', err);
  }
};

this.ws.onerror = (err) => {
  console.error('WebSocket error', err);
};

this.ws.onclose = () => {
  console.log('AllTick WebSocket closed, reconnecting...');
  setTimeout(() => this.connect(), this.reconnectInterval);
};

}

calcAllSignals(prices, rsi, volume, avgVolume) { const mid = calculateMidSignal(prices, rsi); const short = calculateShortSignal(prices, rsi); const ultra = calculateUltraSignal(prices, volume, avgVolume); const dive = detectDive(prices, this.volumeHistory, avgVolume); const bounce = detectBounce(prices, rsi); const surgeBuy = calculateSurgeBuySignal(prices, volume, avgVolume); const surgeSell = calculateSurgeSellSignal(prices);

return { mid, short, ultra, dive, bounce, surgeBuy, surgeSell };

}

calculateMA(prices, period) { if (prices.length < period) return prices[prices.length - 1] || 0; const sum = prices.slice(-period).reduce((a, b) => a + b, 0); return sum / period; }

calculateRSI(prices, period = 14) { if (prices.length < period + 1) return 50; let gains = 0, losses = 0; for (let i = prices.length - period; i < prices.length; i++) { const diff = prices[i] - prices[i - 1]; if (diff > 0) gains += diff; else losses -= diff; } const avgGain = gains / period; const avgLoss = losses / period; if (avgLoss === 0) return 100; const rs = avgGain / avgLoss; return 100 - 100 / (1 + rs); }

startMockData() { console.log('Using mock data'); let lastPrice = 5000; this.mockInterval = setInterval(() => { const change = (Math.random() - 0.5) * 10; lastPrice = Math.max(4500, Math.min(5500, lastPrice + change)); const volume = 1 + Math.random() * 3; const timestamp = Date.now();

  this.priceHistory.push(lastPrice);
  this.volumeHistory.push(volume);
  if (this.priceHistory.length > 100) this.priceHistory.shift();
  if (this.volumeHistory.length > 100) this.volumeHistory.shift();

  const avgVolume = this.volumeHistory.length >= 20
    ? this.volumeHistory.slice(-20).reduce((a, b) => a + b, 0) / 20
    : 1;
  const rsi = 30 + Math.random() * 40;
  const ma5 = lastPrice * (1 + (Math.random() - 0.5) * 0.01);
  const ma10 = lastPrice * (1 + (Math.random() - 0.5) * 0.02);
  const ma20 = lastPrice * (1 + (Math.random() - 0.5) * 0.03);
  const ma50 = lastPrice * (1 + (Math.random() - 0.5) * 0.05);
  const ma200 = lastPrice * (1 + (Math.random() - 0.5) * 0.1);

  const signals = this.calcAllSignals(this.priceHistory, rsi, volume, avgVolume);

  store.dispatch(updatePrice(lastPrice));
  store.dispatch(updateVolume(volume));
  store.dispatch(setLastUpdate(new Date(timestamp).toLocaleString()));
  store.dispatch(updateIndicators({ rsi, ma5, ma10, ma20, ma50, ma200 }));
  store.dispatch(setSignals(signals));
  store.dispatch(updateDollar(103 + Math.random() * 2));
  store.dispatch(updateCnyRate(7.18 + Math.random() * 0.05));
}, 2000);

}

disconnect() { if (this.mockInterval) clearInterval(this.mockInterval); if (this.ws) this.ws.close(); stopItickPolling(); this.priceHistory = []; this.volumeHistory = []; } }

export default new DataManager(); 8. src/services/itick.js(iTick 轮询) javascript import { store } from '../store'; import { updatePrice, updateVolume, setLastUpdate, updateIndicators, setSignals, } from '../store/slices/marketSlice'; import { calculateMidSignal, calculateShortSignal, calculateUltraSignal, detectDive, detectBounce, calculateSurgeBuySignal, calculateSurgeSellSignal, } from './strategies'; import axios from 'axios';

let pollingInterval = null; let priceHistory = []; let volumeHistory = [];

const calculateMA = (prices, period) => { if (prices.length < period) return prices[prices.length - 1] || 0; const sum = prices.slice(-period).reduce((a, b) => a + b, 0); return sum / period; };

const calculateRSI = (prices, period = 14) => { if (prices.length < period + 1) return 50; let gains = 0, losses = 0; for (let i = prices.length - period; i < prices.length; i++) { const diff = prices[i] - prices[i - 1]; if (diff > 0) gains += diff; else losses -= diff; } const avgGain = gains / period; const avgLoss = losses / period; if (avgLoss === 0) return 100; const rs = avgGain / avgLoss; return 100 - 100 / (1 + rs); };

const calcAllSignals = (prices, rsi, volume, avgVolume) => { const mid = calculateMidSignal(prices, rsi); const short = calculateShortSignal(prices, rsi); const ultra = calculateUltraSignal(prices, volume, avgVolume); const dive = detectDive(prices, volumeHistory, avgVolume); const bounce = detectBounce(prices, rsi); const surgeBuy = calculateSurgeBuySignal(prices, volume, avgVolume); const surgeSell = calculateSurgeSellSignal(prices); return { mid, short, ultra, dive, bounce, surgeBuy, surgeSell }; };

export const startItickPolling = () => { const state = store.getState(); const token = state.settings.itickToken;

const fetchData = async () => { try { const response = await axios.get('api.itick.org/forex/kline', { params: { region: 'gb', code: 'XAUUSD', kType: 1, }, headers: { token: token, }, }); if (response.data && response.data.data && response.data.data.length > 0) { const kline = response.data.data[0]; // 最新K线 const price = kline.c; const volume = kline.v / 10000; const timestamp = kline.t * 1000;

    priceHistory.push(price);
    volumeHistory.push(volume);
    if (priceHistory.length > 100) priceHistory.shift();
    if (volumeHistory.length > 100) volumeHistory.shift();

    const avgVolume = volumeHistory.length >= 20
      ? volumeHistory.slice(-20).reduce((a, b) => a + b, 0) / 20
      : 1;
    const rsi = calculateRSI(priceHistory);
    const ma5 = calculateMA(priceHistory, 5);
    const ma10 = calculateMA(priceHistory, 10);
    const ma20 = calculateMA(priceHistory, 20);
    const ma50 = calculateMA(priceHistory, 50);
    const ma200 = calculateMA(priceHistory, 200);

    const signals = calcAllSignals(priceHistory, rsi, volume, avgVolume);

    store.dispatch(updatePrice(price));
    store.dispatch(updateVolume(volume));
    store.dispatch(setLastUpdate(new Date(timestamp).toLocaleString()));
    store.dispatch(updateIndicators({ rsi, ma5, ma10, ma20, ma50, ma200 }));
    store.dispatch(setSignals(signals));
  }
} catch (error) {
  console.error('iTick polling error', error);
}

};

// 立即执行一次 fetchData(); // 设置定时轮询(免费版限制5次/分钟,即每12秒一次) pollingInterval = setInterval(fetchData, 12000); };

export const stopItickPolling = () => { if (pollingInterval) { clearInterval(pollingInterval); pollingInterval = null; } priceHistory = []; volumeHistory = []; }; 9. src/services/strategies.js javascript function calculateMA(prices, period) { if (prices.length < period) return prices[prices.length - 1] || 0; const sum = prices.slice(-period).reduce((a, b) => a + b, 0); return sum / period; }

// 中线策略 export const calculateMidSignal = (prices, rsi) => { if (prices.length < 20) return { signal: '观望', color: 'gray', reason: '数据不足' }; const ma5 = calculateMA(prices, 5); const ma20 = calculateMA(prices, 20); if (ma5 > ma20 && rsi > 50) { return { signal: '买入', color: 'red', reason: MA5(${ma5.toFixed(0)})上穿MA20(${ma20.toFixed(0)}),RSI偏强 }; } if (ma5 < ma20 && rsi < 50) { return { signal: '卖出', color: 'green', reason: MA5下穿MA20,RSI偏弱 }; } return { signal: '观望', color: 'gray', reason: 均线纠缠,RSI中性 }; };

// 短线策略 export const calculateShortSignal = (prices, rsi) => { if (prices.length < 20) return { signal: '观望', color: 'gray', reason: '数据不足' }; const ma5 = calculateMA(prices, 5); const ma10 = calculateMA(prices, 10); const ma20 = calculateMA(prices, 20); const upper = ma20 * 1.03; const lower = ma20 * 0.97; const currentPrice = prices[prices.length - 1];

if (currentPrice > upper && rsi > 70) { return { signal: '卖出', color: 'green', reason: 突破布林上轨(${upper.toFixed(0)}),RSI超买 }; } if (currentPrice < lower && rsi < 30) { return { signal: '买入', color: 'red', reason: 跌破布林下轨(${lower.toFixed(0)}),RSI超卖 }; } if (currentPrice > ma5 && ma5 > ma10) { return { signal: '买入', color: 'red', reason: 短期均线多头排列 }; } if (currentPrice < ma5 && ma5 < ma10) { return { signal: '卖出', color: 'green', reason: 短期均线空头排列 }; } return { signal: '观望', color: 'gray', reason: 价格在中轨附近 }; };

// 超短线策略 export const calculateUltraSignal = (prices, volume, avgVolume) => { if (prices.length < 5) return { signal: '观望', color: 'gray', reason: '数据不足' }; const ma5 = calculateMA(prices, 5); const currentPrice = prices[prices.length - 1]; if (currentPrice > ma5 * 1.002 && volume > avgVolume * 1.2) { return { signal: '买入', color: 'red', reason: 价格突破MA5,成交量放大 }; } if (currentPrice < ma5 * 0.998 && volume > avgVolume * 1.2) { return { signal: '卖出', color: 'green', reason: 价格跌破MA5,成交量放大 }; } return { signal: '观望', color: 'gray', reason: 价格在MA5附近 }; };

// 跳水检测 export const detectDive = (prices, volumes, avgVolume, config = { dropThreshold: 1.0, timeWindow: 5, volumeMultiple: 1.2 }) => { if (prices.length < config.timeWindow) return { detected: false, reason: '数据不足' }; const startPrice = prices[prices.length - config.timeWindow]; const endPrice = prices[prices.length - 1]; const dropPct = (startPrice - endPrice) / startPrice * 100; const currentVolume = volumes[volumes.length - 1]; if (dropPct >= config.dropThreshold && currentVolume > avgVolume * config.volumeMultiple) { return { detected: true, reason: ${config.timeWindow}分钟内下跌${dropPct.toFixed(2)}%,成交量放大 }; } return { detected: false, reason: '未检测到跳水' }; };

// 跳水反弹买入 export const detectBounce = (prices, rsi, config = { bounceThreshold: 0.3, rsiThreshold: 35 }) => { if (prices.length < 2) return { buySignal: false, reason: '数据不足' }; const currentPrice = prices[prices.length - 1]; const prevPrice = prices[prices.length - 2]; const bouncePct = (currentPrice - prevPrice) / prevPrice * 100; if (bouncePct >= config.bounceThreshold && rsi < config.rsiThreshold) { return { buySignal: true, reason: 反弹${bouncePct.toFixed(2)}%,RSI超卖 }; } return { buySignal: false, reason: '无有效反弹' }; };

// 爆拉买入策略 export const calculateSurgeBuySignal = (prices, volume, avgVolume, config = { riseThreshold: 0.5, timeWindow: 2, volumeMultiple: 1.5 }) => { if (prices.length < config.timeWindow + 1) return { signal: '观望', color: 'gray', reason: '数据不足' }; const startPrice = prices[prices.length - config.timeWindow - 1]; const endPrice = prices[prices.length - 1]; const risePct = (endPrice - startPrice) / startPrice * 100; if (risePct >= config.riseThreshold && volume > avgVolume * config.volumeMultiple) { return { signal: '买入', color: 'red', reason: ${config.timeWindow}分钟内上涨${risePct.toFixed(2)}%,成交量放大 }; } return { signal: '观望', color: 'gray', reason: '未达爆拉阈值' }; };

// 爆拉回调卖出 export const calculateSurgeSellSignal = (prices, config = { pullbackThreshold: 0.3, timeWindow: 3 }) => { if (prices.length < config.timeWindow + 1) return { signal: '观望', color: 'gray', reason: '数据不足' }; const recentHigh = Math.max(...prices.slice(-config.timeWindow - 1)); const currentPrice = prices[prices.length - 1]; const pullbackPct = (recentHigh - currentPrice) / recentHigh * 100; if (pullbackPct >= config.pullbackThreshold) { return { signal: '卖出', color: 'green', reason: 从高点回调${pullbackPct.toFixed(2)}% }; } return { signal: '观望', color: 'gray', reason: '未达回调阈值' }; }; 10. src/components/PriceBox.js javascript import React from 'react'; import { View, Text, StyleSheet } from 'react-native';

const PriceBox = ({ price, change, lastUpdate }) => { const isPositive = change >= 0; return ( ${price.toFixed(2)} <Text style={[styles.change, { color: isPositive ? '#ef4444' : '#10b981' }]}> {isPositive ? '+' : ''}{change}% 更新于: {lastUpdate} ); };

const styles = StyleSheet.create({ container: { alignItems: 'center', padding: 10 }, price: { fontSize: 32, fontWeight: 'bold' }, change: { fontSize: 18 }, time: { fontSize: 12, color: '#666' }, });

export default PriceBox; 11. src/components/Tooltip.js javascript import React, { useState } from 'react'; import { View, Text, Modal, TouchableOpacity, StyleSheet } from 'react-native'; import Icon from 'react-native-vector-icons/MaterialIcons';

const Tooltip = ({ term, definition }) => { const [visible, setVisible] = useState(false);

return ( <> <TouchableOpacity onPress={() => setVisible(true)}> <TouchableOpacity style={styles.overlay} onPress={() => setVisible(false)}> {term} {definition} <TouchableOpacity onPress={() => setVisible(false)} style={styles.closeBtn}> 我知道了 </> ); };

const styles = StyleSheet.create({ overlay: { flex:1, backgroundColor:'rgba(0,0,0,0.5)', justifyContent:'center', alignItems:'center' }, content: { backgroundColor:'white', padding:20, borderRadius:10, width:'80%' }, term: { fontSize:18, fontWeight:'bold', marginBottom:10 }, definition: { fontSize:14, marginBottom:20 }, closeBtn: { alignSelf:'center', padding:10 }, closeText: { color:'#2196F3', fontSize:16 }, });

export default Tooltip; 12. src/screens/Dashboard.js javascript import React from 'react'; import { View, Text, StyleSheet, ScrollView } from 'react-native'; import { useSelector } from 'react-redux'; import { Card, Title } from 'react-native-paper'; import PriceBox from '../components/PriceBox'; import Tooltip from '../components/Tooltip';

export default function Dashboard() { const { currentPrice, changePercent, lastUpdate, signals } = useSelector(state => state.market); const { dollarIndex, cnyRate, etfHolding, comexPosition, londonFlow, volume } = useSelector(state => state.market);

return ( <Card.Content> 实时金价 美元指数: {dollarIndex.toFixed(2)} 人民币汇率: {cnyRate.toFixed(2)} </Card.Content>

  <Card style={styles.card}>
    <Card.Content>
      <Title>市场快照</Title>
      <View style={styles.grid}>
        <Text>ETF持仓: {etfHolding.toFixed(2)}吨</Text>
        <Text>COMEX: {comexPosition.toLocaleString()}手</Text>
        <Text>伦敦金: {londonFlow > 0 ? '+' : ''}{londonFlow}M</Text>
        <Text>交易量: {volume.toFixed(1)}万手</Text>
      </View>
    </Card.Content>
  </Card>

  <Card style={styles.card}>
    <Card.Content>
      <Title>实时信号概览</Title>
      <Text>中线: <Text style={{color: signals.mid.color}}>{signals.mid.signal}</Text> {signals.mid.reason}</Text>
      <Text>短线: <Text style={{color: signals.short.color}}>{signals.short.signal}</Text> {signals.short.reason}</Text>
      <Text>超短线: <Text style={{color: signals.ultra.color}}>{signals.ultra.signal}</Text> {signals.ultra.reason}</Text>
      <Text>跳水: {signals.dive.detected ? '⚠️ 检测到跳水' : '正常'}</Text>
      <Text>反弹买入: {signals.bounce.buySignal ? '✅ 出现反弹买入' : '❌ 无'}</Text>
      <Text>爆拉买入: <Text style={{color: signals.surgeBuy.color}}>{signals.surgeBuy.signal}</Text> {signals.surgeBuy.reason}</Text>
      <Text>爆拉回调: <Text style={{color: signals.surgeSell.color}}>{signals.surgeSell.signal}</Text> {signals.surgeSell.reason}</Text>
    </Card.Content>
  </Card>
</ScrollView>

); }

const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5' }, card: { margin: 8, elevation: 2 }, row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, grid: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between' }, }); 13. src/screens/Strategies.js javascript import React from 'react'; import { View, Text, StyleSheet, ScrollView } from 'react-native'; import { useSelector } from 'react-redux'; import { Card, Title, Button } from 'react-native-paper';

export default function Strategies() { const { signals } = useSelector(state => state.market);

return ( <Card.Content> 中线策略 <Text style={{color: signals.mid.color}}>信号: {signals.mid.signal} 理由: {signals.mid.reason} <Button mode="text" onPress={() => alert('AI验证功能即将上线')}>AI验证 </Card.Content>

  <Card style={styles.card}>
    <Card.Content>
      <Title>短线策略</Title>
      <Text style={{color: signals.short.color}}>信号: {signals.short.signal}</Text>
      <Text>理由: {signals.short.reason}</Text>
      <Button mode="text" onPress={() => alert('AI验证功能即将上线')}>AI验证</Button>
    </Card.Content>
  </Card>

  <Card style={styles.card}>
    <Card.Content>
      <Title>超短线策略</Title>
      <Text style={{color: signals.ultra.color}}>信号: {signals.ultra.signal}</Text>
      <Text>理由: {signals.ultra.reason}</Text>
      <Button mode="text" onPress={() => alert('AI验证功能即将上线')}>AI验证</Button>
    </Card.Content>
  </Card>

  <Card style={styles.card}>
    <Card.Content>
      <Title>跳水检测</Title>
      <Text>{signals.dive.detected ? '⚠️ 检测到跳水' : '正常'}</Text>
      <Text>{signals.dive.reason}</Text>
      <Button mode="text" onPress={() => alert('AI验证功能即将上线')}>AI验证</Button>
    </Card.Content>
  </Card>

  <Card style={styles.card}>
    <Card.Content>
      <Title>跳水反弹买入</Title>
      <Text style={{color: signals.bounce.buySignal ? 'red' : 'gray'}}>
        {signals.bounce.buySignal ? '买入信号' : '无信号'}
      </Text>
      <Text>{signals.bounce.reason}</Text>
      <Button mode="text" onPress={() => alert('AI验证功能即将上线')}>AI验证</Button>
    </Card.Content>
  </Card>

  <Card style={styles.card}>
    <Card.Content>
      <Title>爆拉买入</Title>
      <Text style={{color: signals.surgeBuy.color}}>{signals.surgeBuy.signal}</Text>
      <Text>{signals.surgeBuy.reason}</Text>
      <Button mode="text" onPress={() => alert('AI验证功能即将上线')}>AI验证</Button>
    </Card.Content>
  </Card>

  <Card style={styles.card}>
    <Card.Content>
      <Title>爆拉回调卖出</Title>
      <Text style={{color: signals.surgeSell.color}}>{signals.surgeSell.signal}</Text>
      <Text>{signals.surgeSell.reason}</Text>
      <Button mode="text" onPress={() => alert('AI验证功能即将上线')}>AI验证</Button>
    </Card.Content>
  </Card>
</ScrollView>

); }

const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5' }, card: { margin: 8, elevation: 2 }, }); 14. src/screens/Simulation.js javascript import React, { useState } from 'react'; import { View, Text, StyleSheet, ScrollView, TextInput, Alert } from 'react-native'; import { useSelector, useDispatch } from 'react-redux'; import { Card, Title, Button } from 'react-native-paper'; import { userBuy, userSell } from '../store/slices/accountSlice';

export default function Simulation() { const dispatch = useDispatch(); const { currentPrice } = useSelector(state => state.market); const { user } = useSelector(state => state.account); const [amount, setAmount] = useState('1');

const handleBuy = () => { const qty = parseFloat(amount); if (isNaN(qty) || qty <= 0) { Alert.alert('错误', '请输入有效数量'); return; } const cost = qty * 100 * currentPrice; if (cost > user.balance) { Alert.alert('余额不足', '可用余额不足'); return; } dispatch(userBuy({ id: Date.now().toString(), price: currentPrice, amount: qty, cost, time: new Date().toLocaleString(), })); Alert.alert('成功', 买入 ${qty} 手,价格 $${currentPrice}); };

const handleSell = (position) => { const proceed = position.amount * 100 * currentPrice; dispatch(userSell({ id: position.id, proceed, })); Alert.alert('成功', 卖出 ${position.amount} 手,价格 $${currentPrice}); };

return ( <Card.Content> 用户账号 余额: user.balance.toFixed(2)</Text><Text>当前金价:{user.balance.toFixed(2)}</Text> <Text>当前金价: {currentPrice.toFixed(2)} 交易数量(手): 买入 <Button mode="outlined" onPress={() => {}} style={styles.sellBtn}>卖出 </Card.Content>

  <Card style={styles.card}>
    <Card.Content>
      <Title>当前持仓</Title>
      {user.positions.length === 0 ? (
        <Text>暂无持仓</Text>
      ) : (
        user.positions.map(pos => (
          <View key={pos.id} style={styles.positionItem}>
            <Text>入场价: ${pos.price}  数量: {pos.amount}手</Text>
            <Text>浮动盈亏: ${((currentPrice - pos.price) * pos.amount * 100).toFixed(2)}</Text>
            <Button mode="text" onPress={() => handleSell(pos)}>卖出</Button>
          </View>
        ))
      )}
    </Card.Content>
  </Card>
</ScrollView>

); }

const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5' }, card: { margin: 8, elevation: 2 }, row: { flexDirection: 'row', alignItems: 'center', marginVertical: 8 }, input: { borderWidth: 1, borderColor: '#ccc', padding: 8, marginLeft: 8, width: 80 }, buyBtn: { marginRight: 8, backgroundColor: 'red' }, sellBtn: { borderColor: 'green', color: 'green' }, positionItem: { borderBottomWidth: 1, borderBottomColor: '#eee', paddingVertical: 8 }, }); 15. src/screens/Backtest.js javascript import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; import { Card, Title } from 'react-native-paper';

export default function Backtest() { return ( <Card.Content> 回测功能 后续版本将提供策略回测功能 </Card.Content> ); }

const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', padding: 8 }, card: { marginBottom: 8 }, }); 16. src/screens/Analysis.js javascript import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; import { Card, Title, Button } from 'react-native-paper';

export default function Analysis() { return ( <Card.Content> AI市场分析 后续版本将接入DeepSeek AI进行实时分析 <Button mode="text" onPress={() => alert('AI分析即将上线')}>AI验证 </Card.Content> ); }

const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', padding: 8 }, card: { marginBottom: 8 }, }); 17. src/screens/Settings.js javascript import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, Alert, TextInput } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { Card, Title, Switch, Button } from 'react-native-paper'; import { useDispatch, useSelector } from 'react-redux'; import { setTheme, toggleSound, toggleNotification, setTradeMode, setDataSource, setAlltickToken, setItickToken, updateStrategyParam, } from '../store/slices/settingsSlice'; import DataManager from '../services/websocket';

export default function Settings() { const dispatch = useDispatch(); const { theme, soundEnabled, notificationEnabled, tradeMode, dataSource, alltickToken, itickToken, strategyParams, } = useSelector(state => state.settings);

const [localAlltickToken, setLocalAlltickToken] = useState(alltickToken); const [localItickToken, setLocalItickToken] = useState(itickToken); const [isEditingAlltick, setIsEditingAlltick] = useState(false); const [isEditingItick, setIsEditingItick] = useState(false);

// 加载保存的密钥(可选) useEffect(() => { // 可以从 AsyncStorage 加载更多配置 }, []);

const saveAlltickToken = () => { dispatch(setAlltickToken(localAlltickToken)); setIsEditingAlltick(false); Alert.alert('成功', 'AllTick 密钥已保存,重启后生效'); };

const saveItickToken = () => { dispatch(setItickToken(localItickToken)); setIsEditingItick(false); Alert.alert('成功', 'iTick 密钥已保存,重启后生效'); };

const handleDataSourceChange = (newSource) => { dispatch(setDataSource(newSource)); DataManager.setMode(newSource); // 立即切换数据源 };

const handleTradeModeChange = (mode) => { dispatch(setTradeMode(mode)); };

const handleStrategyParamChange = (strategy, param, value) => { dispatch(updateStrategyParam({ strategy, param, value })); };

return ( <Card.Content> 数据源选择 <Button mode={dataSource === 'alltick' ? 'contained' : 'outlined'} onPress={() => handleDataSourceChange('alltick')} style={styles.sourceBtn} > AllTick <Button mode={dataSource === 'itick' ? 'contained' : 'outlined'} onPress={() => handleDataSourceChange('itick')} style={styles.sourceBtn} > iTick <Button mode={dataSource === 'mock' ? 'contained' : 'outlined'} onPress={() => handleDataSourceChange('mock')} style={styles.sourceBtn} > 模拟数据 当前: {dataSource === 'alltick' ? 'AllTick 实时' : dataSource === 'itick' ? 'iTick 轮询' : '模拟数据'} </Card.Content>

  <Card style={styles.card}>
    <Card.Content>
      <Title>AllTick 配置</Title>
      <View style={styles.row}>
        <Text>API密钥</Text>
        {!isEditingAlltick ? (
          <Button onPress={() => setIsEditingAlltick(true)}>修改</Button>
        ) : (
          <Button onPress={() => setIsEditingAlltick(false)}>取消</Button>
        )}
      </View>
      {isEditingAlltick && (
        <View>
          <TextInput
            style={styles.input}
            value={localAlltickToken}
            onChangeText={setLocalAlltickToken}
            placeholder="输入 AllTick 密钥"
          />
          <Button mode="contained" onPress={saveAlltickToken}>保存</Button>
        </View>
      )}
      {!isEditingAlltick && alltickToken ? (
        <Text style={styles.maskedKey}>
          已设置: {alltickToken.substring(0, 8)}...{alltickToken.substring(alltickToken.length - 8)}
        </Text>
      ) : null}
    </Card.Content>
  </Card>

  <Card style={styles.card}>
    <Card.Content>
      <Title>iTick 配置</Title>
      <View style={styles.row}>
        <Text>API密钥</Text>
        {!isEditingItick ? (
          <Button onPress={() => setIsEditingItick(true)}>修改</Button>
        ) : (
          <Button onPress={() => setIsEditingItick(false)}>取消</Button>
        )}
      </View>
      {isEditingItick && (
        <View>
          <TextInput
            style={styles.input}
            value={localItickToken}
            onChangeText={setLocalItickToken}
            placeholder="输入 iTick 密钥"
          />
          <Button mode="contained" onPress={saveItickToken}>保存</Button>
        </View>
      )}
      {!isEditingItick && itickToken ? (
        <Text style={styles.maskedKey}>
          已设置: {itickToken.substring(0, 8)}...{itickToken.substring(itickToken.length - 8)}
        </Text>
      ) : null}
      <Text style={styles.hint}>免费套餐有效期至 2026-03-17,到期前请登录 iTick 续期</Text>
    </Card.Content>
  </Card>

  <Card style={styles.card}>
    <Card.Content>
      <Title>交易方式</Title>
      <View style={styles.row}>
        <Button
          mode={tradeMode === 'long_only' ? 'contained' : 'outlined'}
          onPress={() => handleTradeModeChange('long_only')}
          style={styles.modeBtn}
        >
          只做多
        </Button>
        <Button
          mode={tradeMode === 'short_only' ? 'contained' : 'outlined'}
          onPress={() => handleTradeModeChange('short_only')}
          style={styles.modeBtn}
        >
          只做空
        </Button>
        <Button
          mode={tradeMode === 'both' ? 'contained' : 'outlined'}
          onPress={() => handleTradeModeChange('both')}
          style={styles.modeBtn}
        >
          多空都做
        </Button>
      </View>
    </Card.Content>
  </Card>

  <Card style={styles.card}>
    <Card.Content>
      <Title>策略参数</Title>
      <Text>中线 MA5 周期: {strategyParams.mid.ma5Period}</Text>
      <Text>中线 MA20 周期: {strategyParams.mid.ma20Period}</Text>
      <Text>短线布林周期: {strategyParams.short.bbPeriod}</Text>
      <Text>跳水跌幅阈值: {strategyParams.dive.dropThreshold}%</Text>
      <Text>爆拉涨幅阈值: {strategyParams.surgeBuy.riseThreshold}%</Text>
      <Text>爆拉时间窗口: {strategyParams.surgeBuy.timeWindow} 分钟</Text>
      <Text>回调阈值: {strategyParams.surgeSell.pullbackThreshold}%</Text>
      <Button mode="text" onPress={() => alert('详细参数配置将在后续版本开放')}>更多参数</Button>
    </Card.Content>
  </Card>

  <Card style={styles.card}>
    <Card.Content>
      <Title>外观设置</Title>
      <View style={styles.row}>
        <Text>深色模式</Text>
        <Switch value={theme === 'dark'} onValueChange={() => dispatch(setTheme(theme === 'light' ? 'dark' : 'light'))} />
      </View>
    </Card.Content>
  </Card>

  <Card style={styles.card}>
    <Card.Content>
      <Title>通知设置</Title>
      <View style={styles.row}>
        <Text>声音提醒</Text>
        <Switch value={soundEnabled} onValueChange={() => dispatch(toggleSound())} />
      </View>
      <View style={styles.row}>
        <Text>推送通知</Text>
        <Switch value={notificationEnabled} onValueChange={() => dispatch(toggleNotification())} />
      </View>
    </Card.Content>
  </Card>

  <Card style={styles.card}>
    <Card.Content>
      <Title>关于</Title>
      <Text>黄金AI交易系统 v2.0</Text>
      <Text>数据源: AllTick / iTick / 模拟</Text>
    </Card.Content>
  </Card>
</ScrollView>

); }

const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', padding: 8 }, card: { marginBottom: 8 }, row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginVertical: 4 }, input: { borderWidth: 1, borderColor: '#ccc', padding: 8, marginVertical: 8, borderRadius: 4 }, maskedKey: { color: '#666', marginTop: 4 }, hint: { color: '#666', fontSize: 12, marginTop: 4 }, sourceBtn: { marginHorizontal: 4, flex: 1 }, modeBtn: { marginHorizontal: 4, flex: 1 }, });