汇率监控/盯盘报汇率定时采集入库

428

主题

-245

回帖

2237

积分

管理员

积分
2237
汇率监控/盯盘报汇率定时采集入库

QQ截图20261021095004.png


QQ截图20261021094948.png


QQ截图20261021094935.png

  1. /**
  2. * 案例17:汇率定时采集入库(纯 printl 日志版,输出到 H5 日志终端)
  3. *
  4. * 经典实用案例"汇率监控/盯盘报警",转换成 AIWROK 实现:
  5. *   okHttp 请求 open.er-api.com 免费汇率接口(https 纯 JSON,无需注册、无需Key)
  6. *   → 一次请求拿到全球160+币种对人民币的牌价 → 挑自选币种换算成"1外币=X元"
  7. *   → SQLite 入库(rate_records 表) → 采集结果逐条完整打印到 H5 终端
  8. *
  9. * 案例15(股票)+案例16(天气)+案例17(汇率) 凑齐"API+数据库+定时器"监控三件套,
  10. * 全程不找图、不点击、无悬浮窗,手机后台长期挂机即可。
  11. *
  12. * 选 er-api 接口的原因:
  13. * 1. 完全免费、不用申请 Key,返回标准 JSON,最适合教学演示;
  14. * 2. 一次请求返回所有币种(以人民币为基准),本地挑币种换算,比逐币种请求省流量;
  15. * 3. 官方免费档每天更新一次,教学演示够用;长期挂机建议间隔调大(如 3600 秒)。
  16. *
  17. * 新知识点(对比案例15/16):
  18. * - 接口返回的是"1元=多少外币"(USD 0.14),演示里取倒数换算成"1外币=多少元"(7.14),
  19. *   这就是"数据加工":入库前先按业务口径转换,回看时不用每次再算;
  20. * - 报警从"绝对值差"升级为"百分比波动":外币间波动率可比,绝对差价不可比。
  21. */

  22. // ========== 配置区(改这里) ==========
  23. var 自选币种列表 = [
  24.     { code: "USD", 名称: "美元" },
  25.     { code: "EUR", 名称: "欧元" },
  26.     { code: "JPY", 名称: "日元" },
  27.     { code: "GBP", 名称: "英镑" },
  28.     { code: "HKD", 名称: "港币" }
  29. ];
  30. var 采集间隔秒 = 30;            // 每 30 秒采一轮(录像教学用;该接口每日更新,长期挂机改 3600)
  31. var 报警波动阈值 = 0.5;         // 与上一轮汇率波动 |百分比| >= 0.5% 触发报警
  32. var SQLite路径 = "/sdcard/汇率监测.db";
  33. var 表名 = "rate_records";

  34. // ========== 全局变量顶部声明 ==========
  35. var gStop = false;
  36. var 数据库连接 = null;
  37. var 数据库可用 = false;
  38. var 累计入库条数 = 0;
  39. var 上一轮汇率 = {};            // { "USD": 7.14, ... } 首轮为空对象,不报波动

  40. // ========== importClass 只能放最外层 ==========
  41. importClass(android.database.sqlite.SQLiteDatabase);
  42. importClass(java.io.File);

  43. // ========== 工具函数(先定义后调用) ==========

  44. // 连接 SQLite(零配置;如需远程库可仿照案例14扩展 mysql 分支)
  45. function dbConnect() {
  46.     try {
  47.         数据库连接 = SQLiteDatabase.openOrCreateDatabase(new File(SQLite路径), null);
  48.         数据库可用 = true;
  49.         printl("[DB] ✅ SQLite 已打开: " + SQLite路径);
  50.     } catch (e) {
  51.         数据库可用 = false;
  52.         printl("[DB] ⚠ 打开失败(本轮只打印不入库): " + e);
  53.     }
  54. }

  55. // 建表(存在则跳过):code/name/rate_cny/collected_at
  56. function dbEnsureTable() {
  57.     if (!数据库可用) return;
  58.     try {
  59.         var sql = "CREATE TABLE IF NOT EXISTS " + 表名 + " (" +
  60.             "id INTEGER PRIMARY KEY AUTOINCREMENT," +
  61.             "code TEXT, name TEXT, rate_cny REAL, collected_at INTEGER)";
  62.         数据库连接.execSQL(sql);
  63.         printl("[DB] ✅ 表已就绪: " + 表名);
  64.     } catch (e) {
  65.         printl("[DB] ⚠ 建表失败: " + e);
  66.         数据库可用 = false;
  67.     }
  68. }

  69. // 单币种一轮汇率入库(rateCny = 1单位外币合多少人民币)
  70. function dbInsertRate(code, name, rateCny) {
  71.     if (!数据库可用) return false;
  72.     try {
  73.         var stmt = 数据库连接.compileStatement("INSERT INTO " + 表名 +
  74.             " (code, name, rate_cny, collected_at) VALUES (?,?,?,?)");
  75.         stmt.bindString(1, "" + code);
  76.         stmt.bindString(2, "" + name);
  77.         stmt.bindDouble(3, rateCny);
  78.         stmt.bindLong(4, time.nowStamp());   // nowStamp=毫秒数字;time.now()是格式化字符串不能bindLong
  79.         stmt.executeInsert();
  80.         stmt.close();
  81.         return true;
  82.     } catch (e) {
  83.         printl("[DB] ❌ 入库失败: " + e);
  84.         return false;
  85.     }
  86. }

  87. function dbClose() {
  88.     if (数据库可用 && 数据库连接 != null) {
  89.         try { 数据库连接.close(); } catch (e) {}
  90.         数据库连接 = null;
  91.     }
  92. }

  93. // 拉取一轮汇率:一次请求拿全部币种,返回数组 [{code,名称,汇率(1外币=X元)}],失败返回 null
  94. function 拉取汇率() {
  95.     var url = "https://open.er-api.com/v6/latest/CNY";   // 以人民币为基准,一次拿全球币种

  96.     try {
  97.         printl("📡 正在请求汇率接口(基准 CNY,自选币种 " + 自选币种列表.length + " 个)...");
  98.         var http = new okHttp();
  99.         http.setTimeOut(5000, 8000, 5000);   // 必须设超时,防网络挂死(踩坑经验)
  100.         printl("📡 等待接口返回(最长8秒)...");
  101.         var text = "" + http.get(url);
  102.         printl("📡 接口已返回,开始解析 JSON...");
  103.         var obj = JSON.parse(text);
  104.         if (!obj || obj.result !== "success" || !obj.rates) {
  105.             printl("[API] ⚠ 接口异常: " + text.substring(0, 120));
  106.             return null;
  107.         }
  108.         var 结果 = [];
  109.         for (var i = 0; i < 自选币种列表.length; i++) {
  110.             var 币 = 自选币种列表[i];
  111.             var 正向 = parseFloat(obj.rates[币.code]);   // 1元人民币 = 正向 个外币
  112.             if (isNaN(正向) || 正向 <= 0) {
  113.                 printl("⚠ 接口无 " + 币.名称 + "(" + 币.code + ") 牌价,跳过");
  114.                 continue;
  115.             }
  116.             var 反向 = 1 / 正向;                          // 1个外币 = 反向 元人民币(入库口径)
  117.             结果.push({ code: 币.code, 名称: 币.名称, 汇率: 反向 });
  118.         }
  119.         printl("✅ 解析完成,共取到 " + 结果.length + " 个币种汇率(牌价更新时间: " +
  120.             obj.time_last_update_utc + ")");
  121.         return 结果;
  122.     } catch (e) {
  123.         printl("[API] ❌ 拉取失败: " + e);
  124.         return null;
  125.     }
  126. }

  127. // 逐条完整打印采集结果(演示用:每个币种的正向/反向牌价都打出来)
  128. function 打印采集结果(汇率数组) {
  129.     printl("📋 本轮采集结果(共 " + 汇率数组.length + " 个币种):");
  130.     for (var i = 0; i < 汇率数组.length; i++) {
  131.         var r = 汇率数组[i];
  132.         printl("   [" + (i + 1) + "] " + r.名称 + "(" + r.code + ") 1 " + r.code +
  133.             " = " + r.汇率.toFixed(4) + " 元  |  1 元 = " + (1 / r.汇率).toFixed(4) + " " + r.code);
  134.     }
  135. }

  136. // 报警检查:与上一轮比波动百分比超阈值就打 ⚠(外币间波动率可比,不用绝对差)
  137. function 检查报警(汇率数组) {
  138.     var 有报警 = false;
  139.     for (var i = 0; i < 汇率数组.length; i++) {
  140.         var r = 汇率数组[i];
  141.         var 旧值 = 上一轮汇率[r.code];
  142.         if (旧值 != null && 旧值 > 0) {
  143.             var 波动 = (r.汇率 - 旧值) / 旧值 * 100;
  144.             if (Math.abs(波动) >= 报警波动阈值) {
  145.                 有报警 = true;
  146.                 var 符号 = 波动 >= 0 ? "+" : "";
  147.                 printl("⚠ 报警:" + r.名称 + "(" + r.code + ") 波动 " + 符号 + 波动.toFixed(3) +
  148.                     "%(" + 旧值.toFixed(4) + " → " + r.汇率.toFixed(4) + "),阈值 " + 报警波动阈值 + "%");
  149.             }
  150.         }
  151.         上一轮汇率[r.code] = r.汇率;   // 记录本轮,供下一轮对比
  152.     }
  153.     if (!有报警) {
  154.         printl("🔕 报警检查:全部币种波动 |百分比| < " + 报警波动阈值 + "%,无报警(首轮无对比基准)");
  155.     }
  156. }

  157. // ========== 主流程 ==========
  158. printl("===== 案例17 汇率定时采集入库 启动 =====");

  159. // 步骤0:连库建表
  160. dbConnect();
  161. dbEnsureTable();
  162. printl("⚙ 配置:自选币种 " + 自选币种列表.length + " 个 · 采集间隔 " + 采集间隔秒 +
  163.     " 秒 · 报警阈值 波动 >= " + 报警波动阈值 + "%");

  164. // 停止按钮清理(顺序:先置标志,stopAllRuning 放最后)
  165. event.onStopBtnEvent(function () {
  166.     gStop = true;
  167.     dbClose();
  168.     runTime.stopAllRuning();
  169. });

  170. // 步骤1:定时循环采集
  171. var 轮次 = 0;
  172. while (!gStop) {
  173.     轮次++;
  174.     printl("\n---- 第 " + 轮次 + " 轮采集 ----");

  175.     var 汇率数组 = 拉取汇率();
  176.     if (汇率数组 != null && 汇率数组.length > 0) {
  177.         // 1) 逐条完整打印采集结果(演示重点:正反两个口径的牌价全打出来)
  178.         打印采集结果(汇率数组);

  179.         // 2) 入库
  180.         var 本轮入库 = 0;
  181.         printl("💾 开始逐条写入数据库(表: " + 表名 + ")...");
  182.         for (var k = 0; k < 汇率数组.length; k++) {
  183.             var q = 汇率数组[k];
  184.             if (dbInsertRate(q.code, q.名称, q.汇率)) {
  185.                 本轮入库++;
  186.                 累计入库条数++;
  187.                 printl("   ✓ 已入库 [" + 本轮入库 + "/" + 汇率数组.length + "] " +
  188.                     q.名称 + "(" + q.code + ") 1 " + q.code + " = " + q.汇率.toFixed(4) + " 元");
  189.             } else {
  190.                 printl("   ✗ 入库失败: " + q.名称 + "(" + q.code + ")");
  191.             }
  192.         }
  193.         printl("[DB] 本轮入库 " + 本轮入库 + " 条 | 累计 " + 累计入库条数 + " 条 | 库文件: " + SQLite路径);

  194.         // 3) 报警检查
  195.         检查报警(汇率数组);
  196.     } else {
  197.         printl("⚠ 本轮无数据,跳过入库(" + time.StampToTime(time.nowStamp()) + ")");
  198.     }

  199.     // 分片等待下一轮:每 5 秒打一条进度日志(画面不空,方便观察/录像)
  200.     printl("⏳ 本轮全部完成," + 采集间隔秒 + " 秒后自动开始下一轮");
  201.     var 剩余 = 采集间隔秒;
  202.     while (剩余 > 0 && !gStop) {
  203.         var 步长 = 剩余 >= 5 ? 5 : 剩余;
  204.         printl("⏳ 等待下一轮...剩余 " + 剩余 + " 秒(期间脚本在待机,到点自动拉汇率→入库)");
  205.         sleep.second(秒 = 步长);
  206.         剩余 = 剩余 - 步长;
  207.     }
  208. }

  209. dbClose();
  210. printl("===== 流程结束:共 " + 轮次 + " 轮,累计入库 " + 累计入库条数 + " 条 =====");
复制代码



举报 回复

随机推荐
随机推荐每次刷新 · 热帖 + 双随机栏 各 50 篇
热帖浏览量最高
随机换个角度看
随机随便看看
随机会员刷新换一批