Author SHA1 Message Date
discountry 958de8f2b8 feat: integrate Extended exchange support with new API, adapter, and order handling functionality 2025-11-17 18:02:12 +08:00
discountry 59ccd1fea8 feat: implement client ping/pong mechanism in LighterGateway for improved WebSocket connection health 2025-11-13 02:53:23 +08:00
discountry a9fa7f2e19 feat: add bid, ask, and mark price to AsterTicker and normalize order status in LighterGateway 2025-11-13 02:34:10 +08:00
discountry 7529de334f feat: implement position pruning and tracking in LighterGateway to manage stale positions and improve data integrity 2025-11-13 02:29:36 +08:00
discountry 1bfe4d58c0 refactor: streamline position handling in LighterGateway by removing unnecessary HTTP empty position logic and improving logging for empty position scenarios 2025-11-13 02:13:52 +08:00
discountry 4ce2b07e21 refactor: adjust feed staleness timeout and check intervals in LighterGateway for optimized monitoring and connection management 2025-11-13 02:05:29 +08:00
discountry 6bb0994cae fix: ensure stale monitoring is stopped on WebSocket error and closure events in LighterGateway for improved connection management 2025-11-13 01:16:03 +08:00
discountry 486b911bed feat: implement feed staleness monitoring in LighterGateway to enhance connection reliability and update handling 2025-11-12 23:57:21 +08:00
discountry f48371ccd8 feat: add priceDecimals to MakerEngine and OffsetMakerEngine for improved price formatting in MakerApp and OffsetMakerApp 2025-11-12 21:24:16 +08:00
discountry 9ca35584b5 feat: add 'canceled-reduce-only' status to TERMINAL_ORDER_STATUSES in LighterGateway for comprehensive order status management 2025-11-12 21:08:35 +08:00
discountry 01e5ab9997 feat: add price viability checks and ensure maker price adjustments in OffsetMakerEngine for improved order handling 2025-11-12 20:45:29 +08:00
discountry 28bc1d5210 feat: improve error handling in OffsetMakerEngine and enhance logging in LighterGateway for better debugging and robustness 2025-11-12 20:34:02 +08:00
discountry 870fe2b8d7 feat: enhance order error handling in OffsetMakerEngine and update LighterGateway order status types for improved robustness 2025-11-12 19:35:31 +08:00
discountry e7f6341961 feat: enhance order handling in LighterGateway and introduce order identity normalization for improved precision and consistency 2025-11-12 19:17:04 +08:00
discountry 1baee3a207 feat: add Guardian strategy to manage existing positions with stop loss and trailing stop functionality 2025-11-09 14:09:54 +08:00
discountry e94cf1bda2 feat: enhance toAccountSnapshot function with improved market ID and symbol matching logic, and add corresponding unit tests 2025-11-08 17:14:01 +08:00
discountry 274e2f3d75 fix: improve WebSocket close reason normalization in LighterGateway for better error handling 2025-11-08 12:10:47 +08:00
discountry a91e87534b refactor: streamline order handling logic in LighterGateway by consolidating market ID checks and improving snapshot order clearing 2025-11-08 12:09:27 +08:00
discountry d0d1afa0ea feat: enhance quantity and price rounding functions for improved precision in trading calculations 2025-11-06 21:06:03 +08:00
discountry 0f70c6b6aa feat: enhance WebSocket documentation and improve LighterGateway position handling for better account management 2025-11-06 18:47:29 +08:00
discountry 078b201d15 feat: implement getPrecision method in AsterExchangeAdapter and enhance AsterGateway for precision handling in order normalization 2025-11-04 22:10:45 +08:00
discountry 1832c4c13e feat: add scaleQuantityWithMinimum function to normalize order quantities and implement corresponding tests 2025-11-03 21:30:19 +08:00
discountry 73a88a8a0f fix: improve order price comparison logic in makeOrderPlan function for better precision 2025-10-28 18:59:37 +08:00
discountry 265cb6df50 feat: enhance close position handling in AsterRestClient to support STOP_MARKET and TAKE_PROFIT_MARKET types 2025-10-28 12:50:31 +08:00
discountry 5bba8169b5 feat: enhance precision synchronization in trading strategies and improve order quantity normalization logic 2025-10-27 18:42:44 +08:00
discountry 295c6a47b7 fix: reverse Kline entries in mapKlines function to maintain chronological order 2025-10-23 20:01:49 +08:00
discountry e7dedbba2c chore: update referral links in README files for Backpack and add Apex referral link 2025-10-23 19:56:44 +08:00
discountry 6196912cf4 fix: ensure order IDs are consistently treated as strings and configure ed25519 to use custom sha512 implementation 2025-10-23 19:45:19 +08:00
discountry f1ebfacaa2 feat: update dependencies and add ccxt documentation for new features 2025-10-23 19:36:42 +08:00
discountry ea5da20311 feat: 重构订单路由逻辑,添加订单意图类型以支持多交易所订单处理 2025-10-23 14:04:44 +08:00
discountry fabc8af679 feat: 更新市场平仓逻辑,添加数量参数并调整reduceOnly处理以符合交易所要求 2025-10-12 10:16:14 +08:00
discountry 189fed0f6e feat: 更新止损订单逻辑,添加数量步进和触发类型处理以符合交易所要求 2025-10-11 22:05:50 +08:00
discountry 09ff4dc296 feat: 移除不必要的数量参数,优化止损和市价平仓订单逻辑以确保精确处理 2025-10-11 21:45:56 +08:00
discountry 2627fa9db8 feat: 优化订单去重和取消逻辑,确保在处理STOP订单时避免精度损失 2025-10-11 21:41:39 +08:00
discountry 8b20e60753 feat: 更新止损订单逻辑,针对Aster期货调整reduceOnly参数处理以符合交易所要求 2025-10-11 21:27:37 +08:00
discountry f991215d86 feat: 优化订单创建和止损逻辑,确保参数处理符合交易所要求 2025-10-09 20:18:13 +08:00
discountry 6a3b842a3b feat: 优化平仓订单逻辑,避免在止损订单中下调数量,确保精确传递给交易所 2025-10-08 20:05:02 +08:00
discountry b36c1dce0f feat: 优化Paradex网关的订单金额处理逻辑,确保在平仓时仅对市场订单省略金额参数 2025-10-08 20:00:08 +08:00
discountry dbe8df6934 feat: 优化平仓逻辑,确保正确处理订单数量并提示交易所关闭整个仓位 2025-10-08 19:55:51 +08:00
discountry 400c7ad4b7 feat: 优化Paradex网关的订单金额处理逻辑,确保平仓时正确传递金额参数 2025-10-08 19:53:44 +08:00
discountry e96cbb6ff4 feat: 更新Paradex网关,添加平仓逻辑以优化订单金额处理 2025-10-08 19:48:25 +08:00
discountry aa3d5e8c6c feat: 更新网格引擎,添加跳过去重参数以优化新订单下单逻辑 2025-10-08 05:44:10 +08:00
discountry 96cf4ea47b feat: 更新网格引擎,优化新订单下单逻辑,合并快照更新与冷却期条件以提升下单效率 2025-10-08 04:59:21 +08:00
discountry 45061ed748 feat: 更新网格引擎,添加订单版本控制与冷却机制以优化新订单下单逻辑 2025-10-08 04:58:11 +08:00
discountry da3dacb549 feat: 更新网格引擎,添加时间戳以跟踪等待订单状态,优化新订单下单逻辑以避免重复下单 2025-10-08 04:56:04 +08:00
discountry 51e0ce2bea feat: 更新网格引擎,添加启动撤单未完成时的处理逻辑,优化撤单与新订单的交互 2025-10-08 04:48:24 +08:00
discountry 8c13d20cb7 feat: 更新网格引擎,添加平仓优先逻辑以确保在持有仓位时正确处理撤单与新订单 2025-10-08 04:44:48 +08:00
discountry 3d959ae579 feat: 更新网格引擎,修复订单处理逻辑中的布尔参数,确保正确的撤单处理 2025-10-08 04:38:22 +08:00
discountry 4240cc6901 feat: 更新网格引擎,添加启动撤单处理逻辑,优化初始平仓时的订单处理与抑制机制 2025-10-08 04:32:22 +08:00
discountry 8d0e9578c4 feat: 更新网格引擎,优化买卖订单处理逻辑,添加相同价格出口意图检查以避免意图冲突 2025-10-08 04:23:30 +08:00
discountry 95f07145a9 feat: 更新网格引擎,添加键级抑制机制以优化订单处理,支持处理已知与未知意图的订单 2025-10-08 04:19:23 +08:00
discountry e3411c6791 feat: 更新网格引擎,增强日志记录以优化订单处理逻辑,添加跳过条件的详细信息 2025-10-08 03:51:29 +08:00
discountry 640f1bfb4e feat: 更新网格引擎,增强入口数量限制逻辑,支持按买卖方向分别计算待处理订单数量 2025-10-08 03:43:47 +08:00
discountry f973fe433d feat: 更新网格引擎,添加等待分类机制以处理账户快照后的订单状态,优化入口数量限制与订单过滤逻辑 2025-10-08 03:38:21 +08:00
discountry 730a75df93 feat: 更新网格引擎,移除未使用的函数与临时阻塞逻辑,增强订单意图管理与出口数量限制以优化订单处理 2025-10-08 03:03:24 +08:00
discountry de8a793bd5 feat: 更新网格引擎,增强订单意图管理与消失订单处理逻辑,添加入口与出口意图支持以优化订单分类 2025-10-08 02:48:06 +08:00
discountry bb072b2d1d feat: 更新网格引擎,添加跳过去重选项以优化订单处理逻辑 2025-10-08 02:38:01 +08:00
discountry 95426e222b feat: 更新网格引擎,优化订单处理逻辑,添加计划订单计数与唯一性检查以提升订单管理效率 2025-10-08 02:34:37 +08:00
discountry 4365d73562 feat: 更新网格引擎,移除减仓订单相关逻辑,优化即时平仓订单处理与状态管理 2025-10-08 02:32:07 +08:00
discountry 6067f759a0 feat: 更新网格引擎,增强即时平仓订单处理逻辑,添加减仓订单支持以优化利润捕获 2025-10-08 02:11:31 +08:00
discountry b31b98f816 feat: 更新网格引擎,添加即时平仓订单队列以优化订单处理逻辑 2025-10-08 01:55:05 +08:00
discountry 801543488e feat: 更新网格引擎,优化等待分类机制,增强账户快照确认逻辑以处理消失订单 2025-10-08 01:45:12 +08:00
discountry 20c73eba21 feat: 更新网格引擎,添加等待分类机制以处理消失订单,优化订单状态确认逻辑 2025-10-08 00:14:12 +08:00
discountry ed49dca2a6 feat: 更新网格引擎,增强订单消失分类处理逻辑,添加临时阻塞机制以避免重复开仓 2025-10-07 23:57:29 +08:00
discountry ca559d0f82 feat: 更新网格引擎,进一步简化状态管理,移除持久化逻辑,优化订单处理与目标检测 2025-10-07 22:34:05 +08:00
discountry fef7604490 feat: 更新网格引擎,增强订单消失处理逻辑,优化平仓订单管理与目标检测 2025-10-07 22:26:32 +08:00
discountry e524c225f2 feat: 更新网格引擎,重构订单处理逻辑,移除减仓订单相关处理,增强平仓订单管理 2025-10-07 22:15:47 +08:00
discountry 39a97110dd feat: 更新网格引擎,优化订单填充检测逻辑,增强对历史键和元数据的比较处理 2025-10-07 22:00:43 +08:00
discountry 8ad19b007b feat: 更新基础套利引擎,使用扣费后价差计算入场机会并优化日志信息 2025-10-07 16:31:26 +08:00
discountry 2e2990369b feat: 更新基础套利引擎,添加市场就绪时间逻辑,优化信号评估以使用快照数据 2025-10-07 16:29:09 +08:00
discountry 24f93fa8a7 feat: 更新基础套利引擎,添加资金收益和手续费计算逻辑,优化信号评估功能并更新UI以显示相关信息 2025-10-07 16:23:53 +08:00
discountry d778e20c23 feat: 添加现货和合约账户余额获取功能至基础套利引擎,更新UI以显示账户余额信息 2025-10-07 16:06:14 +08:00
discountry 58f5de4a48 feat: 添加资金费率获取功能至基础套利引擎,更新UI以显示资金费率信息 2025-10-07 15:56:50 +08:00
discountry ad46635e20 feat: 更新网格引擎,添加绝对持仓量跟踪逻辑,优化锚定价格选择和订单消失分类处理 2025-10-07 15:30:28 +08:00
discountry 6dde78f799 feat: 更新网格引擎,简化状态管理和订单处理逻辑,移除持久化和曝光映射,优化初始平仓处理 2025-10-07 15:18:15 +08:00
discountry 6b70aa0936 feat: 更新网格引擎,优化平仓逻辑以独立处理减仓订单,增强买卖档位映射管理 2025-10-07 14:12:32 +08:00
discountry 66e1b3e6f5 feat: 更新网格引擎,添加现有减仓订单恢复逻辑,优化订单曝光管理和状态同步功能 2025-10-07 04:41:32 +08:00
discountry c6f279c51b feat: 更新网格引擎,优化平仓逻辑以处理实际持仓情况,确保安全平仓数量计算 2025-10-07 04:16:33 +08:00
discountry 9bad7f180e feat: 更新网格引擎,添加新逻辑以处理无持仓和无挂单状态,优化网格状态清理和初始侧分配功能 2025-10-07 04:09:06 +08:00
discountry 88292c9c49 feat: 添加最大平仓滑点配置,更新网格引擎状态持久化逻辑,优化订单管理和状态恢复功能 2025-10-07 03:48:12 +08:00
discountry 5878a3dc0b feat: 更新网格引擎,重构订单管理逻辑,添加持仓水平和关闭目标管理,优化订单计算和曝光对齐处理 2025-10-07 03:24:09 +08:00
discountry ccb23ecf44 feat: 更新网格引擎,添加买卖水平索引以稳定订单侧分配,增强订单计算逻辑 2025-10-07 02:54:01 +08:00
discountry b6a6515677 feat: 增强网格引擎逻辑,添加持仓水平管理和订单书更新功能,优化订单取消和曝光对齐处理 2025-10-07 02:15:23 +08:00
discountry 5e65c7025d feat: 添加基础网格策略支持,更新环境配置示例和文档,增强 CLI 和 UI 界面 2025-10-07 01:40:06 +08:00
discountry d7a95ceb36 feat: 添加期现套利策略支持,更新相关配置和界面,增强 Aster 现货 API 客户端功能 2025-10-07 00:23:24 +08:00
discountry 1bd3e7edc6 feat: 更新 README 文件,优化项目描述,添加多交易所支持信息和环境变量配置指南 2025-10-06 20:32:49 +08:00
discountry 28ba0613ca feat: 更新 .env.example 和 README.md,添加 Paradex 相关环境变量和手续费优惠注册链接 2025-10-06 20:19:11 +08:00
discountry 1dce3dfcfb feat: 更新 README.md,添加 Binance、Backpack 和 edgex 手续费优惠注册链接 2025-10-06 20:16:52 +08:00
discountry d829e451cd feat: 更新 BackpackGateway 适配器,增强账户快照逻辑,支持合约市场的持仓信息和资产归一化处理 2025-10-06 20:11:46 +08:00
discountry 0d2aa84b84 feat: 更新 Lighter 适配器的账户快照逻辑,支持市场符号和市场 ID 过滤功能 2025-10-06 19:17:24 +08:00
discountry d2867ba98c feat: 更新订单协调器和 GRVT 适配器,添加触发类型支持,优化趋势引擎的损益计算逻辑 2025-10-06 19:06:49 +08:00
discountry e28240e799 feat: 更新订单协调器和 GRVT 适配器逻辑,添加止损单的 reduceOnly 和 closePosition 参数,修改默认价格触发器为 LAST 2025-10-06 18:44:16 +08:00
discountry cfd5f4309d feat: 添加 Paradex 适配器支持,更新环境变量配置和适配器构建逻辑 2025-10-06 18:06:23 +08:00
discountry ccc3a2bf89 feat: 增强 OffsetMakerEngine 订单处理逻辑,添加快速重价抑制机制以减少重复下单 2025-10-03 16:16:20 +08:00
discountry f8a87f4557 feat: 优化 Lighter 适配器的订单簿处理逻辑,添加排序和限制深度功能 2025-10-03 16:06:31 +08:00
discountry 263cbd7c21 feat: 增强 Lighter 适配器的账户更新逻辑,支持市场账户的实时更新和合并处理 2025-10-03 15:52:18 +08:00
discountry d328758074 feat: 添加止损单去抖机制,避免重复提交同价同向止损单 2025-10-03 15:40:17 +08:00
discountry 971a2a6adb feat: 优化 Lighter 适配器的订单过期逻辑,简化市场订单的即时过期处理 2025-10-03 15:35:45 +08:00
discountry fcb83bd16b commit 2025-10-03 15:28:16 +08:00
discountry d5c4b04746 feat: 更新订单处理逻辑,将价格类型改为字符串以避免精度问题,并添加价格格式化函数 2025-10-03 07:15:21 +08:00
discountry 65aaf26879 feat: 更新 Maker 刷新间隔配置,将间隔时间从 1500ms 修改为 500ms 2025-10-03 06:59:24 +08:00
discountry 7635b7d467 feat: 更新 Lighter 适配器的订单过期逻辑,支持即时或取消订单类型的处理 2025-10-03 03:29:54 +08:00
discountry f565c399e3 feat: 更新 LighterSigner 配置,baseUrl 变为可选以支持离线签名和测试 2025-10-03 03:25:30 +08:00
discountry b43eaa0c64 feat: 在文档中添加 CLI 参数说明,支持通过命令行临时指定交易所 2025-10-03 03:16:44 +08:00
discountry 9a419c36ac feat: 更新 Lighter 适配器的订单过期逻辑,使用绝对未来时间戳以符合链的预期 2025-10-03 03:08:41 +08:00
discountry 0e5026aaa1 feat: 优化 Lighter 适配器的账户消息处理逻辑,支持部分更新和市场订单类型的处理 2025-10-03 02:54:50 +08:00
discountry 6273f3b94a feat: 在 Lighter 适配器中添加订单簿序列跟踪,优化 WebSocket 消息处理逻辑 2025-10-03 02:40:09 +08:00
discountry e478fb4330 feat: 添加 Lighter 适配器的调试日志功能,优化错误处理和订单簿数据规范化 2025-10-03 02:27:58 +08:00
discountry 599b4ec3f3 chore: 移除 grvt-pysdk 项目中的所有文件,清理代码库 2025-10-03 02:12:45 +08:00
discountry 42061017f6 chore: 移除 Lighter 和 Poseidon Crypto 项目中的所有文件,清理代码库 2025-10-03 01:54:08 +08:00
discountry b7cd6eed99 feat: 重构 Lighter 策略,更新签名桥接路径,移除不必要的文件,添加新的签名库支持 2025-10-03 01:51:55 +08:00
discountry abb56b917a chore: 更新 .env.example 文件,调整 Maker 策略的损失限制参数以优化风险管理 2025-10-03 01:34:32 +08:00
discountry 9124c06315 chore: 移除 Maker 策略中的价格追逐阈值配置,简化订单计划逻辑 2025-10-03 01:13:06 +08:00
discountry 56bcaddb22 chore: 更新 .env.example 文件,调整风险管理参数以优化交易策略 2025-10-03 01:01:23 +08:00
discountry 4c4abb3b09 feat: 添加 Lighter 签名桥接和公钥派生脚本,支持新的签名库和环境检测功能 2025-10-01 18:32:02 +08:00
discountry c8b0ab1c8e feat: 添加 Lighter 适配器及相关功能,支持 trailing stops 和新的交易逻辑 2025-09-30 22:08:05 +08:00
discountry e83bdfccf9 docs: 更新 lighter-python-main 文档,添加示例代码和 API 说明,重构部分内容以提高可读性 2025-09-30 18:35:14 +08:00
discountry d6533733c9 docs: 更新 README 文件,添加静默启动和后台运行的说明,包括命令行用法和 pm2 配置示例 2025-09-28 01:39:49 +08:00
discountry 5c1283fac2 feat: 添加命令行参数解析和策略启动功能,支持静默模式和帮助信息 2025-09-28 01:26:00 +08:00
discountry 0e4ae67573 docs: 更新 AGENTS.md 文件,重构项目结构和开发命令部分,移除旧版文档信息并添加新的策略和测试指南 2025-09-28 01:03:25 +08:00
discountry 6ab0759d3d feat: 引入事件发射器和会话交易量跟踪器,重构策略引擎以支持更灵活的事件处理和交易量计算 2025-09-28 00:43:49 +08:00
discountry 25ab1ababf chore: 重构策略引擎,将核心逻辑迁移至策略目录,删除旧的引擎文件并更新相关引用 2025-09-28 00:03:48 +08:00
discountry e60b32a7f8 docs: 更新 README 文件,添加策略运行所需资金、杠杆设置及时间同步的常见问题解答 2025-09-27 23:33:00 +08:00
discountry 46c0551133 fix: 更新 AsterRestClient 和 AsterGateway 中的订单处理逻辑,支持字符串类型的订单 ID 2025-09-27 23:23:53 +08:00
discountry 7e2d312fe3 chore: 更新 bun.lock 文件,添加多个依赖以支持以太坊相关功能 2025-09-27 22:59:28 +08:00
discountry e8dd36f540 chore: 删除 GRVT 适配器的 README 文件,清理项目文档 2025-09-27 22:58:56 +08:00
discountry 47d44c7cb5 chore: 移除 README 中关于旧版文档的说明,以简化文档结构 2025-09-27 22:58:38 +08:00
discountry 048dfb1698 fix: 更新 ethereum-cryptography 依赖至 2.1.3 以支持最新功能 2025-09-27 22:52:57 +08:00
discountry fc1a74174c feat: 在 package.json 中添加 ethereum-cryptography 依赖以支持以太坊相关功能 2025-09-27 22:51:28 +08:00
discountry b45539b7c2 feat: 添加英文文档,更新中文文档以提升用户体验和使用指南的清晰度 2025-09-27 22:46:32 +08:00
discountry a998b3d3a1 fix: 更新最小布林带宽度配置,调整为更小的阈值以支持新策略 2025-09-27 22:33:32 +08:00
discountry d392f54f21 feat: 添加布林带宽度计算功能及相关配置项,优化趋势引擎以支持布林带宽度过滤 2025-09-27 22:29:56 +08:00
1249 changed files with 204180 additions and 15338 deletions
+87 -7
View File
@@ -1,5 +1,5 @@
# Exchange selection # Exchange selection
EXCHANGE=aster # Pick aster (default) or grvt EXCHANGE=aster # Pick aster (default) or grvt/lighter/backpack/paradex
# Aster API credentials # Aster API credentials
ASTER_API_KEY= ASTER_API_KEY=
@@ -10,11 +10,14 @@ TRADE_SYMBOL=BTCUSDT # Trading pair symbol
TRADE_AMOUNT=0.001 # Base order quantity (base asset, e.g. BTC) TRADE_AMOUNT=0.001 # Base order quantity (base asset, e.g. BTC)
# Risk management (USD amounts unless noted) # Risk management (USD amounts unless noted)
LOSS_LIMIT=0.03 # Max loss per trade in USDT before forced close LOSS_LIMIT=0.04 # Max loss per trade in USDT before forced close
TRAILING_PROFIT=0.2 # Trailing stop activation profit (USDT) TRAILING_PROFIT=0.2 # Trailing stop activation profit (USDT)
TRAILING_CALLBACK_RATE=0.2 # Trailing callback percent (e.g. 0.2 => 0.2%) TRAILING_CALLBACK_RATE=0.2 # Trailing callback percent (e.g. 0.2 => 0.2%)
PROFIT_LOCK_TRIGGER_USD=0.1 # Start moving base stop once unrealized PnL > this (USDT) PROFIT_LOCK_TRIGGER_USD=0.08 # Start moving base stop once unrealized PnL > this (USDT)
PROFIT_LOCK_OFFSET_USD=0.05 # Base stop offset from entry after trigger (USDT) PROFIT_LOCK_OFFSET_USD=0.04 # Base stop offset from entry after trigger (USDT)
BOLLINGER_LENGTH=20 # SMA window (minutes) used for Bollinger bandwidth
BOLLINGER_STD_MULTIPLIER=2 # Standard deviation multiplier for Bollinger bands
MIN_BOLLINGER_BANDWIDTH=0.001 # Require bandwidth >= this ratio before new entries
# Precision (per-symbol exchange filters) # Precision (per-symbol exchange filters)
PRICE_TICK=0.1 # Price tick size (e.g. BTCUSDT uses 0.1) PRICE_TICK=0.1 # Price tick size (e.g. BTCUSDT uses 0.1)
@@ -27,14 +30,29 @@ KLINE_INTERVAL=1m # Kline interval (e.g., 1m/3m/5m)
MAX_CLOSE_SLIPPAGE_PCT=0.05 # Max allowed deviation vs mark when closing (0.05 => 5%) MAX_CLOSE_SLIPPAGE_PCT=0.05 # Max allowed deviation vs mark when closing (0.05 => 5%)
# Maker-only settings # Maker-only settings
MAKER_LOSS_LIMIT=0.03 # Maker loss cap (USDT). Defaults to LOSS_LIMIT if unset MAKER_LOSS_LIMIT=0.05 # Maker loss cap (USDT). Defaults to LOSS_LIMIT if unset
MAKER_PRICE_CHASE=0.3 # Price chase threshold (USDT)
MAKER_BID_OFFSET=0 # Bid quote offset from top bid (USDT) MAKER_BID_OFFSET=0 # Bid quote offset from top bid (USDT)
MAKER_ASK_OFFSET=0 # Ask quote offset from top ask (USDT) MAKER_ASK_OFFSET=0 # Ask quote offset from top ask (USDT)
MAKER_REFRESH_INTERVAL_MS=1500 # Maker refresh cadence (ms) MAKER_REFRESH_INTERVAL_MS=500 # Maker refresh cadence (ms)
MAKER_MAX_CLOSE_SLIPPAGE_PCT=0.05 # Maker close slippage guard (fallbacks to MAX_CLOSE_SLIPPAGE_PCT) MAKER_MAX_CLOSE_SLIPPAGE_PCT=0.05 # Maker close slippage guard (fallbacks to MAX_CLOSE_SLIPPAGE_PCT)
MAKER_PRICE_TICK=0.1 # Maker price tick size (defaults to PRICE_TICK) MAKER_PRICE_TICK=0.1 # Maker price tick size (defaults to PRICE_TICK)
# Grid strategy defaults
GRID_LOWER_PRICE=25000 # Grid lower bound price (quote currency)
GRID_UPPER_PRICE=35000 # Grid upper bound price
GRID_LEVELS=10 # Number of grid levels between bounds (>=2)
GRID_ORDER_SIZE=0.001 # Quantity per grid order (base asset units)
GRID_MAX_POSITION_SIZE=0.01 # Max inventory the grid may hold (base units)
GRID_REFRESH_INTERVAL_MS=1000 # Grid evaluation cadence (ms)
GRID_MAX_LOG_ENTRIES=200 # Grid trade log length (defaults to MAX_LOG_ENTRIES when unset)
GRID_DIRECTION=both # Order direction: both | long | short
GRID_STOP_LOSS_PCT=0.01 # Stop loss trigger percentage beyond bounds (0.01 => 1%)
GRID_RESTART_TRIGGER_PCT=0.01 # Restart buffer percentage inside bounds
GRID_AUTO_RESTART_ENABLED=true # Automatically resume grid when price re-enters range
GRID_MAX_CLOSE_SLIPPAGE_PCT=0.05 # Close-order slippage guard relative to mark price
# GRID_PRICE_TICK=0.1 # Optional override for grid price tick (falls back to PRICE_TICK)
# GRID_QTY_STEP=0.001 # Optional override for grid quantity step (falls back to QTY_STEP)
# GRVT authentication (set when EXCHANGE=grvt) # GRVT authentication (set when EXCHANGE=grvt)
GRVT_API_KEY= GRVT_API_KEY=
GRVT_API_SECRET= GRVT_API_SECRET=
@@ -47,3 +65,65 @@ GRVT_ENV=prod
# GRVT_COOKIE="gravity=..." # Pre-provisioned session cookie (auto-refresh uses API key when absent) # GRVT_COOKIE="gravity=..." # Pre-provisioned session cookie (auto-refresh uses API key when absent)
# GRVT_ACCOUNT_ID= # Populated automatically after login # GRVT_ACCOUNT_ID= # Populated automatically after login
# GRVT_SIGNER_PATH=./grvt-signer.cjs # Custom signature provider module # GRVT_SIGNER_PATH=./grvt-signer.cjs # Custom signature provider module
# Lighter authentication (set when EXCHANGE=lighter)
LIGHTER_ACCOUNT_INDEX=
LIGHTER_API_PRIVATE_KEY= # 40-byte hex private key (e.g., 0x...)
LIGHTER_API_KEY_INDEX=0 # API key slot (default 0)
LIGHTER_SYMBOL=BTCUSDT # Trading pair (defaults to TRADE_SYMBOL when omitted)
LIGHTER_ENV=testnet # mainnet | testnet | staging | dev
# LIGHTER_BASE_URL=https://testnet.zklighter.elliot.ai
# LIGHTER_CHAIN_ID=300 # Override inferred chain id when needed
# LIGHTER_MARKET_ID=1 # Prefer explicit market id when symbols differ
# LIGHTER_PRICE_DECIMALS=3 # Manual override for price decimals (optional)
# LIGHTER_SIZE_DECIMALS=3 # Manual override for size decimals (optional)
# Backpack exchange configuration
# Fill these with your Backpack API credentials and preferences
BACKPACK_API_KEY=
BACKPACK_API_SECRET=
BACKPACK_PASSWORD=
BACKPACK_SUBACCOUNT=
# Use sandbox environment: "true" to enable, otherwise leave as false
BACKPACK_SANDBOX=false
# Default trading symbol (falls back to TRADE_SYMBOL or BTCUSDC)
BACKPACK_SYMBOL=BTC_USD_PERP
# Enable verbose adapter logging: set to "1" or "true"
BACKPACK_DEBUG=false
# Paradex exchange configuration
# Provide the EVM private key & wallet address for onboarded accounts.
# When EXCHANGE=paradex these values are used automatically.
PARADEX_SYMBOL=BTC-USD-PERP
PARADEX_PRIVATE_KEY=
PARADEX_WALLET_ADDRESS=
# Enable testnet endpoints by setting to "true"; defaults to false (mainnet).
# PARADEX_SANDBOX=false
# Force disabling ccxt.pro websocket usage by setting to "false" (pro is preferred when installed).
# PARADEX_USE_PRO=true
# Optional reconnect delay override (milliseconds, e.g., 2000). Leave blank for default.
# PARADEX_RECONNECT_DELAY_MS=
# Enable verbose adapter logging: set to "1" or "true"
# PARADEX_DEBUG=false
# Extended exchange configuration (Starknet)
# Set EXCHANGE=extended to activate. Requires on-chain Stark key + vault id.
EXTENDED_API_KEY=
EXTENDED_STARK_PRIVATE_KEY= # 0x-prefixed Stark private key for signing orders
EXTENDED_VAULT_ID= # Vault/sub-account id from Extended UI
EXTENDED_MARKET=BTC-USD # Defaults to TRADE_SYMBOL when omitted
# Optional overrides (defaults target mainnet host)
# EXTENDED_API_HOST=api.starknet.extended.exchange
# EXTENDED_STREAM_HOST=api.starknet.extended.exchange
# EXTENDED_PRIVATE_STREAM_HOST=api.starknet.extended.exchange
# EXTENDED_USER_AGENT=ritmex-bot
+22 -6
View File
@@ -1,19 +1,35 @@
# Repository Guidelines # Repository Guidelines
## Project Structure & Module Organization ## Project Structure & Module Organization
The Bun-based entry point (`index.ts`) lives at the root for quick smoke checks, alongside `tsconfig.json` and the Bun lockfile. The production-ready trading agents reside in `legacy/`, which is a pnpm-managed workspace. Inside `legacy/`, strategy scripts (`trendV2.ts`, `maker.ts`, `bot.ts`) and the CLI live at the top level, shared helpers are under `legacy/utils/`, and exchange adapters plus tests sit in `legacy/exchanges/`. Reference environment samples are provided in `legacy/env.example`, and longer-form docs are kept in `legacy/docs/`. The Bun entry point (`index.ts`) lives at the repo root for quick CLI smoke checks. All production code is under `src/`:
- `src/strategy/` gathers every live trading engine (`maker`, `offset-maker`, `trend`). Shared helpers for strategy wiring sit in `src/strategy/common/`.
- `src/core/` keeps order coordination plus shared libs used by multiple strategies.
- `src/exchanges/` exposes the adapters and REST/websocket clients.
- `src/ui/` implements the Ink dashboards for each strategy (they remain independent dashboards).
- `src/logging/` contains the trade log helper.
- `src/utils/` and `src/config.ts` hold cross-cutting utilities and runtime config.
- `docs/` stores reference material, while `tests/` contains Vitest suites.
## Build, Test, and Development Commands ## Build, Test, and Development Commands
Run `bun install` at the root to satisfy the lightweight Bun demo. Change into `legacy/` for real work: `pnpm install` bootstraps dependencies, `pnpm start` launches the default trend strategy, `pnpm maker` starts the market-making loop, `pnpm cli:start` triggers the dual-exchange hedging flow, and `pnpm test` executes the full Vitest suite. Use `pnpm aster:test` when you only need the Aster adapter checks. - `bun install` install dependencies.
- `bun run index.ts` launch the CLI menu.
- `bun x vitest run` execute the full test suite; `bun x vitest --watch` for incremental runs.
Strategy-specific scripts still execute through the CLI; there is no separate `legacy/` workspace.
## Coding Style & Naming Conventions ## Coding Style & Naming Conventions
All code is modern TypeScript using native ES modules. Follow the existing two-space indentation, keep imports sorted from external to local, and prefer `camelCase` for variables/functions with `PascalCase` for classes and enums. Strategy files stay in the project root with descriptive verbs (for example `maker.ts`), while shared utilities belong under `legacy/utils/`. Comments should stay concise and explain non-obvious trading logic. Use modern TypeScript with ES modules, two-space indentation, and sorted imports (external → internal). Favor `camelCase` for variables/functions and `PascalCase` for classes/enums. Place new strategies under `src/strategy/`, shared utilities under `src/utils/` or `src/strategy/common/` when they only apply to strategies. Keep comments focused on non-obvious trading logic.
## Testing Guidelines ## Testing Guidelines
Vitest powers unit and integration checks. Place new suites beside their subjects using the `<feature>.test.ts` pattern (e.g., `legacy/exchanges/aster.test.ts`). Ensure strategies ship with coverage for order flow, risk guardrails, and websocket edge cases before opening a pull request. Run `pnpm test --watch` during development to keep feedback tight. Vitest powers unit/integration tests. Co-locate new tests next to their subject using `<feature>.test.ts`. Strategies should have coverage for order lifecycle, risk guards, and websocket edge cases. Run `bun x vitest --watch` during development for fast feedback.
## Commit & Pull Request Guidelines ## Commit & Pull Request Guidelines
The repository history is empty, so adopt a lightweight Conventional Commits style (for example `feat: add hedging status panel`) to keep future changelogs clear. Commits should stay scoped to one strategy or module. Pull requests need a concise summary, reproduction or validation notes (commands run, environments touched), and screenshots or log excerpts when behavior changes. Link tracking issues or tasks to help downstream coordination. Follow lightweight Conventional Commits (e.g. `feat: add hedging status panel`). Scope each commit to a single module or strategy. PRs should include:
- Summary of changes.
- Validation notes (commands run, environments touched).
- Relevant logs or screenshots for behavior changes.
Link issues/tasks when available.
## Environment & Secrets ## Environment & Secrets
Copy `legacy/env.example` to `.env` and populate API keys for Bitget and AsterDex before running live strategies. Never commit secrets; rely on local dotenv files or your deployment platform's secret manager. Rotate keys immediately if logs or configs leave the sandbox. Duplicate `.env.example` to `.env` and populate API keys (Aster/GRVT) before running strategies. Do not commit secrets—use local `.env` or deployment secret managers. Rotate keys if they leak into logs or backups.
+148 -178
View File
@@ -1,219 +1,189 @@
# ritmex-bot # ritmex-bot
一个基于 Bun 的 Aster 永续合约终端机器人,内置趋势跟随(SMA30)与做市策略,使用 websocket 实时行情,命令行界面由 Ink 驱动,可在断线后自动恢复运行 基于 Bun 的多交易所永续合约量化终端,内置趋势跟随(SMA30)、Guardian 防守与做市策略,支持快速恢复、实时行情订阅、日志追踪与 CLI 仪表盘
如果您希望获取优惠并支持本项目,请考虑使用以下注册链接:
* [Aster 30% 手续费优惠注册链接](https://www.asterdex.com/zh-CN/referral/4665f3)
* [Binance 手续费优惠注册链接](https://www.binance.com/join?ref=KNKCA9XC)
* [GRVT 手续费优惠注册链接](https://grvt.io/exchange/sign-up?ref=sea)
* [Backpack 手续费优惠注册链接](https://backpack.exchange/join/ritmex)
* [edgex 手续费优惠注册链接](https://pro.edgex.exchange/referral/BULL)
* [Paradex 手续费优惠注册链接](https://paradex.io/ref/xingxingjun)
* [Apex 手续费优惠注册链接](https://join.omni.apex.exchange/SEA)
## 文档索引
- [English README](README_en.md)
- [简明上手指南(零基础)](simple-readme.md)
- [基础网格策略使用教程](grid-trading.md)
## 核心特性
- **实时行情与风控**Websocket + REST 自动同步账户、挂单与仓位,断线后自动恢复。
- **趋势策略**:SMA30 穿越入场,内置止损、移动止盈、布林带带宽过滤与步进锁盈。
- **Guardian 策略**:不主动开单,实时监听账户仓位并强制补挂/移动止损与动态止盈,防止裸奔。
- **做市策略**:支持双边追价、风险阈值控制与订单自愈。
- **模块化架构**:策略引擎、交易所适配器与 Ink CLI 相互解耦,新增交易所或策略更容易。
## 支持的交易所
| 交易所 | 合约类型 | 必填环境变量 | 备注 |
| --- | --- | --- | --- |
| Aster | USDT 永续 | `ASTER_API_KEY`, `ASTER_API_SECRET` | 默认交易所;兼容脚本引导
| GRVT | USDT 永续 | `GRVT_API_KEY`, `GRVT_API_SECRET`, `GRVT_SUB_ACCOUNT_ID` | `GRVT_ENV` 可切换 `prod`/`testnet`
| Lighter | zkLighter 永续 | `LIGHTER_ACCOUNT_INDEX`, `LIGHTER_API_PRIVATE_KEY` | 默认 `LIGHTER_ENV=testnet`
| Backpack | USDC 永续 | `BACKPACK_API_KEY`, `BACKPACK_API_SECRET`, `BACKPACK_PASSWORD` | `BACKPACK_SANDBOX=true` 启用沙盒
| Paradex | StarkEx 永续 | `PARADEX_PRIVATE_KEY`, `PARADEX_WALLET_ADDRESS` | `PARADEX_SANDBOX=true` 使用测试网
## 系统要求
- Bun ≥ 1.2(需同时包含 `bun``bunx` 命令)
- macOS、Linux 或 Windows (推荐 WSL)
- Node.js 仅在部分工具链场景需要,可选
## 快速上手 ## 快速上手
### 一键脚本(macOS / Linux / WSL
使用优惠码获取 30% 手续费折扣:[注册 Aster 获取手续费优惠](https://www.asterdex.com/zh-CN/referral/4665f3)
如果你完全不懂代码,可以查看 **[小白教程](simple-readme.md) 了解使用方法。**
遇到Bug,反馈问题,请到 [Telegram群组](https://t.me/+4fdo0quY87o4Mjhh)
### 一键脚本
一键安装并启动(macOS / Linux):
```bash ```bash
curl -fsSL https://github.com/discountry/ritmex-bot/raw/refs/heads/main/setup.sh | bash curl -fsSL https://github.com/discountry/ritmex-bot/raw/refs/heads/main/setup.sh | bash
``` ```
脚本会自动安装 Bun、安装依赖、引导输入 API Key/Secret,生成 `.env` 并启动程序 脚本会安装 Bun、项目依赖,收集 Aster API 凭证,生成 `.env` 并启动 CLI。运行前请准备好对应交易所的 API Key/Secret
Windows 使用 WSL(推荐): ### 手动安装
1. 先安装并启用 WSL,参考微软官方文档:[在 Windows 上安装 WSL](https://learn.microsoft.com/zh-cn/windows/wsl/install) 1. **获取代码**
2. 在命令行或者 PowerShell 输入 `wsl` 并按下回车打开 WSL。
3. 在 WSL 中运行一键脚本:
```bash ```bash
curl -fsSL https://github.com/discountry/ritmex-bot/raw/refs/heads/main/setup.sh | bash git clone https://github.com/discountry/ritmex-bot.git
cd ritmex-bot
``` ```
按提示输入 ASTER_API_KEY / ASTER_API_SECRET,脚本将自动安装 Bun、依赖并启动程序 不便使用 Git 时,可在仓库页面下载 ZIP 后手动解压
2. **安装 Bun**
## 手动安装 - macOS / Linux`curl -fsSL https://bun.sh/install | bash`
- Windows PowerShell`powershell -c "irm bun.sh/install.ps1 | iex"`
1. **下载代码** 安装完成后重新打开终端,确认 `bun -v` 正常输出版本号。
- 如果会使用 Git`git clone https://github.com/discountry/ritmex-bot.git` 3. **安装依赖**
- 如果不会使用 Git:点击仓库页面的 `Code` → `Download ZIP`,将压缩包解压到如 `桌面/ritmex-bot` 的目录。
2. **打开命令行并进入项目目录**
- macOS:通过 Spotlight (`⌘ + 空格`) 搜索 “Terminal” 并打开。
- Windows:在开始菜单搜索 “PowerShell” 或 “Windows Terminal” 并打开。
- 使用 `cd` 切换到项目目录,例如:
```bash
# macOS / Linux
cd ~/Desktop/ritmex-bot
# Windows
cd C:\Users\用户名\Desktop\ritmex-bot
```
3. **安装 [Bun](https://bun.com) ≥ 1.2**
- macOS / Linux
```bash
curl -fsSL https://bun.sh/install | bash
```
- WindowsPowerShell):
```powershell
powershell -c "irm bun.sh/install.ps1 | iex"
```
安装完成后关闭并重新打开终端,运行 `bun -v` 确认命令可用。
如果上述命令无法完成安装,请尝试 [bun官网](https://bun.com/get) 提供的各种安装方式。
Windows 用户如果无法正常安装,可以尝试先[安装 nodejs](https://nodejs.org/en/download)
然后使用 `npm` 安装 `bun`
```bash
npm install -g bun
```
4. **安装依赖**
```bash ```bash
bun install bun install
``` ```
5. **配置环境变量** 4. **复制环境变量模板并填写**
复制 `.env.example` 为 `.env` 并填入你的 Aster API Key/Secret
```bash ```bash
cp .env.example .env cp .env.example .env
``` ```
然后根据需要修改 `.env` 中的配置项: 按下文指南修改 `.env`,至少需要正确配置一个交易所的凭证。
- API KEY 获取地址 [https://www.asterdex.com/zh-CN/api-management](https://www.asterdex.com/zh-CN/api-management) 5. **运行 CLI**
- `ASTER_API_KEY` / `ASTER_API_SECRET`:Aster 交易所提供的 API 凭证。
- `TRADE_SYMBOL`:策略运行的交易对(默认 `BTCUSDT`),需与 API 权限范围一致。
- `TRADE_AMOUNT`:单次下单数量(合约张数折算后单位为标的货币,例如 BTC)。
- `LOSS_LIMIT`:单笔允许的最大亏损(USDT),触发即强制平仓。
- `TRAILING_PROFIT` / `TRAILING_CALLBACK_RATE`:趋势策略的动态止盈触发值(单位 USDT)与回撤百分比(百分数,如 0.2 表示 0.2%)。
- `PROFIT_LOCK_TRIGGER_USD` / `PROFIT_LOCK_OFFSET_USD`:达到一定浮盈后,将基础止损上调(做多)或下调(做空)到开仓价的偏移量(单位 USDT)。
- `PRICE_TICK` / `QTY_STEP`:交易对的最小价格变动单位与最小下单数量步长(例如 BTCUSDT 分别为 0.1 与 0.001)。
- `MAKER_*` 参数:做市策略追价阈值、报价偏移、刷新频率等,可按流动性需求调节。
6. **运行机器人**
```bash ```bash
bun run index.ts bun run index.ts
``` ```
在终端中按 ↑/↓ 选择 “趋势策略” 或 “做市策略”,回车启动。按 `Esc` 返回选择菜单,`Ctrl+C` 退出。 方向键选择策略并回车启动`Esc` 返回菜单,`Ctrl+C` 退出。
7. **风险提示**
建议先在小额或仿真环境中测试策略;真实资金操作前请确认 API 仅开启必要权限,并逐步验证配置。
A Bun-powered trading workstation for Aster perpetual contracts. The project ships two production strategies—an SMA30 trend follower and a dual-sided maker—that share a modular gateway, UI, and runtime state derived entirely from the exchange. Everything runs in the terminal via Ink, with live websocket refresh and automatic recovery from restarts or network failures. ## 通用环境变量
`.env.example` 提供了所有默认键值,下表概括最常用参数:
## Features | 变量 | 说明 |
- **Live data over websockets** with REST fallbacks and automatic re-sync after reconnects. | --- | --- |
- **Trend strategy**: SMA30 crossover entries, automated stop-loss / trailing-stop, and P&L tracking. | `EXCHANGE` | 选择交易所(`aster`/`grvt`/`lighter`/`backpack`/`paradex` |
- **Maker strategy**: adaptive bid/ask chasing, risk stops, and target order introspection. | `TRADE_SYMBOL` | 交易对(默认 `BTCUSDT` |
- **Extensibility**: exchange gateway, engines, and UI components are modular for new venues or strategies. | `TRADE_AMOUNT` | 单笔下单数量(标的资产计) |
| `LOSS_LIMIT` | 单笔最大亏损触发的强平额度(USDT) |
| `TRAILING_PROFIT` / `TRAILING_CALLBACK_RATE` | 动态止盈触发值(USDT)与回撤百分比 |
| `PROFIT_LOCK_TRIGGER_USD` / `PROFIT_LOCK_OFFSET_USD` | 浮盈超过阈值后上调止损的触发金额与偏移 |
| `BOLLINGER_*` | 趋势策略布林带过滤参数 |
| `PRICE_TICK` / `QTY_STEP` | 交易所要求的最小报价与数量精度 |
| `POLL_INTERVAL_MS` | 趋势策略循环间隔(毫秒) |
| `MAX_CLOSE_SLIPPAGE_PCT` | 平仓时相对标记价允许的最大偏差 |
| `MAKER_*` | 做市策略专属参数(追价阈值、报价偏移、刷新频率等) |
## Requirements > 可通过命令行临时覆盖交易所与策略(优先级高于 `.env`):
- [Bun](https://bun.com) ≥ 1.2 > ```bash
- Node.js (optional, only if you prefer `npm` tooling) > bun run index.ts --exchange grvt --strategy maker
- Valid Aster API credentials with futures access > bun run index.ts -e lighter -s offset-maker --silent
> ```
## Installation ## 交易所配置指南
### Aster
1. 将 `EXCHANGE` 保持为 `aster`(默认值)。
2. 填写 `ASTER_API_KEY` 与 `ASTER_API_SECRET`。
3. 根据交易对调整 `TRADE_SYMBOL`、`PRICE_TICK`、`QTY_STEP` 等精度参数。
4. 一键脚本会自动写入这些变量,手动部署时需自行维护。
### GRVT
1. 在 `.env` 中设置 `EXCHANGE=grvt`。
2. 填写 `GRVT_API_KEY`、`GRVT_API_SECRET`、`GRVT_SUB_ACCOUNT_ID`。
3. 若使用测试网,可将 `GRVT_ENV=testnet` 并调整 `GRVT_INSTRUMENT`/`GRVT_SYMBOL`。
4. 可选:提供 `GRVT_COOKIE` 或自定义 `GRVT_SIGNER_PATH` 以复用已有登录态。
### Lighter
1. 设置 `EXCHANGE=lighter`。
2. 填写 `LIGHTER_ACCOUNT_INDEX` 与 `LIGHTER_API_PRIVATE_KEY`40 字节十六进制私钥),其中`LIGHTER_ACCOUNT_INDEX`是你的账户索引,需要你在官网按F12观察接口请求获取,`LIGHTER_API_PRIVATE_KEY`是你的API私钥。
3. 如需切换环境,将 `LIGHTER_ENV` 改为 `mainnet`/`staging`/`dev`;必要时指定 `LIGHTER_BASE_URL`。
4. 交易对默认为 `LIGHTER_SYMBOL=BTCUSDT`,也可按需重写价格与数量小数位。
### Backpack
1. 设置 `EXCHANGE=backpack`。
2. 填写 `BACKPACK_API_KEY`、`BACKPACK_API_SECRET`、`BACKPACK_PASSWORD`;如有分账户,补充 `BACKPACK_SUBACCOUNT`,默认填写主账户ID。
3. 使用测试环境时将 `BACKPACK_SANDBOX=true`,并确认 `BACKPACK_SYMBOL` 与实际符号一致(默认 `BTC_USD_PERP`)。
4. 可通过 `BACKPACK_DEBUG=true` 观察适配器详细日志。
### Paradex
1. 设置 `EXCHANGE=paradex`。
2. 提供 `PARADEX_PRIVATE_KEY`EVM 私钥)与 `PARADEX_WALLET_ADDRESS` 注意这是你EVM钱包的地址和私钥,建议创建全新钱包,不要放置无关资产。
3. 默认连接主网,若需测试网,将 `PARADEX_SANDBOX=true` 并根据需要调整 `PARADEX_SYMBOL`。
4. 复杂环境可额外设置 `PARADEX_USE_PRO`、`PARADEX_RECONNECT_DELAY_MS` 或调试开关。
## 命令速查
```bash ```bash
bun install bun run index.ts # 启动 CLI(默认入口)
bun run start # 等价于运行 index.ts
bun run dev # 调试模式
bun x vitest run # 执行全部测试
``` ```
## Configuration ## 静默启动与后台运行
Create an `.env` (or export environment variables) with at least: ### 直接静默启动
无需进入 Ink 菜单,可用命令行直接拉起指定策略:
```bash ```bash
ASTER_API_KEY=your_key bun run index.ts --strategy trend --silent
ASTER_API_SECRET=your_secret bun run index.ts --strategy maker --silent
TRADE_SYMBOL=BTCUSDT # optional, defaults to BTCUSDT bun run index.ts --strategy offset-maker --silent
TRADE_AMOUNT=0.001 # position size used by both strategies
LOSS_LIMIT=0.03 # per-trade USD loss cap
TRAILING_PROFIT=0.2 # trailing activation profit in USDT
TRAILING_CALLBACK_RATE=0.2 # trailing callback in percent, e.g. 0.2 => 0.2%
PROFIT_LOCK_TRIGGER_USD=0.1 # profit threshold to start moving base stop (USDT)
PROFIT_LOCK_OFFSET_USD=0.05 # base stop offset from entry after trigger (USDT)
PRICE_TICK=0.1 # price tick size; set per symbol
QTY_STEP=0.001 # quantity step size; set per symbol
``` ```
Additional maker-specific knobs (`MAKER_*`) live in `src/config.ts` and may be overridden via env vars: 如需同时指定交易所,可叠加 `--exchange/-e` 参数。
### 项目内置脚本
`package.json` 提供了便捷脚本:
```bash ```bash
# Maker-specific (units in USDT unless noted) bun run start:trend:silent
MAKER_LOSS_LIMIT=0.03 # override maker risk stop; defaults to LOSS_LIMIT bun run start:maker:silent
MAKER_PRICE_CHASE=0.3 # chase threshold bun run start:offset:silent
MAKER_BID_OFFSET=0 # bid offset from top bid (USDT)
MAKER_ASK_OFFSET=0 # ask offset from top ask (USDT)
MAKER_REFRESH_INTERVAL_MS=1500 # maker refresh cadence (ms)
MAKER_MAX_CLOSE_SLIPPAGE_PCT=0.05 # allowed deviation vs mark when closing
MAKER_PRICE_TICK=0.1 # maker tick size; defaults to PRICE_TICK
``` ```
To switch the entire CLI to GRVT instead of Aster, set `EXCHANGE=grvt` and provide the programmable API credentials: ### 使用 pm2 守护并自动重启
安装 `pm2`(示例:`bun add -d pm2`)后,可在项目内直接运行:
```bash ```bash
EXCHANGE=grvt bunx pm2 start bun --name ritmex-trend --cwd . --restart-delay 5000 -- run index.ts --strategy trend --silent
GRVT_API_KEY=your_api_key
GRVT_API_SECRET=0xabc123... # private key used for EIP-712 order signatures
GRVT_SUB_ACCOUNT_ID=your_sub_account # sub account that actually trades
GRVT_INSTRUMENT=BTC_USDT_Perp # instrument as defined by GRVT
GRVT_SYMBOL=BTCUSDT # optional display symbol; defaults to instrument sans underscores
GRVT_ENV=prod # optional environment switch (prod/testnet/staging/dev)
# Optional overrides
# GRVT_SIGNER_PATH=./grvt-signer.cjs # replace the built-in signer if you run an external service
# GRVT_COOKIE="gravity=..." # pre-provisioned session cookie (auto-fetched via API key when absent)
# GRVT_ACCOUNT_ID=... # populated automatically after login
``` ```
或调用预置脚本:
The adapter logs in with `GRVT_API_KEY`, refreshes cookies automatically, and signs orders locally using `GRVT_API_SECRET` following GRVT's EIP712 schema. If you prefer to delegate signing to another process, set `GRVT_SIGNER_PATH`; the module should export a function (default export also works) that receives a context object (unsigned order, nonce/expiration, instrument metadata, chain id, etc.) and returns `{ signer, r, s, v, expiration, nonce }`. If you leave `GRVT_SIGNER_PATH` unset, the built-in signer uses `GRVT_API_SECRET` directly.
### 切换到 GRVT 交易所
将环境变量 `EXCHANGE` 设为 `grvt` 后,所有策略会改用 GRVT 适配器:
```bash ```bash
EXCHANGE=grvt bun run pm2:start:trend
GRVT_API_KEY=你的APIKey bun run pm2:start:maker
GRVT_API_SECRET=0xabc123... # 用于订单签名的私钥 bun run pm2:start:offset
GRVT_SUB_ACCOUNT_ID=your_sub_account_id # 具体交易子账号
GRVT_INSTRUMENT=BTC_USDT_Perp # 交易品种,需与策略一致
GRVT_SYMBOL=BTCUSDT # 可选,内部用于展示,默认由 instrument 推导
GRVT_ENV=prod # 可选:prod / testnet / staging / dev,默认 prod
# 可选覆盖项
# GRVT_SIGNER_PATH=./grvt-signer.cjs # 如果你希望由外部服务签名
# GRVT_COOKIE="gravity=..." # 预先获取到的 Cookie(若未提供,将使用 API Key 自动登录)
# GRVT_ACCOUNT_ID=... # 登录后自动填充
``` ```
完成配置后可执行 `pm2 save` 持久化进程列表。
适配器会基于 `GRVT_API_SECRET` 自动完成 EIP‑712 签名并提交订单。如果你需要自定义签名流程,可通过 `GRVT_SIGNER_PATH` 指定模块(CommonJS 或 ESM 均可)。模块需导出一个函数,接收签名上下文(未签名订单、nonce/expiration、合约元信息、链 ID 等)并返回包含 `signer/r/s/v/expiration/nonce` 字段的对象,例如: ## 测试
项目使用 Vitest
```js
// grvt-signer.cjs
module.exports = async function signOrder(context) {
// 调用你自己的签名服务,或者使用本地私钥完成签名
const signature = await mySigner(context);
return {
signer: signature.signer,
r: signature.r,
s: signature.s,
v: signature.v,
expiration: signature.expiration,
nonce: signature.nonce,
};
};
```
如未自定义 `GRVT_SIGNER_PATH`,适配器会直接使用 `GRVT_API_SECRET` 完成签名。
## Running the CLI
```bash ```bash
bun run index.ts # or: bun run dev / bun run start bun run test
bun x vitest --watch
``` ```
Pick a strategy with the arrow keys. Press `Esc` to return to the menu. The dashboard shows live order books, holdings, pending orders, and recent events. 状态完全以交易所数据为准,重新启动时会自动同步账户和挂单。
## Testing ## 常见问题
```bash - 至少准备 50–100 USDT 资金以覆盖策略运行需求。
bun run test # bun x vitest run - 杠杆需在交易所提前设置(建议 ~50 倍),程序不会自动调整。
bun run test:watch # stay in watch mode - 请确保服务器/电脑时间同步真实世界时间,避免签名过期。
``` - 账户需保持单向持仓模式。
Current tests cover the order coordinator utilities and strategy helpers; add unit tests beside new modules as you extend the system. - `.env` 未读取:确认文件位于项目根目录且变量名无误。
- API 拒绝访问:检查交易所后台权限,确保开启合约读写。
- 精度错误:同步交易对的最小价格与数量步长。
更多排查细节可参见 [简明上手指南](simple-readme.md)。
## Project Layout ## 社区与支持
- `src/config.ts` shared runtime configuration - Telegram 交流群:[https://t.me/+4fdo0quY87o4Mjhh](https://t.me/+4fdo0quY87o4Mjhh)
- `src/core/` trend & maker engines plus order coordination - 欢迎通过 Issue 或 PR 提交反馈、特性建议
- `src/exchanges/` Aster REST/WS gateway and adapters
- `src/ui/` Ink components and strategy dashboards
- `src/utils/` math helpers and strategy utilities
- `tests/` Vitest suites for critical modules
## Troubleshooting ## 风险提示
- **Websocket reconnect loops**: ensure outbound access to `wss://fstream.asterdex.com/ws` and REST endpoints. 量化交易具备风险。请先在仿真或小额账户中验证策略表现,妥善保管 API 密钥,仅开启必要权限。
- **429 or 5xx responses**: the gateway backs off automatically, but check your rate limits and credentials.
- **CLI input errors**: run in a real TTY; non-interactive shells disable keyboard shortcuts but the UI still renders.
## Contributing
Issues and PRs are welcome. When adding strategies or exchanges, follow the modular patterns in `src/core` and add tests under `tests/`.
+186
View File
@@ -0,0 +1,186 @@
# ritmex-bot
A Bun-powered multi-exchange perpetuals workstation that ships an SMA30 trend engine, a Guardian stop sentinel, and two market-making modes. It offers instant restarts, realtime market data, structured logging, and an Ink-based CLI dashboard.
* [Aster referral (30% fee discount)](https://www.asterdex.com/en/referral/4665f3)
* [Binance referral link](https://www.binance.com/join?ref=KNKCA9XC)
* [GRVT referral link](https://grvt.io/exchange/sign-up?ref=sea)
* [Backpack referral link](https://backpack.exchange/join/ritmex)
* [edgex referral link](https://pro.edgex.exchange/referral/BULL)
* [Paradex referral link](https://paradex.io/ref/xingxingjun)
* [Apex referral link](https://join.omni.apex.exchange/RITHMEX)
## Documentation Map
- [中文 README](README.md)
- [Beginner-friendly Quick Start](simple-readme.md)
## Highlights
- **Live data & risk sync** via websockets with REST fallbacks and full reconciliation on restart.
- **Trend strategy** featuring SMA30 entries, fixed stop loss, trailing stop, Bollinger bandwidth gate, and profit-lock stepping.
- **Guardian strategy** that never opens trades but mirrors your live exposure, ensuring every position has a synced stop loss and trailing stop.
- **Market-making loop** with dual-sided quote chasing, loss caps, and automatic order healing.
- **Modular architecture** decoupling engines, exchange adapters, and the Ink CLI for easy venue or strategy extensions.
## Supported Exchanges
| Exchange | Contract Type | Required Environment Variables | Notes |
| --- | --- | --- | --- |
| Aster | USDT perpetuals | `ASTER_API_KEY`, `ASTER_API_SECRET` | Default venue; works with the bootstrap script |
| GRVT | USDT perpetuals | `GRVT_API_KEY`, `GRVT_API_SECRET`, `GRVT_SUB_ACCOUNT_ID` | Switch `GRVT_ENV` between `prod` and `testnet` |
| Lighter | zkLighter perpetuals | `LIGHTER_ACCOUNT_INDEX`, `LIGHTER_API_PRIVATE_KEY` | Defaults to `LIGHTER_ENV=testnet` |
| Backpack | USDC perpetuals | `BACKPACK_API_KEY`, `BACKPACK_API_SECRET`, `BACKPACK_PASSWORD` | Set `BACKPACK_SANDBOX=true` for the sandbox |
| Paradex | StarkEx perpetuals | `PARADEX_PRIVATE_KEY`, `PARADEX_WALLET_ADDRESS` | Toggle `PARADEX_SANDBOX=true` for the testnet |
## Requirements
- Bun ≥ 1.2 (both `bun` and `bunx` on PATH)
- macOS, Linux, or Windows via WSL (native Windows works but WSL is recommended)
- Node.js is optional unless your tooling requires it
## Quick Start
### One-line bootstrap (macOS / Linux / WSL)
```bash
curl -fsSL https://github.com/discountry/ritmex-bot/raw/refs/heads/main/setup.sh | bash
```
The script installs Bun, project dependencies, collects Aster API credentials, generates `.env`, and launches the CLI. Prepare the relevant exchange API keys before running it.
### Manual installation
1. **Clone the repository**
```bash
git clone https://github.com/discountry/ritmex-bot.git
cd ritmex-bot
```
Alternatively, download the ZIP from GitHub and extract it manually.
2. **Install Bun**
- macOS / Linux: `curl -fsSL https://bun.sh/install | bash`
- Windows PowerShell: `powershell -c "irm bun.sh/install.ps1 | iex"`
Re-open the terminal and verify `bun -v` prints a version.
3. **Install dependencies**
```bash
bun install
```
4. **Create your environment file**
```bash
cp .env.example .env
```
Edit `.env` with the exchange credentials and overrides you plan to use.
5. **Launch the CLI**
```bash
bun run index.ts
```
Use the arrow keys to pick a strategy, `Enter` to start, `Esc` to go back, and `Ctrl+C` to exit.
## Shared Configuration
`.env.example` captures all defaults; the most common settings are summarised below.
| Variable | Purpose |
| --- | --- |
| `EXCHANGE` | Choose the venue (`aster` / `grvt` / `lighter` / `backpack` / `paradex`) |
| `TRADE_SYMBOL` | Contract symbol (defaults to `BTCUSDT`) |
| `TRADE_AMOUNT` | Order size in base asset units |
| `LOSS_LIMIT` | Max per-trade loss in USDT before forced close |
| `TRAILING_PROFIT` / `TRAILING_CALLBACK_RATE` | Trailing stop trigger (USDT) and pullback percentage |
| `PROFIT_LOCK_TRIGGER_USD` / `PROFIT_LOCK_OFFSET_USD` | Profit lock trigger and offset thresholds |
| `BOLLINGER_*` | Bollinger bandwidth filters for the trend engine |
| `PRICE_TICK` / `QTY_STEP` | Exchange precision filters for price and quantity |
| `POLL_INTERVAL_MS` | Trend engine polling cadence in milliseconds |
| `MAX_CLOSE_SLIPPAGE_PCT` | Allowed deviation vs mark price when closing |
| `MAKER_*` | Maker-specific knobs (quote offsets, refresh cadence, slippage guard, etc.) |
> CLI flags override environment variables at runtime:
> ```bash
> bun run index.ts --exchange grvt --strategy maker
> bun run index.ts -e lighter -s offset-maker --silent
> ```
## Exchange Setup Guides
### Aster
1. Keep `EXCHANGE=aster` (default value).
2. Supply `ASTER_API_KEY` and `ASTER_API_SECRET`.
3. Adjust `TRADE_SYMBOL`, `PRICE_TICK`, and `QTY_STEP` to match the requested market.
4. The bootstrap script auto-populates these variables; manual installs must maintain them.
### GRVT
1. Set `EXCHANGE=grvt` inside `.env`.
2. Fill `GRVT_API_KEY`, `GRVT_API_SECRET`, and `GRVT_SUB_ACCOUNT_ID`.
3. Use `GRVT_ENV=testnet` when targeting the test environment, and align `GRVT_INSTRUMENT` / `GRVT_SYMBOL`.
4. Optional: provide `GRVT_COOKIE` or a custom `GRVT_SIGNER_PATH` when reusing an existing session.
### Lighter
1. Set `EXCHANGE=lighter`.
2. Provide `LIGHTER_ACCOUNT_INDEX` and `LIGHTER_API_PRIVATE_KEY` (40-byte hex private key).
3. Switch `LIGHTER_ENV` to `mainnet`, `staging`, or `dev` when necessary; override `LIGHTER_BASE_URL` if endpoints differ.
4. `LIGHTER_SYMBOL` defaults to `BTCUSDT`; override price/size decimals when markets differ.
### Backpack
1. Set `EXCHANGE=backpack`.
2. Populate `BACKPACK_API_KEY`, `BACKPACK_API_SECRET`, and `BACKPACK_PASSWORD`; add `BACKPACK_SUBACCOUNT` if you trade from a subaccount.
3. Toggle `BACKPACK_SANDBOX=true` for the sandbox environment and verify `BACKPACK_SYMBOL` matches the contract (defaults to `BTC_USD_PERP`).
4. Enable `BACKPACK_DEBUG=true` for verbose adapter logging.
### Paradex
1. Set `EXCHANGE=paradex`.
2. Provide `PARADEX_PRIVATE_KEY` (EVM private key) and `PARADEX_WALLET_ADDRESS`.
3. The adapter connects to mainnet by default; enable `PARADEX_SANDBOX=true` and adjust `PARADEX_SYMBOL` for testnet usage.
4. Advanced tuning: use `PARADEX_USE_PRO`, `PARADEX_RECONNECT_DELAY_MS`, or debug flags as needed.
## Command Cheatsheet
```bash
bun run index.ts # Launch the CLI (default entrypoint)
bun run start # Alias for bun run index.ts
bun run dev # Development entrypoint
bun x vitest run # Execute the full Vitest suite
```
## Silent & Background Execution
### Direct silent launch
Skip the Ink menu and start a strategy directly:
```bash
bun run index.ts --strategy trend --silent
bun run index.ts --strategy maker --silent
bun run index.ts --strategy offset-maker --silent
```
Combine with `--exchange/-e` to pin the venue for that run.
### Package scripts
Convenience aliases exposed via `package.json`:
```bash
bun run start:trend:silent
bun run start:maker:silent
bun run start:offset:silent
```
### Daemonising with pm2
Install `pm2` locally (e.g. `bun add -d pm2`) and launch the process:
```bash
bunx pm2 start bun --name ritmex-trend --cwd . --restart-delay 5000 -- run index.ts --strategy trend --silent
```
You can also call the bundled scripts:
```bash
bun run pm2:start:trend
bun run pm2:start:maker
bun run pm2:start:offset
```
Run `pm2 save` afterwards if you want the process list to survive reboots.
## Testing
Powered by Vitest:
```bash
bun run test
bun x vitest --watch
```
## Troubleshooting
- Keep at least 50100 USDT in the account before deploying a live strategy.
- Configure leverage on the exchange manually (~50x is recommended); the bot will not change it.
- Ensure your server or workstation clock is in sync to avoid signature errors.
- Accounts must run in one-way position mode.
- **Env not loading**: make sure `.env` lives in the repo root and variable names are spelled correctly.
- **Permission rejected**: confirm the API key has perpetual trading scopes enabled.
- **Precision errors**: align `PRICE_TICK`, `QTY_STEP`, and `TRADE_SYMBOL` with the exchange filters.
See [simple-readme.md](simple-readme.md) for more detailed walkthroughs.
## Community & Support
- Telegram: [https://t.me/+4fdo0quY87o4Mjhh](https://t.me/+4fdo0quY87o4Mjhh)
- Issues and PRs are welcome for bug reports and feature requests
## Disclaimer
Algorithmic trading carries risk. Validate strategies with paper trading or small capital first, safeguard your API keys, and only grant the minimum required permissions.
+118 -2
View File
@@ -5,11 +5,17 @@
"name": "ritmex-bot", "name": "ritmex-bot",
"dependencies": { "dependencies": {
"@grvt/client": "^1.6.4", "@grvt/client": "^1.6.4",
"@noble/ed25519": "^3.0.0",
"@x10xchange/stark-crypto-wrapper-wasm": "^0.2.0",
"axios": "^1.12.2", "axios": "^1.12.2",
"ccxt": "^4.5.5", "bignumber.js": "^9.3.1",
"ccxt": "^4.5.12",
"date-fns": "^4.1.0",
"dotenv": "^17.2.2", "dotenv": "^17.2.2",
"ethereum-cryptography": "^2.1.3",
"ink": "^6.3.1", "ink": "^6.3.1",
"react": "^19.1.1", "react": "^19.1.1",
"starknet": "^8.5.4",
"ws": "^8.18.3", "ws": "^8.18.3",
}, },
"devDependencies": { "devDependencies": {
@@ -80,6 +86,12 @@
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@noble/curves": ["@noble/curves@1.4.2", "", { "dependencies": { "@noble/hashes": "1.4.0" } }, "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw=="],
"@noble/ed25519": ["@noble/ed25519@3.0.0", "", {}, "sha512-QyteqMNm0GLqfa5SoYbSC3+Pvykwpn95Zgth4MFVSMKBB75ELl9tX1LAVsN4c3HXOrakHsF2gL4zWDAYCcsnzg=="],
"@noble/hashes": ["@noble/hashes@1.4.0", "", {}, "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.0", "", { "os": "android", "cpu": "arm" }, "sha512-VxDYCDqOaR7NXzAtvRx7G1u54d2kEHopb28YH/pKzY6y0qmogP3gG7CSiWsq9WvDFxOQMpNEyjVAHZFXfH3o/A=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.0", "", { "os": "android", "cpu": "arm" }, "sha512-VxDYCDqOaR7NXzAtvRx7G1u54d2kEHopb28YH/pKzY6y0qmogP3gG7CSiWsq9WvDFxOQMpNEyjVAHZFXfH3o/A=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.0", "", { "os": "android", "cpu": "arm64" }, "sha512-pqDirm8koABIKvzL59YI9W9DWbRlTX7RWhN+auR8HXJxo89m4mjqbah7nJZjeKNTNYopqL+yGg+0mhCpf3xZtQ=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.0", "", { "os": "android", "cpu": "arm64" }, "sha512-pqDirm8koABIKvzL59YI9W9DWbRlTX7RWhN+auR8HXJxo89m4mjqbah7nJZjeKNTNYopqL+yGg+0mhCpf3xZtQ=="],
@@ -124,6 +136,18 @@
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.0", "", { "os": "win32", "cpu": "x64" }, "sha512-6iKDCVSIUQ8jPMoIV0OytRKniaYyy5EbY/RRydmLW8ZR3cEBhxbWl5ro0rkUNe0ef6sScvhbY79HrjRm8i3vDQ=="], "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.0", "", { "os": "win32", "cpu": "x64" }, "sha512-6iKDCVSIUQ8jPMoIV0OytRKniaYyy5EbY/RRydmLW8ZR3cEBhxbWl5ro0rkUNe0ef6sScvhbY79HrjRm8i3vDQ=="],
"@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="],
"@scure/bip32": ["@scure/bip32@1.4.0", "", { "dependencies": { "@noble/curves": "~1.4.0", "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg=="],
"@scure/bip39": ["@scure/bip39@1.3.0", "", { "dependencies": { "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ=="],
"@scure/starknet": ["@scure/starknet@1.1.0", "", { "dependencies": { "@noble/curves": "~1.7.0", "@noble/hashes": "~1.6.0" } }, "sha512-83g3M6Ix2qRsPN4wqLDqiRZ2GBNbjVWfboJE/9UjfG+MHr6oDSu/CWgy8hsBSJejr09DkkL+l0Ze4KVrlCIdtQ=="],
"@starknet-io/starknet-types-08": ["@starknet-io/types-js@0.8.4", "", {}, "sha512-0RZ3TZHcLsUTQaq1JhDSCM8chnzO4/XNsSCozwDET64JK5bjFDIf2ZUkta+tl5Nlbf4usoU7uZiDI/Q57kt2SQ=="],
"@starknet-io/starknet-types-09": ["@starknet-io/types-js@0.9.2", "", {}, "sha512-vWOc0FVSn+RmabozIEWcEny1I73nDGTvOrLYJsR1x7LGA3AZmqt4i/aW69o/3i2NN5CVP8Ok6G1ayRQJKye3Wg=="],
"@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="], "@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="],
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="], "@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
@@ -154,12 +178,18 @@
"@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], "@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
"@x10xchange/stark-crypto-wrapper-wasm": ["@x10xchange/stark-crypto-wrapper-wasm@0.2.0", "", {}, "sha512-pi7cHvDgPf9Ri9q0cvrUnCnucafB+F3ch7ia0oEJhKMY+bWvxWRYA2vve9s6UMMNV6PnmTyFZPOYptVGGdLTWA=="],
"abi-wan-kanabi": ["abi-wan-kanabi@2.2.4", "", { "dependencies": { "ansicolors": "^0.3.2", "cardinal": "^2.1.1", "fs-extra": "^10.0.0", "yargs": "^17.7.2" }, "bin": { "generate": "dist/generate.js" } }, "sha512-0aA81FScmJCPX+8UvkXLki3X1+yPQuWxEkqXBVKltgPAK79J+NB+Lp5DouMXa7L6f+zcRlIA/6XO7BN/q9fnvg=="],
"ansi-escapes": ["ansi-escapes@7.1.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-YdhtCd19sKRKfAAUsrcC1wzm4JuzJoiX4pOJqIoW2qmKj5WzG/dL8uUJ0361zaXtHqK7gEhOwtAtz7t3Yq3X5g=="], "ansi-escapes": ["ansi-escapes@7.1.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-YdhtCd19sKRKfAAUsrcC1wzm4JuzJoiX4pOJqIoW2qmKj5WzG/dL8uUJ0361zaXtHqK7gEhOwtAtz7t3Yq3X5g=="],
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
"ansicolors": ["ansicolors@0.3.2", "", {}, "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg=="],
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
@@ -168,13 +198,17 @@
"axios": ["axios@1.12.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw=="], "axios": ["axios@1.12.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw=="],
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
"bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="], "bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"ccxt": ["ccxt@4.5.5", "", { "dependencies": { "ws": "^8.8.1" } }, "sha512-AyhwTFLkx4sO985ImIOfumEBox7AHD/iqk5tPGICObUSZG6wTXg0aRzU8Hjz974aCMG4msFwLk3A/iXPKAU4wA=="], "cardinal": ["cardinal@2.1.1", "", { "dependencies": { "ansicolors": "~0.3.2", "redeyed": "~2.1.0" }, "bin": { "cdl": "./bin/cdl.js" } }, "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw=="],
"ccxt": ["ccxt@4.5.12", "", { "dependencies": { "ws": "^8.8.1" } }, "sha512-2lfL2TKjq4vBkQUQWJfDqFywhvYCZmk9r0SWC8GqA4AHZ6qozKVUJowxQTvdRsLX9jBwYSE0nc7JVurBrQ6SHg=="],
"chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
@@ -188,14 +222,22 @@
"cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="], "cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="],
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
"code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="], "code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="], "convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
@@ -224,10 +266,16 @@
"esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="], "esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], "escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
"ethereum-cryptography": ["ethereum-cryptography@2.2.1", "", { "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", "@scure/bip32": "1.4.0", "@scure/bip39": "1.3.0" } }, "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg=="],
"expect-type": ["expect-type@1.2.2", "", {}, "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA=="], "expect-type": ["expect-type@1.2.2", "", {}, "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
@@ -236,10 +284,14 @@
"form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="], "form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
"fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
"get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
@@ -248,6 +300,8 @@
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
@@ -264,6 +318,10 @@
"js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
"jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
"lossless-json": ["lossless-json@4.3.0", "", {}, "sha512-ToxOC+SsduRmdSuoLZLYAr5zy1Qu7l5XhmPWM3zefCZ5IcrzW/h108qbJUKfOlDlhvhjUK84+8PSVX0kxnit0g=="],
"loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
"magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="], "magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="],
@@ -282,6 +340,8 @@
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"pako": ["pako@2.1.0", "", {}, "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="],
"patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="], "patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
@@ -300,6 +360,10 @@
"react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="], "react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="],
"redeyed": ["redeyed@2.1.1", "", { "dependencies": { "esprima": "~4.0.0" } }, "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ=="],
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
"restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="], "restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],
"rollup": ["rollup@4.52.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.0", "@rollup/rollup-android-arm64": "4.52.0", "@rollup/rollup-darwin-arm64": "4.52.0", "@rollup/rollup-darwin-x64": "4.52.0", "@rollup/rollup-freebsd-arm64": "4.52.0", "@rollup/rollup-freebsd-x64": "4.52.0", "@rollup/rollup-linux-arm-gnueabihf": "4.52.0", "@rollup/rollup-linux-arm-musleabihf": "4.52.0", "@rollup/rollup-linux-arm64-gnu": "4.52.0", "@rollup/rollup-linux-arm64-musl": "4.52.0", "@rollup/rollup-linux-loong64-gnu": "4.52.0", "@rollup/rollup-linux-ppc64-gnu": "4.52.0", "@rollup/rollup-linux-riscv64-gnu": "4.52.0", "@rollup/rollup-linux-riscv64-musl": "4.52.0", "@rollup/rollup-linux-s390x-gnu": "4.52.0", "@rollup/rollup-linux-x64-gnu": "4.52.0", "@rollup/rollup-linux-x64-musl": "4.52.0", "@rollup/rollup-openharmony-arm64": "4.52.0", "@rollup/rollup-win32-arm64-msvc": "4.52.0", "@rollup/rollup-win32-ia32-msvc": "4.52.0", "@rollup/rollup-win32-x64-gnu": "4.52.0", "@rollup/rollup-win32-x64-msvc": "4.52.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g=="], "rollup": ["rollup@4.52.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.0", "@rollup/rollup-android-arm64": "4.52.0", "@rollup/rollup-darwin-arm64": "4.52.0", "@rollup/rollup-darwin-x64": "4.52.0", "@rollup/rollup-freebsd-arm64": "4.52.0", "@rollup/rollup-freebsd-x64": "4.52.0", "@rollup/rollup-linux-arm-gnueabihf": "4.52.0", "@rollup/rollup-linux-arm-musleabihf": "4.52.0", "@rollup/rollup-linux-arm64-gnu": "4.52.0", "@rollup/rollup-linux-arm64-musl": "4.52.0", "@rollup/rollup-linux-loong64-gnu": "4.52.0", "@rollup/rollup-linux-ppc64-gnu": "4.52.0", "@rollup/rollup-linux-riscv64-gnu": "4.52.0", "@rollup/rollup-linux-riscv64-musl": "4.52.0", "@rollup/rollup-linux-s390x-gnu": "4.52.0", "@rollup/rollup-linux-x64-gnu": "4.52.0", "@rollup/rollup-linux-x64-musl": "4.52.0", "@rollup/rollup-openharmony-arm64": "4.52.0", "@rollup/rollup-win32-arm64-msvc": "4.52.0", "@rollup/rollup-win32-ia32-msvc": "4.52.0", "@rollup/rollup-win32-x64-gnu": "4.52.0", "@rollup/rollup-win32-x64-msvc": "4.52.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g=="],
@@ -318,6 +382,8 @@
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
"starknet": ["starknet@8.9.0", "", { "dependencies": { "@noble/curves": "~1.7.0", "@noble/hashes": "~1.6.0", "@scure/base": "~1.2.1", "@scure/starknet": "1.1.0", "@starknet-io/starknet-types-08": "npm:@starknet-io/types-js@~0.8.4", "@starknet-io/starknet-types-09": "npm:@starknet-io/types-js@~0.9.1", "abi-wan-kanabi": "2.2.4", "lossless-json": "^4.2.0", "pako": "^2.0.4", "ts-mixer": "^6.0.3" } }, "sha512-uhXSCKW0SMBGckd4yVlscVz4DMkQqZ+/RTpfxZ5J46e6AuCUHOqGKv61tqgUP9GhdvgHV/XyN+kdmlyZn3rXjA=="],
"std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="], "std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="],
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
@@ -338,12 +404,16 @@
"tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="], "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="],
"ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
"undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="], "undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
"vite": ["vite@7.1.7", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA=="], "vite": ["vite@7.1.7", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA=="],
"vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="], "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
@@ -358,10 +428,56 @@
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
"yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
"@scure/bip32/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="],
"@scure/bip39/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="],
"@scure/starknet/@noble/curves": ["@noble/curves@1.7.0", "", { "dependencies": { "@noble/hashes": "1.6.0" } }, "sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw=="],
"@scure/starknet/@noble/hashes": ["@noble/hashes@1.6.1", "", {}, "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w=="],
"cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="], "cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="],
"cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"starknet/@noble/curves": ["@noble/curves@1.7.0", "", { "dependencies": { "@noble/hashes": "1.6.0" } }, "sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw=="],
"starknet/@noble/hashes": ["@noble/hashes@1.6.1", "", {}, "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w=="],
"yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"@scure/starknet/@noble/curves/@noble/hashes": ["@noble/hashes@1.6.0", "", {}, "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ=="],
"cli-truncate/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], "cli-truncate/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="],
"cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"cliui/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"starknet/@noble/curves/@noble/hashes": ["@noble/hashes@1.6.0", "", {}, "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ=="],
"yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
} }
} }
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,51 @@
<!DOCTYPE HTML>
<html>
<head>
<title>CCXT Basic example for the browser</title>
<script type="text/javascript" src="https://unpkg.com/ccxt"></script>
<script>
document.addEventListener ("DOMContentLoaded", function () {
const pollTickerContinuously = async function (exchange, symbol) {
while (true) {
try {
const ticker = await exchange.watchTicker (symbol)
const text = [
exchange.id,
symbol,
JSON.stringify (ticker, undefined, '\n\t')
]
document.getElementById ('content').innerHTML = text.join (' ');
} catch (e) {
const text = [
e.constructor.name,
e.message,
]
document.getElementById ('content').innerHTML = text.join (' ');
}
}
}
const exchange = new ccxt.pro.binance ({ enableRateLimit: true })
const symbol = 'ETH/BTC'
pollTickerContinuously (exchange, symbol)
})
</script>
</head>
<body>
<h1>Hello, CCXT!</h1>
<p>This example uses websockets to watch changes in price of ETH/BTC</p>
<pre id="content"></pre>
</body>
</html>
@@ -0,0 +1,74 @@
'use strict';
import ccxt from '../../../js/ccxt.js';
// your version must be 0.7+
console.log ('CCXT Version:', ccxt.version)
function handle (exchange, symbol, ticker) {
console.log (new Date (), exchange.id, symbol, ticker)
}
async function loop (exchange, symbol) {
while (true) {
try {
const ticker = await exchange.watchMyTrades ()
handle (exchange, symbol, ticker)
sleep( 10000 )
} catch (e) {
console.log (symbol, e)
// do nothing and retry on next loop iteration
// throw e // uncomment to break all loops in case of an error in any one of them
// break // you can also break just this one loop if it fails
}
}
}
async function main () {
const exchange = new ccxt.pro.apex ({
'apiKey': 'your api Key',
'secret': 'your api secret',
'walletAddress': 'your eth address',
'options': {
'accountId': 'your account id',
'passphrase': 'your api passphrase',
'seeds': 'your zklink omni seed',
'brokerId': '',
},
}) // usd(s)-margined contracts
exchange.setSandboxMode(true)
//
// or
//
// const exchange = new ccxt.pro.binance () // spot markets
//
// WARNING: when using the spot markets mind subscription limits!
// don't attempt to subscribe to all of them
// the exchanges will not allow that in general
// instead, specify a shorter list of symbols to subscribe to
//
// or
//
// const exchange = new ccxt.pro.binancecoinm () // coin-margined contracts
if (exchange.has['watchTicker']) {
await exchange.loadMarkets ()
// many symbols
//await Promise.all (exchange.symbols.map (symbol => loop (exchange, symbol)))
//
// or
//
// const symbols = [ 'BTC/USDT', 'ETH/USDT' ] // specific symbols
// await Promise.all (symbols.map (symbol => loop (exchange, symbol)))
//
// or
//
await loop (exchange, ['BTC-USDT','ETH-USDT']) // one symbol
} else {
console.log (exchange.id, 'does not support watchTicker yet')
}
}
main ()
@@ -0,0 +1,55 @@
'use strict';
const ccxt = require ('../../../ccxt')
console.log ('CCXT Version:', ccxt.version)
// This example will run silent and will return your balance only when the balance is updated.
//
// 1. launch the example with your keys and keep it running
// 2. go to the trading section on the website
// 3. place a order on a spot market
// 4. see your balance updated in the example
//
// Warning! This example might produce a lot of output to your screen
async function watchBalance (exchange) {
let balance = await exchange.fetchBalance ()
console.log ('---------------------------------------------------------')
console.log (exchange.iso8601 (exchange.milliseconds ()))
console.log (balance, '\n')
while (true) {
try {
const update = await exchange.watchBalance ()
balance = exchange.deep_extend (balance, update)
// it will print the balance update when the balance changes
// if the balance remains unchanged the exchange will not send it
console.log ('---------------------------------------------------------')
console.log (exchange.iso8601 (exchange.milliseconds ()))
console.log (balance, '\n')
} catch (e) {
console.log (e.constructor.name, e.message)
break
}
}
}
async function main() {
const exchange = new ccxt.pro.binance ({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
})
await exchange.loadMarkets ()
// exchange.verbose = true // uncomment for debugging purposes if necessary
await watchBalance (exchange)
await exchange.close ()
}
main ()
@@ -0,0 +1,48 @@
'use strict';
const ccxt = require ('../../../ccxt')
console.log ('CCXT Version:', ccxt.version)
let HttpsProxyAgent = undefined
try {
HttpsProxyAgent = require ('https-proxy-agent')
} catch (e) {
console.log (e.constructor.name, e.message)
console.log ("\nCould not load the HTTPS proxy agent")
console.log ("\nPlease, run this command to make sure it's properly installed:")
console.log ("\nnpm install https-proxy-agent\n")
process.exit ()
}
async function main () {
console.log ('Using proxy server', httpsProxyUrl);
// adjust for your HTTPS proxy URL
const httpsProxyUrl = process.env.https_proxy || 'https://username:password@your-proxy.com'
, httpsAgent = new HttpsProxyAgent (httpsProxyUrl)
, exchange = new ccxt.binance ({
httpsAgent: httpsAgent, // ←--------------------- httpsAgent here
options: {
'ws': {
'options': { agent: httpsAgent }, // ←--- httpsAgent here
},
},
})
const symbol = 'BTC/USDT'
await exchange.loadMarkets ()
console.log ('Markets loaded')
while (true) {
try {
const orderbook = await exchange.watchOrderBook (symbol)
console.log (exchange.iso8601 (exchange.milliseconds()), symbol, orderbook['asks'][0], orderbook['bids'][0])
} catch (e) {
console.log (e.constructor.name, e.message)
}
}
}
main ()
@@ -0,0 +1,38 @@
"use strict";
const ccxt = require ('ccxt')
const ohlcvsBySymbol = {}
function handleAllOHLCVs (exchange, ohlcvs, symbol, timeframe) {
const now = exchange.iso8601 (exchange.milliseconds ())
const lastCandle = exchange.safeValue (ohlcvs, ohlcvs.length - 1)
const datetime = exchange.iso8601 (lastCandle[0])
console.log (now, datetime, symbol, timeframe, lastCandle.slice (1))
}
async function pollOHLCV (exchange, symbol, timeframe) {
await exchange.throttle (1000) // 1000ms delay between subscriptions
while (true) {
try {
const response = await exchange.watchOHLCV (symbol, timeframe)
ohlcvsBySymbol[symbol] = response
handleAllOHLCVs(exchange, response, symbol, timeframe)
} catch (e) {
console.log (e.constructor.name, e.message)
}
}
}
async function main () {
const exchange = new ccxt.pro.binance()
const markets = await exchange.loadMarkets ()
const timeframe = '5m'
const firstOneHundredSymbols = exchange.symbols.slice (0, 100)
await Promise.all (firstOneHundredSymbols.map (symbol => pollOHLCV (exchange, symbol, timeframe)))
}
main ()
@@ -0,0 +1,64 @@
'use strict';
const ccxt = require ('ccxt');
// your version must be 0.7+
console.log ('CCXT Version:', ccxt.version)
function handle (exchange, symbol, timeframe, candles) {
const lastCandle = candles[candles.length - 1]
const lastClosingPrice = lastCandle[4]
console.log (new Date (), exchange.id, timeframe, symbol, '\t', lastClosingPrice)
}
async function loop (exchange, symbol, timeframe) {
while (true) {
try {
const candles = await exchange.watchOHLCV (symbol, timeframe)
handle (exchange, symbol, timeframe, candles)
} catch (e) {
console.log (symbol, e)
// do nothing and retry on next loop iteration
// throw e // uncomment to break all loops in case of an error in any one of them
// break // you can also break just this one loop if it fails
}
}
}
async function main () {
const exchange = new ccxt.pro.binanceusdm () // usd(s)-margined contracts
//
// or
//
// const exchange = new ccxt.pro.binance () // spot markets
//
// WARNING: when using the spot markets mind subscription limits!
// don't attempt to subscribe to all of them
// the exchanges will not allow that in general
// instead, specify a shorter list of symbols to subscribe to
//
// or
//
// const exchange = new ccxt.pro.binancecoinm () // coin-margined contracts
if (exchange.has['watchOHLCV']) {
await exchange.loadMarkets ()
const timeframe = '15m'
// many symbols
await Promise.all (exchange.symbols.map (symbol => loop (exchange, symbol, timeframe)))
//
// or
//
// const symbols = [ 'BTC/USDT', 'ETH/USDT' ] // specific symbols
// await Promise.all (symbols.map (symbol => loop (exchange, symbol, timeframe)))
//
// or
//
// await loop (exchange, 'BTC/USDT', timeframe) // one symbol
} else {
console.log (exchange.id, 'does not support watchOHLCV yet')
}
}
main ()
@@ -0,0 +1,62 @@
'use strict';
const ccxt = require ('ccxt');
// your version must be 0.7+
console.log ('CCXT Version:', ccxt.version)
function handle (exchange, symbol, ticker) {
console.log (new Date (), exchange.id, symbol, ticker['last'])
}
async function loop (exchange, symbol) {
while (true) {
try {
const ticker = await exchange.watchTicker (symbol)
handle (exchange, symbol, ticker)
} catch (e) {
console.log (symbol, e)
// do nothing and retry on next loop iteration
// throw e // uncomment to break all loops in case of an error in any one of them
// break // you can also break just this one loop if it fails
}
}
}
async function main () {
const exchange = new ccxt.pro.binanceusdm () // usd(s)-margined contracts
//
// or
//
// const exchange = new ccxt.pro.binance () // spot markets
//
// WARNING: when using the spot markets mind subscription limits!
// don't attempt to subscribe to all of them
// the exchanges will not allow that in general
// instead, specify a shorter list of symbols to subscribe to
//
// or
//
// const exchange = new ccxt.pro.binancecoinm () // coin-margined contracts
if (exchange.has['watchTicker']) {
await exchange.loadMarkets ()
// many symbols
await Promise.all (exchange.symbols.map (symbol => loop (exchange, symbol)))
//
// or
//
// const symbols = [ 'BTC/USDT', 'ETH/USDT' ] // specific symbols
// await Promise.all (symbols.map (symbol => loop (exchange, symbol)))
//
// or
//
// await loop (exchange, 'BTC/USDT') // one symbol
} else {
console.log (exchange.id, 'does not support watchTicker yet')
}
}
main ()
@@ -0,0 +1,68 @@
'use strict';
const ccxt = require ('ccxt');
const asTable = require ('as-table').configure ({ delimiter: ' | ' })
console.log ('CCXT Version:', ccxt.version)
async function loop (exchange, symbol, timeframe, completeCandlesOnly = false) {
const durationInSeconds = exchange.parseTimeframe (timeframe)
const durationInMs = durationInSeconds * 1000
while (true) {
try {
const trades = await exchange.watchTrades (symbol)
if (trades.length > 0) {
const currentMinute = parseInt (exchange.milliseconds () / durationInMs)
let ohlcvc = exchange.buildOHLCVC (trades, timeframe)
if (completeCandlesOnly) {
ohlcvc = ohlcvc.filter (candle => parseInt (candle[0] / durationInMs) < currentMinute)
}
if (ohlcvc.length > 0) {
console.log("Symbol:", symbol, "timeframe:", timeframe);
console.log ('-----------------------------------------------------------')
console.log (asTable (ohlcvc))
console.log ('-----------------------------------------------------------')
}
}
} catch (e) {
console.log (symbol, e)
// do nothing and retry on next loop iteration
// throw e // uncomment to break all loops in case of an error in any one of them
// break // you can also break just this one loop if it fails
}
}
}
async function main () {
// select the exchange
const exchange = new ccxt.pro.ftx ()
if (exchange.has['watchTrades']) {
await exchange.loadMarkets ()
// Change this value accordingly
const timeframe = '1m'
const allSymbols = exchange.symbols;
// arbitrary n symbols
const limit = 5;
// const selectedSymbols = allSymbols.slice(0, limit);
// you can also specify the symbols manually
// example:
// const selectedSymbols = ['BTC/USDT', 'LTC/USDT']
console.log(selectedSymbols);
// Use this variable to choose if only complete candles
// should be considered
const completeCandlesOnly = true
await Promise.all (selectedSymbols.map (symbol => loop (exchange, symbol, timeframe, completeCandlesOnly)))
} else {
console.log (exchange.id, 'does not support watchTrades yet')
}
}
main ()
@@ -0,0 +1,82 @@
'use strict';
const ccxt = require ('../../../ccxt');
console.log ('CCXT Version:', ccxt.version)
let ohlcvs = {}
async function log (exchange, symbol, timeframe) {
const market = exchange.market (symbol)
const duration = exchange.parseTimeframe (timeframe) * 1000
console.log (exchange.iso8601 (exchange.milliseconds ()), 'Warming up, waiting for at least one trade...')
while (!Object.keys (ohlcvs).length) {
await exchange.throttle (1)
}
let now = exchange.milliseconds ()
const start = (parseInt (now / duration) + 1) * duration
console.log (exchange.iso8601 (exchange.milliseconds ()), 'Trades started arriving, waiting till', exchange.iso8601 (start))
await exchange.sleep (start - now)
console.log (exchange.iso8601 (exchange.milliseconds ()), 'Done warming up')
for (let i = 0;; i++) {
now = exchange.milliseconds ()
let candle = Object.values (ohlcvs)[0]
console.log ('')
console.log (exchange.iso8601 (now), '------------------------------------------------------')
console.log ('Datetime ', 'Timestamp ', ... [ 'Open', 'High', 'Low', 'Close', market['base'], market['quote'] ].map (x => x.toString ().padEnd (10, ' ')))
for (let j = start; j < now; j += duration) {
if (!(j in ohlcvs)) {
ohlcvs[j] = [ j, candle[4], candle[4], candle[4], candle[4], 0, 0 ]
}
candle = exchange.safeValue (ohlcvs, j);
console.log (exchange.iso8601 (j), ... candle.map (x => x.toString ().padEnd (10, ' ')))
}
ohlcvs = exchange.indexBy (Object.values (ohlcvs).slice (-1000), 0)
await exchange.sleep(1000)
}
}
async function watch (exchange, symbol, timeframe) {
console.log ('Starting', exchange.id, symbol)
const duration = exchange.parseTimeframe (timeframe) * 1000
while (true) {
try {
const trades = await exchange.watchTrades(symbol)
for (const trade of trades) {
const timestamp = parseInt(trade['timestamp'] / duration) * duration
let candle = exchange.safe_value(ohlcvs, timestamp)
if (candle) {
candle[2] = Math.max(trade['price'], candle[2])
candle[3] = Math.min(trade['price'], candle[3])
candle[4] = trade['price']
candle[5] = exchange.parseNumber(exchange.amountToPrecision(symbol, trade['amount'] + candle[5]))
candle[6] = exchange.parseNumber(exchange.costToPrecision(symbol, trade['cost'] + candle[6]))
} else {
candle = [
timestamp,
trade['price'],
trade['price'],
trade['price'],
trade['price'],
exchange.parseNumber(exchange.amountToPrecision(symbol, trade['amount'])),
exchange.parseNumber(exchange.costToPrecision(symbol, trade['cost'])),
]
}
ohlcvs[timestamp] = candle
}
} catch (e) {
console.log (e.constructor.name, e.message)
}
}
}
async function main() {
const symbol = 'BTC/USDT'
const exchange = new ccxt.pro.binance ({ 'newUpdates': true })
await exchange.loadMarkets ()
const timeframe = '1m'
await Promise.all ([ watch (exchange, symbol, timeframe), log (exchange, symbol, timeframe) ])
await exchange.close ()
}
main()
@@ -0,0 +1,59 @@
'use strict';
const ccxt = require ('../../../ccxt');
console.log ('CCXT Version:', ccxt.version)
async function main() {
let ohlcvs = {}
const symbol = 'BTC/USDT'
const exchange = new ccxt.pro.binance ({ 'newUpdates': true })
await exchange.loadMarkets ()
const market = exchange.market (symbol)
const timeframe = '1m'
const duration = exchange.parseTimeframe (timeframe) * 1000
console.log ('Starting', exchange.id, symbol)
while (true) {
try {
const trades = await exchange.watchTrades(symbol)
for (const trade of trades) {
const timestamp = parseInt(trade['timestamp'] / duration) * duration
let candle = exchange.safe_value(ohlcvs, timestamp)
if (candle) {
candle[2] = Math.max(trade['price'], candle[2])
candle[3] = Math.min(trade['price'], candle[3])
candle[4] = trade['price']
candle[5] = exchange.parseNumber(exchange.amountToPrecision(symbol, trade['amount'] + candle[5]))
candle[6] = exchange.parseNumber(exchange.costToPrecision(symbol, trade['cost'] + candle[6]))
} else {
candle = [
timestamp,
trade['price'],
trade['price'],
trade['price'],
trade['price'],
exchange.parseNumber(exchange.amountToPrecision(symbol, trade['amount'])),
exchange.parseNumber(exchange.costToPrecision(symbol, trade['cost'])),
]
}
ohlcvs[timestamp] = candle
}
console.log ('')
console.log (exchange.iso8601 (exchange.milliseconds ()), '------------------------------------------------------')
const values = Object.values (ohlcvs).slice (-1000)
ohlcvs = exchange.indexBy (values, 0)
console.log ('Datetime ', 'Timestamp ', ... [ 'Open', 'High', 'Low', 'Close', market['base'], market['quote'] ].map (x => x.toString ().padEnd (10, ' ')))
for (let i = 0; i < values.length; i++) {
const candle = values[i]
console.log (exchange.iso8601 (candle[0]), ... candle.map (x => x.toString ().padEnd (10, ' ')))
}
} catch (e) {
console.log (e.constructor.name, e.message)
}
}
await exchange.close ()
}
main()
@@ -0,0 +1,72 @@
"use strict";
/* ------------------------------------------------------------------------ */
const ccxt = require ('../../../ccxt.js').pro
, asTable = require ('as-table') // .configure ({ print: require ('string.ify').noPretty })
, log = require ('ololog').noLocate
, ansi = require ('ansicolor').nice
;(async function test () {
let total = 0
let missing = 0
let implemented = 0
let emulated = 0
const exchanges = ccxt.exchanges
.map (id => new ccxt[id]())
.filter (exchange => exchange.has.ws)
log (
asTable (
exchanges
.map (exchange => {
let result = {};
[
'ws',
'watchOrderBook',
'watchTicker',
'watchTrades',
'watchOHLCV',
'watchBalance',
'watchOrders',
'watchMyTrades',
].forEach (key => {
total += 1
let capability = (key in exchange.has) ?
exchange.has[key].toString () :
'undefined'
if (!exchange.has[key]) {
capability = exchange.id.red.dim
missing += 1
} else if (exchange.has[key] === 'emulated') {
capability = exchange.id.yellow
emulated += 1
} else {
capability = exchange.id.green
implemented += 1
}
result[key] = capability
})
return result
})
)
)
log ('Summary:',
exchanges.length.toString ().green, 'exchanges,',
implemented.toString ().green, 'methods implemented,',
emulated.toString ().yellow, 'emulated,',
missing.toString ().red, 'missing,',
total.toString (), 'total')
}) ()
@@ -0,0 +1,46 @@
'use strict';
const ccxt = require ('../../../ccxt')
console.log ("CCXT Pro Version:", ccxt.version)
const orderbooks = {}
let run = true
async function watchOrderBook (exchange, symbol) {
while (run) {
try {
const orderbook = await exchange.watchOrderBook (symbol)
orderbooks[symbol] = orderbook
console.log (exchange.iso8601 (exchange.milliseconds ()), orderbook['datetime'], orderbook['nonce'], symbol, orderbook['asks'][0], orderbook['bids'][0])
} catch (e) {
console.log (e.constructor.name, e.message)
}
}
}
async function stop (exchange) {
await exchange.sleep (10000)
run = false
await exchange.close ()
}
async function main () {
const exchange = new ccxt.pro.binance ()
await exchange.loadMarkets ()
exchange.verbose = true
const symbols = [
'BTC/USDT',
'ETH/USDT',
]
stop (exchange).then (() => {})
await Promise.all (symbols.map (symbol => watchOrderBook (exchange, symbol)))
console.log ('Sleeping for a moment...')
await exchange.sleep (10000)
console.log ('Done')
}
main ()
@@ -0,0 +1,30 @@
'use strict';
const ccxt = require ('ccxt')
console.log ("CCXT Pro Version:", ccxt.version)
async function loop (exchange, method, symbol) {
while (true) {
try {
const orderbook = await exchange[method] (symbol)
console.log (exchange.iso8601 (exchange.milliseconds ()), orderbook['datetime'], orderbook['nonce'], symbol, orderbook['asks'][0], orderbook['bids'][0])
} catch (e) {
console.log (e.constructor.name, e.message)
}
}
}
async function main () {
const exchange = new ccxt.pro.gateio ({
'options': {'defaultType':'swap'}
})
await exchange.loadMarkets ()
// exchange.verbose = true // uncomment for debugging purposes if necessary
const symbols = [
'ANC/USDT:USDT',
]
await Promise.all (symbols.map (symbol => loop (exchange, 'fetchOrderBook', symbol)))
}
main ()
@@ -0,0 +1,23 @@
const ccxt = require ('../../../ccxt')
console.log ('CCXT Pro version:', ccxt.version)
async function main () {
const exchange = new ccxt.pro.gateio ({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
})
await exchange.loadMarkets ()
exchange.verbose = true
while (true) {
try {
const response = await exchange.watchBalance ()
console.log (new Date (), response)
} catch (e) {
console.log (e.constructor.name, e.message)
await exchange.sleep (1000)
}
}
}
main ()
@@ -0,0 +1,71 @@
'use strict';
const ccxt = require ('ccxt')
console.log ("CCXT Pro Version:", ccxt.version)
const orderbooks = {}
async function watchAllSymbols (exchange, symbols) {
while (true) {
const keys = Object.keys (orderbooks);
if (symbols.length === keys.length) {
console.log ('\n\n\n\n\n')
console.log ('----------------------------------------------------')
console.log ('All orderbooks received at least one update:');
for (let i = 0; i < symbols.length; i++) {
const symbol = symbols[i]
const orderbook = orderbooks[symbol]
console.log (exchange.iso8601 (exchange.milliseconds ()), orderbook['datetime'], orderbook['nonce'], symbol, orderbook['asks'][0], orderbook['bids'][0])
}
console.log ('----------------------------------------------------')
console.log ('\n\n\n\n\n')
// process.exit () // stop here if you want
break
} else {
await exchange.sleep (1000);
}
}
}
async function watchOrderBook (exchange, symbol) {
while (true) {
try {
const orderbook = await exchange.watchOrderBook (symbol)
orderbooks[symbol] = orderbook
console.log (exchange.iso8601 (exchange.milliseconds ()), orderbook['datetime'], orderbook['nonce'], symbol, orderbook['asks'][0], orderbook['bids'][0])
} catch (e) {
console.log (e.constructor.name, e.message)
}
}
}
async function main () {
const exchange = new ccxt.pro.gateio ({
'options': {
'defaultType': 'swap',
},
})
await exchange.loadMarkets ()
// exchange.verbose = true // uncomment for debugging purposes if necessary
const symbols = [
// 'SOS/USDT:USDT',
// 'JASMY/USDT:USDT',
// 'SLP/USDT:USDT',
'ACH/USDT:USDT',
'MKISHU/USDT:USDT',
// 'GMT/USDT:USDT',
// 'ASTR/USDT:USDT',
'RAMP/USDT:USDT',
'RSR/USDT:USDT',
// 'RACA/USDT:USDT',
// 'ROOK/USDT:USDT',
// 'ROSE/USDT:USDT',
]
await Promise.all ([
watchAllSymbols (exchange, symbols),
... symbols.map (symbol => watchOrderBook (exchange, symbol))
])
}
main ()
@@ -0,0 +1,43 @@
'use strict';
const ccxt = require ('../../../ccxt');
let stop = false
async function shutdown (milliseconds) {
await ccxt.sleep (10000)
stop = true
}
async function watchOrderBook (exchangeId, symbol) {
const exchange = new ccxt.pro[exchangeId] ()
await exchange.loadMarkets ()
// exchange.verbose = true
while (!stop) {
try {
const orderbook = await exchange.watchOrderBook (symbol)
console.log (new Date (), exchange.id, symbol, orderbook['asks'][0], orderbook['bids'][0])
} catch (e) {
console.log (symbol, e)
stop = true
break
}
}
await exchange.close ()
}
async function main () {
const streams = {
'binance': 'BTC/USDT',
'ftx': 'BTC/USDT',
};
await Promise.all ([
shutdown (10000),
... Object.entries (streams).map (([ exchangeId, symbol ]) => watchOrderBook (exchangeId, symbol))
])
}
main()
@@ -0,0 +1,37 @@
'use strict';
const ccxt = require ('../../../ccxt');
(async () => {
const streams = {
'binance': 'BTC/USDT',
'bittrex': 'BTC/USDT',
'poloniex': 'BTC/USDT',
'bitfinex': 'BTC/USDT',
'hitbtc': 'BTC/USDT',
'upbit': 'BTC/USDT',
'coinbasepro': 'BTC/USD',
'ftx': 'BTC/USDT',
'okex': 'BTC/USDT',
'gateio': 'BTC/USDT',
};
await Promise.all (Object.keys (streams).map (exchangeId =>
(async () => {
const exchange = new ccxt.pro[exchangeId] ({ enableRateLimit: true })
const symbol = streams[exchangeId]
while (true) {
try {
const orderbook = await exchange.watchOrderBook (symbol)
console.log (new Date (), exchange.id, symbol, orderbook['asks'][0], orderbook['bids'][0])
} catch (e) {
console.log (symbol, e)
}
}
}) ())
)
}) ()
@@ -0,0 +1,48 @@
'use strict';
const ccxt = require ('ccxt')
, exchange = new ccxt.okex ({
apiKey: 'YOUR_API_KEY',
secret: 'YOUR_API_SECRET',
password: 'YOUR_API_PASSWORD',
enableRateLimit: true,
options: { defaultType: 'futures' },
})
, symbol = 'BTC/USDT:USDT-201225'
, amount = 1 // how may contracts
, price = undefined // or your limit price
, side = 'buy' // or 'sell'
, type = '1' // 1 open long, 2 open short, 3 close long, 4 close short for futures
, order_type = '4' // 0 = limit order, 4 = market order
console.log ('CCXT Pro Version: ', ccxt.version)
async function main () {
try {
await exchange.loadMarkets ()
// exchange.verbose = true // uncomment for debugging
// open long market price order
const order = await exchange.createOrder(symbol, 'market', side, amount, price, { type });
// --------------------------------------------------------------------
// open long market price order
// const order = await exchange.createOrder(symbol, type, side, amount, price, { order_type });
// --------------------------------------------------------------------
// close short market price order
// const order = await exchange.createOrder(symbol, 'market', side, amount, price, { type, order_type });
// --------------------------------------------------------------------
// close short market price order
// const order = await exchange.createOrder(symbol, '4', side, amount, price, { order_type });
// ...
console.log (order)
} catch (e) {
console.log (e.constructor.name, e.message)
}
}
main ()
@@ -0,0 +1,54 @@
const ccxt = require ('ccxt')
console.log ('Node.js:', process.version)
console.log ('CCXT Pro v' + ccxt.version)
const exchange = new ccxt.pro.okex ({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_API_SECRET',
'password': 'YOUR_API_PASWORD'
})
async function watchBalance (code) {
while (true) {
const balance = await exchange.watchBalance ({
'code': code,
})
console.log ('New', code, 'balance is: ', balance[code])
}
}
async function createOrder (symbol, type, side, amount, price = undefined, params = {}) {
console.log ('Creating', symbol, type, 'order')
const order = await exchange.createOrder (symbol, type, side, amount, price, params);
console.log (symbol, type, side, 'order created')
console.log (order)
}
;(async () => {
await exchange.loadMarkets ()
console.log (exchange.id, 'markets loaded')
const symbol = 'ETH/USDT'
const market = exchange.market (symbol)
const quote = market['quote']
// run in parallel without await
console.log ('Watching', quote, 'balance')
watchBalance (quote)
// wait a bit to allow it to subscribe
await exchange.sleep (3000)
const ticker = await exchange.watchTicker (symbol)
const type = 'market'
const side = 'buy'
const amount = 0.1
const price = ticker['bid']
await createOrder (symbol, type, side, amount, price)
}) ()
@@ -0,0 +1,31 @@
import ccxt from '../../../js/ccxt.js';
async function main() {
const exchange = new ccxt.pro.okx()
await exchange.loadMarkets()
// exchange.verbose = true
while (true) {
try {
// don't do this, specify a list of symbols to watch for watchTickers
// or a very large subscription message will crash your WS connection
// const tickers = await exchange.watchTickers ()
// do this instead
const symbols = [
'ETH/BTC',
'BTC/USDT',
'ETH/USDT',
// ...
]
const tickers = await exchange.watchTickers (symbols)
symbols = Object.keys(tickers)
console.log (new Date(), 'received', symbols.length, 'symbols', ... symbols.slice(0,5).join(', '), '...')
} catch (e) {
console.log (new Date(), e.constructor.name, e.message)
break
}
}
}
main()
@@ -0,0 +1,46 @@
'use strict';
const ccxt = require ('ccxt')
console.log ('CCXT Version:', ccxt.version)
async function watchOrders(exchange) {
while (true) {
try {
const orders = await exchange.watchOrders() // await here
console.log(orders)
} catch (e) {
console.log(e)
}
}
}
async function watchBalance(exchange) {
while (true) {
try {
const balance = await exchange.watchBalance() // await here
console.log(balance)
} catch (e) {
console.log(e)
}
}
}
async function main() {
const exchange = new ccxt.pro.binance({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
'password': 'IF NECESSARY',
// etc...
})
await exchange.loadMarkets () // await here
// exchange.verbose = true // uncomment for debugging purposes if necessary
watchOrders(exchange) // no await
watchBalance(exchange) // no await
await exchange.close()
}
main()
@@ -0,0 +1,26 @@
'use strict';
const ccxt = require ('ccxt');
(async () => {
const exchange = new ccxt.pro.binance ({ enableRateLimit: true })
const symbols = [ 'BTC/USDT', 'ETH/BTC', 'ETH/USDT' ]
const loop = async (symbol) => {
while (true) {
try {
const orderbook = await exchange.watchOrderBook (symbol)
console.log (new Date (), symbol, orderbook['asks'][0], orderbook['bids'][0])
} catch (e) {
console.log (symbol, e)
// do nothing and retry on next loop iteration
// throw e // uncomment to break all loops in case of an error in any one of them
// break // you can also break just this one loop if it fails
}
}
}
await Promise.all (symbols.map (symbol => loop (symbol)))
}) ()
@@ -0,0 +1,28 @@
'use strict';
const ccxt = require ('ccxt');
(async () => {
const exchange = new ccxt.pro.binance ({ enableRateLimit: true })
const symbols = [ 'BTC/USDT', 'ETH/BTC', 'ETH/USDT' ]
await Promise.all (symbols.map (symbol =>
(async () => {
while (true) {
try {
const orderbook = await exchange.watchOrderBook (symbol)
console.log (new Date (), symbol, orderbook['asks'][0], orderbook['bids'][0])
} catch (e) {
console.log (symbol, e)
// do nothing and retry on next loop iteration
// throw e // uncomment to break all loops in case of an error in any one of them
// break // you can also break just this one loop if it fails
}
}
}) ())
)
}) ()
@@ -0,0 +1,30 @@
'use strict';
const ccxt = require ('ccxt')
, SocksProxyAgent = require ('socks-proxy-agent')
, socks = 'socks://127.0.0.1:7000'
, socksAgent = new SocksProxyAgent (socks)
, exchange = new ccxt.binance ({
enableRatLimit: true,
httpsAgent: socksAgent, // ←--------------------- socksAgent here
options: {
'ws': {
'options': { agent: socksAgent }, // ←--- socksAgent here
},
},
})
;(async () => {
console.log (socks)
const symbol = 'BTC/USDT'
await exchange.loadMarkets ()
console.log ('Markets loaded')
while (true) {
try {
const orderbook = await exchange.watchOrderBook (symbol)
console.log (exchange.iso8601 (exchange.milliseconds()), symbol, orderbook['asks'][0], orderbook['bids'][0])
} catch (e) {
console.log (e.constructor.name, e.message)
}
}
}) ()
@@ -0,0 +1,40 @@
'use strict';
const ccxt = require ('../../../ccxt');
async function watchOrderBook (exchange, symbol) {
while (true) {
try {
const method = exchange.has.watchOrderBook ? 'watchOrderBook' : 'fetchOrderBook'
const orderbook = await exchange[method](symbol)
console.log (new Date (), exchange.id, symbol, orderbook['asks'][0], orderbook['bids'][0])
} catch (e) {
console.log (symbol, e)
process.exit ()
}
}
}
async function watchExchange (exchangeId, symbols) {
const exchange = new ccxt.pro[exchangeId] ()
await exchange.loadMarkets ()
await Promise.all (symbols.map (symbol => watchOrderBook (exchange, symbol)))
}
async function main () {
const streams = {
'ftx': [
'BTC/USDT',
'ETH/BTC',
],
'coinex': [
'BTC/USDT',
'ETH/BTC',
],
};
const entries = Object.entries (streams)
await Promise.all (entries.map (([ exchangeId, symbols ]) => watchExchange (exchangeId, symbols)))
}
main()
@@ -0,0 +1,38 @@
'use strict';
const ccxt = require ('../../../ccxt');
async function watchOrderBook (exchange, symbol) {
while (true) {
try {
const orderbook = await exchange.watchOrderBook (symbol)
console.log (new Date (), exchange.id, symbol, orderbook['asks'][0], orderbook['bids'][0])
} catch (e) {
console.log (symbol, e)
}
}
}
async function watchExchange (exchangeId, symbols) {
const exchange = new ccxt.pro[exchangeId] ()
await exchange.loadMarkets ()
await Promise.all (symbols.map (symbol => watchOrderBook (exchange, symbol)))
}
async function main () {
const streams = {
'binance': [
'BTC/USDT',
'ETH/BTC',
],
'ftx': [
'BTC/USDT',
'ETH/BTC',
],
};
const entries = Object.entries (streams)
await Promise.all (entries.map (([ exchangeId, symbols ]) => watchExchange (exchangeId, symbols)))
}
main()
@@ -0,0 +1,40 @@
'use strict';
const ccxt = require ('../../../ccxt');
console.log ('CCXT Version:', ccxt.version)
async function watchTickerLoop (exchange, symbol) {
// exchange.verbose = true // uncomment for debugging purposes if necessary
while (true) {
try {
const ticker = await exchange.watchTicker (symbol)
console.log (new Date (), exchange.id, symbol, ticker['last'])
} catch (e) {
console.log (symbol, e)
// do nothing and retry on next loop iteration
// throw e // uncomment to break all loops in case of an error in any one of them
// break // you can also break just this one loop if it fails
}
}
}
async function exchangeLoop (exchangeId, symbols) {
const exchange = new ccxt.pro[exchangeId]()
await exchange.loadMarkets ()
const loops = symbols.map (symbol => watchTickerLoop (exchange, symbol))
await Promise.all (loops)
await exchange.close ()
}
async function main () {
const exchanges = {
'binance': [ 'BTC/USDT', 'ETH/USDT' ],
'ftx': [ 'BTC/USD', 'ETH/USD' ],
}
const loops = Object.entries (exchanges).map (([ exchangeId, symbols ]) => exchangeLoop (exchangeId, symbols))
await Promise.all (loops)
}
main ()
@@ -0,0 +1,27 @@
'use strict';
const ccxt = require ('ccxt');
async function watchOrderBook (exchangeId, symbol) {
const exchange = new ccxt.pro[exchangeId] ()
while (true) {
try {
const orderbook = await exchange.watchOrderBook (symbol)
console.log (new Date (), exchange.id, symbol, orderbook['asks'][0], orderbook['bids'][0])
} catch (e) {
console.log (symbol, e)
}
}
}
async function main () {
const streams = {
'binance': 'BTC/USDT',
'ftx': 'BTC/USDT',
};
await Promise.all (Object.entries (streams).map (([ exchangeId, symbol ]) => watchOrderBook (exchangeId, symbol)))
}
main()
@@ -0,0 +1,43 @@
'use strict';
const ccxt = require ('../../../ccxt');
console.log ('CCXT Version:', ccxt.version);
async function watchExchange (exchangeId, symbol) {
const exchange = new ccxt.pro[exchangeId] ({
newUpdates: true,
})
await exchange.loadMarkets ();
// exchange.verbose = true // uncomment for debugging purposes if necessary
while (true) {
try {
const trades = await exchange.watchTrades (symbol)
for (let i = 0; i < trades.length; i++) {
const trade = trades[i]
console.log (exchange.iso8601 (exchange.milliseconds ()), exchange.id, trade['symbol'], trade['id'], trade['datetime'], trade['price'], trade['amount'])
}
} catch (e) {
console.log (symbol, e)
}
}
}
async function main () {
const streams = {
'binance': 'BTC/USDT',
'okex': 'BTC/USDT',
'kraken': 'BTC/USD',
};
const values = Object.entries (streams)
const promises = values.map (([ exchangeId, symbol ]) => watchExchange (exchangeId, symbol))
await Promise.all (promises)
}
main ()
@@ -0,0 +1,42 @@
'use strict';
const ccxt = require ('../../../ccxt');
(async () => {
const streams = {
'binance': 'BTC/USDT',
'okex': 'BTC/USDT'
};
await Promise.all (Object.keys (streams).map (exchangeId =>
(async () => {
const exchange = new ccxt.pro[exchangeId] ({
enableRateLimit: true,
options: {
tradesLimit: 100, // lower = better, 1000 by default
},
})
const symbol = streams[exchangeId]
let lastId = ''
while (true) {
console.log ('---')
try {
const trades = await exchange.watchTrades (symbol)
for (let i = 0; i < trades.length; i++) {
const trade = trades[i]
if (trade['id'] > lastId) {
console.log (exchange.iso8601 (exchange.milliseconds ()), exchange.id, trade['symbol'], trade['id'], trade['datetime'], trade['price'], trade['amount'])
lastId = trade['id']
}
}
} catch (e) {
console.log (symbol, e)
}
}
}) ())
)
}) ()
@@ -0,0 +1,29 @@
'use strict';
const ccxt = require ('ccxt');
console.log ('CCXT Version:', ccxt.version)
async function watchTrades (exchange, symbol) {
while (true) {
try {
const trades = await exchange.watchTrades (symbol)
console.log (new Date (), exchange.id, symbol, trades.length, 'trades')
} catch (e) {
console.log (symbol, e)
}
}
}
async function main () {
const symbols = [ 'USDT/THB', 'BTC/THB', 'ETH/THB' ]
const exchange = new ccxt.pro.zipmex({
'newUpdates': true
})
const markets = await exchange.loadMarkets ()
exchange.verbose = true
await Promise.all (symbols.map ((symbol) => watchTrades (exchange, symbol)))
}
main()
@@ -0,0 +1,29 @@
// see this issue for details
// https://github.com/ccxt/ccxt/issues/6659
const ccxt = require ('ccxt')
const exchange = new ccxt.pro.kraken ()
function yellow (s) {
return '\x1b[33m' + s + '\x1b[0m'
}
async function runWs () {
while (1) {
const book = await exchange.watchOrderBook ('ETH/BTC')
console.log (new Date (), 'WS ', book['datetime'], book['bids'][0][0], book['asks'][0][0])
}
}
async function runRest () {
while (1) {
const book = await exchange.fetchOrderBook ('ETH/BTC')
const timestamp = new Date (exchange.last_response_headers['Date']).getTime ()
const datetime = exchange.iso8601 (timestamp)
console.log (new Date (), 'REST', yellow (datetime), book['bids'][0][0], book['asks'][0][0])
}
}
runWs ()
runRest ()
@@ -0,0 +1,76 @@
<?php
error_reporting(E_ALL);
date_default_timezone_set('UTC');
$root = dirname(dirname(dirname(dirname(__FILE__))));
include $root . '/ccxt.php';
function create_exchange($exchange_id, $config) {
$keys = array(
'ids',
'markets',
'markets_by_id',
'currencies',
'currencies_by_id',
'base_currencies',
'quote_currencies',
'symbols',
);
$exchange_class = '\\ccxt\\pro\\' . $exchange_id;
$exchange = new $exchange_class($config);
$markets_on_disk = "./{$exchange_id}.markets.json";
$exchange->verbose = true; // this is a debug output to demonstrate which networking calls are being issued
if (file_exists($markets_on_disk)) {
$cache = json_decode(file_get_contents($markets_on_disk), true);
foreach ($keys as $key) {
$exchange->{$key} = $cache[$key];
}
} else {
$markets = yield $exchange->load_markets();
$cache = array();
foreach ($keys as $key) {
$cache[$key] = $exchange->{$key};
}
file_put_contents($markets_on_disk, json_encode($cache));
}
return $exchange;
}
$exchanges = array(
array('binance', array(
'id' => 'binance1',
'apiKey' => 'YOUR_API_KEY_HERE',
'secret' => 'YOUR_SECRET_HERE',
)),
array('binance', array(
'id' => 'binance2',
'apiKey' => 'YOUR_API_KEY_HERE',
'secret' => 'YOUR_SECRET_HERE',
)),
);
$loop = function($exchange_id, $config) {
$exchange = yield create_exchange($exchange_id, $config);
$exchange->verbose = true;
while (true) {
$response = yield $exchange->watch_balance();
print('--------------------------------------------------------------');
print($exchange->id);
print($response);
}
};
foreach ($exchanges as $exchange) {
\React\Async\coroutine($loop, $exchange[0], $exchange[1]);
}
@@ -0,0 +1,27 @@
<?php
$root = dirname(dirname(dirname(dirname(__FILE__))));
include $root . '/ccxt.php';
$config = array('enableRateLimit' => true);
$binance = new \ccxt\pro\binance($config);
$bittrex = new \ccxt\pro\bittrex($config);
$symbol = "BTC/USDT";
$loop = function($exchange, $symbol) {
echo 'got inside' . PHP_EOL;
for ($i = 0; $i < 5; $i++) {
$ticker = yield $exchange->watch_ticker($symbol);
print_ticker($ticker, $exchange->id, $symbol);
}
};
function print_ticker($ticker, $exchange_name, $symbol) {
$bid = $ticker['bid'];
$ask = $ticker['ask'];
echo "$exchange_name $symbol - bid: $bid <> ask: $ask" . PHP_EOL;
}
\React\Async\coroutine($loop, $bittrex, $symbol);
\React\Async\coroutine($loop, $binance, $symbol);
@@ -0,0 +1,30 @@
<?php
$root = dirname(dirname(dirname(dirname(__FILE__))));
include $root . '/ccxt.php';
$id = 'aax';
$exchange_class = '\\ccxt\\pro\\' . $id;
$exchange = new $exchange_class(array(
'enableRateLimit' => true,
));
$symbols = array('BTC/USDT', 'ETH/USDT', 'ETH/BTC');
function print_orderbook($orderbook, $symbol) {
$id = isset($orderbook['nonce']) ? $orderbook['nonce'] : $orderbook['datetime'];
echo $id, ' ', $symbol, ' ',
count($orderbook['asks']), ' asks ', json_encode($orderbook['asks'][0]), ' ',
count($orderbook['bids']), ' bids ', json_encode($orderbook['bids'][0]), "\n";
}
$loop = function($exchange, $symbol) {
while (true) {
$orderbook = yield $exchange->watch_order_book($symbol);
print_orderbook($orderbook, $symbol);
}
};
foreach ($symbols as $symbol) {
\React\Async\coroutine($loop, $exchange, $symbol);
}
@@ -0,0 +1,41 @@
<?php
error_reporting(E_ALL);
date_default_timezone_set('UTC');
include dirname(dirname(dirname(dirname(__FILE__)))). '/ccxt.php';
$binance_id = '\\ccxt\\pro\\binance';
$binance_exchange = new $binance_id( /*[ 'apiKey' => _YOUR_APIKEY_HERE_, 'secret' => _YOUR_SECRET_HERE_ ]*/ );
$ftx_id = '\\ccxt\\pro\\ftx';
$ftx_exchange = new $ftx_id( /*['apiKey' => _YOUR_APIKEY_HERE_, 'secret' => _YOUR_SECRET_HERE_]*/ );
$wrapper_func = function($exchange, $symbol, $method_name) {
if ($exchange->has[$method_name]) {
print ("Starting $method_name for $exchange->id -> $symbol\n");
while (true) {
try {
$orderbook = yield $exchange->$method_name($symbol);
print("$exchange->id -> $method_name -> $symbol : " . substr(json_encode($orderbook), 0 , 70) . "...\n");
} catch (\Exception $ex) {
print($ex->getMessage());
sleep(5);
}
}
} else {
print ($exchange->id . " API yet doesnt support $method_name");
}
};
function runAsync (...$args) {
\React\Async\coroutine(...$args);
}
// *** uncomment whichever methods you want to test ***
// runAsync($wrapper_func, ...[$exchange, 'ETH/USDT', 'watchOrderBook']);
// runAsync($wrapper_func, ...[$exchange, 'ETH/USDT', 'watchTicker']);
// runAsync($wrapper_func, ...[$exchange, 'ETH/USDT', 'watchOrders']);
// runAsync($wrapper_func, ...[$exchange, 'ETH/USDT', 'watchMyTrades']);
runAsync($wrapper_func, ...[$binance_exchange, 'SOL/USDT', 'watchTrades']);
runAsync($wrapper_func, ...[$ftx_exchange, 'ETH/USDT', 'watchTrades']);
@@ -0,0 +1,34 @@
import ccxt.pro
from pprint import pprint
from asyncio import run
print('CCXT Version:', ccxt.__version__)
async def main():
exchange = ccxt.pro.binance({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
})
markets = await exchange.load_markets()
# exchange.verbose = True # uncomment for debugging purposes if necessary
symbol = 'ETH/BTC'
type = 'limit' # or 'market'
side = 'sell' # or 'buy'
amount = 1.0
price = 0.060154 # or None
order = await exchange.create_order(symbol, type, side, amount, price)
canceled = await exchange.cancel_order(order['id'], order['symbol'])
pprint(canceled)
await exchange.close()
run(main())
@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
import ccxt.pro
from asyncio import run
print('CCXT Version:', ccxt.__version__)
# This example will run silent and will return your balance only when the balance is updated.
#
# 1. launch the example with your keys and keep it running
# 2. go to the trading section on the website
# 3. place a order on a spot market
# 4. see your balance updated in the example
#
# Warning! This example might produce a lot of output to your screen
async def watch_balance(exchange):
await exchange.load_markets()
# exchange.verbose = True # uncomment for debugging purposes if necessary
balance = await exchange.fetch_balance()
print('---------------------------------------------------------')
print(exchange.iso8601(exchange.milliseconds()))
print(balance)
print('')
while True:
try:
update = await exchange.watch_balance()
balance = exchange.deep_extend(balance, update)
# it will print the balance update when the balance changes
# if the balance remains unchanged the exchange will not send it
print('---------------------------------------------------------')
print(exchange.iso8601(exchange.milliseconds()))
print(balance)
print('')
except Exception as e:
print('watch_balance() failed')
print(type(e).__name__, str(e))
break
async def main():
exchange = ccxt.pro.binance({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
})
await watch_balance(exchange)
await exchange.close()
run(main())
@@ -0,0 +1,39 @@
import ccxt
import ccxt.pro
import asyncio
from pprint import pprint
loop = asyncio.get_event_loop()
async def print_balance(exchange, market_type):
while True:
try:
balance = await exchange.watch_balance({'type': market_type})
pprint(balance)
print('balance of ' + market_type, balance)
print(exchange.options[market_type])
except ccxt.BaseError as e:
print(type(e), e)
except Exception as e:
print(type(e), e)
async def main():
exchange = ccxt.pro.binance({
"apiKey": "",
"secret": "",
'enableRateLimit': True,
'newUpdates': True,
})
# you must make an order a transfer first to the websocket to send updates
asyncio.ensure_future(print_balance(exchange, 'future'))
asyncio.ensure_future(print_balance(exchange, 'delivery')) # inverse futures settled in BTC
asyncio.ensure_future(print_balance(exchange, 'spot'))
asyncio.run(main())
asyncio.ensure_future(main())
loop.run_forever()
@@ -0,0 +1,22 @@
import ccxt.pro
from asyncio import run
async def main():
exchange = ccxt.pro.binance({
'options': {
'defaultType': 'future',
},
})
symbol = 'BTC/USDT'
while True:
try:
orderbook = await exchange.watch_order_book(symbol)
print(orderbook['bids'][0], orderbook['asks'][0])
except Exception as e:
print(type(e).__name__, str(e))
await exchange.close()
run(main())
@@ -0,0 +1,27 @@
import ccxt.pro as ccxt
from asyncio import run
print('CCXT Version:', ccxt.__version__)
async def main():
exchange = ccxt.pro.binance({
'options': {
'defaultType': 'future', # spot, margin, future, delivery
},
})
# or
# exchange = ccxt.pro.binanceusdm()
# or
# exchange = ccxt.pro.binancecoinm()
symbol = 'BTC/USDT'
while True:
try:
orderbook = await exchange.watch_order_book(symbol)
print(exchange.iso8601(exchange.milliseconds()), exchange.id, symbol, 'ask:', orderbook['asks'][0], 'bid:', orderbook['bids'][0])
except Exception as e:
print(type(e).__name__, str(e))
break
await exchange.close()
run(main())
@@ -0,0 +1,42 @@
import ccxt.pro
from asyncio import run, gather
print('CCXT Pro version', ccxt.pro.__version__)
async def watch_order_book(exchange, symbol):
while True:
try:
orderbook = await exchange.watch_order_book(symbol)
datetime = exchange.iso8601(exchange.milliseconds())
print(datetime, orderbook['nonce'], symbol, orderbook['asks'][0], orderbook['bids'][0])
except Exception as e:
print(type(e).__name__, str(e))
break
async def reload_markets(exchange, delay):
while True:
try:
await exchange.sleep(delay)
markets = await exchange.load_markets(True)
datetime = exchange.iso8601(exchange.milliseconds())
print(datetime, 'Markets reloaded')
except Exception as e:
print(type(e).__name__, str(e))
break
async def main():
exchange = ccxt.pro.binance()
await exchange.load_markets()
# exchange.verbose = True
symbol = 'BTC/USDT'
delay = 60000 # every minute = 60 seconds = 60000 milliseconds
loops = [watch_order_book(exchange, symbol), reload_markets(exchange, delay)]
await gather(*loops)
await exchange.close()
run(main())
@@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
import ccxt.pro
from asyncio import gather, run
orderbooks = {}
def handle_all_orderbooks(orderbooks):
print('We have the following orderbooks:')
for id, orderbooks_by_symbol in orderbooks.items():
for symbol in orderbooks_by_symbol.keys():
orderbook = orderbooks_by_symbol[symbol]
print(ccxt.pro.Exchange.iso8601(orderbook['timestamp']), id, symbol, orderbook['asks'][0], orderbook['bids'][0])
async def symbol_loop(exchange, id, symbol):
print('Starting', id, symbol)
while True:
try:
orderbook = await exchange.watch_order_book(symbol)
orderbooks[id] = orderbooks.get(id, {})
orderbooks[id][symbol] = orderbook
print('===========================================================')
#
# here you can do what you want
# with the most recent versions of each orderbook you have so far
#
# you can also wait until all of them are available
# by just looking into all the orderbooks and counting them
#
# we just print them here to keep this example simple
#
handle_all_orderbooks(orderbooks)
except Exception as e:
print(str(e))
# raise e # uncomment to break all loops in case of an error in any one of them
break # you can break just this one loop if it fails
async def exchange_loop(id, config):
print('Starting', id)
exchange = getattr(ccxt.pro, config['id'])({
'options': config['options'],
})
loops = [symbol_loop(exchange, id, symbol) for symbol in config['symbols']]
await gather(*loops)
await exchange.close()
async def main():
configs = {
'Exchange A (Binance spot)': {
'id': 'binance',
'symbols': ['BTC/USDT', 'ETH/BTC','ETH/USDT'],
'options': {
'defaultType': 'spot',
},
},
'Exchange B (Binance futures)': {
'id': 'binance',
'symbols': ['BTC/USDT', 'ETH/USDT'],
'options': {
'defaultType': 'future',
},
},
}
loops = [exchange_loop(id, config) for id, config in configs.items()]
await gather(*loops)
run(main())
@@ -0,0 +1,51 @@
import ccxt.pro as ccxt
import asyncio
orderbooks = {}
def when_orderbook_changed(exchange_spot, symbol, orderbook):
# this is a common handler function
# it is called when any of the orderbook is updated
# it has access to both the orderbook that was updated
# as well as the rest of the orderbooks
# ...................................................................
print('-------------------------------------------------------------')
print('Last updated:', exchange_spot.iso8601(exchange_spot.milliseconds()))
# ...................................................................
# print just one orderbook here
# print(orderbook['datetime'], symbol, orderbook['asks'][0], orderbook['bids'][0])
# ...................................................................
# or print all orderbooks that have been already subscribed-to
for symbol, orderbook in orderbooks.items():
print(orderbook['datetime'], symbol, orderbook['asks'][0], orderbook['bids'][0])
async def watch_one_orderbook(exchange_spot, symbol):
# a call cost of 1 in the queue of subscriptions
# means one subscription per exchange.rateLimit milliseconds
your_delay = 1
await exchange_spot.throttle(your_delay)
while True:
try:
orderbook = await exchange_spot.watch_order_book(symbol)
orderbooks[symbol] = orderbook
when_orderbook_changed(exchange_spot, symbol, orderbook)
except Exception as e:
print(type(e).__name__, str(e))
async def watch_some_orderbooks(exchange_spot, symbol_list):
loops = [watch_one_orderbook(exchange_spot, symbol) for symbol in symbol_list]
# let them run, don't for all tasks cause they execute asynchronously
# don't print here
await asyncio.gather(*loops)
async def main():
exchange_spot = ccxt.binance()
await exchange_spot.load_markets()
await watch_some_orderbooks(exchange_spot, ['ZEN/USDT', 'RUNE/USDT', 'AAVE/USDT', 'SNX/USDT'])
await exchange_spot.close()
asyncio.run(main())
@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
from asyncio import run
import ccxt.pro as ccxt
from pprint import pprint
# This example will run silent and will return your balance only when the balance is updated.
# 1. launch the example with your keys and keep it running
# 2. go to the margin trading on the website
# 3. place a margin order on a spot market
# 4. see your balance updated in the example
async def main():
exchange = ccxt.pro.binance({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
'options': {
'defaultType': 'margin',
},
# comment it out if you don't want debug output
# this is for the demo purpose only (to show the communication)
'verbose': True,
})
while True:
try:
balance = await exchange.watch_balance()
# it will print the balance update when the balance changes
# if the balance remains unchanged the exchange will not send it
pprint(balance)
except Exception as e:
print('watch_balance() failed')
print(e)
break
await exchange.close()
run(main())
@@ -0,0 +1,43 @@
import ccxt.pro
from asyncio import run
print('CCXT Pro version', ccxt.pro.__version__)
def table(values):
first = values[0]
keys = list(first.keys()) if isinstance(first, dict) else range(0, len(first))
widths = [max([len(str(v[k])) for v in values]) for k in keys]
string = ' | '.join(['{:<' + str(w) + '}' for w in widths])
return "\n".join([string.format(*[str(v[k]) for k in keys]) for v in values])
async def main():
exchange = ccxt.pro.binance({
# 'options': {
# 'OHLCVLimit': 1000, # how many candles to store in memory by default
# },
})
symbol = 'ETH/USDT' # or BNB/USDT, etc...
timeframe = '1m' # 5m, 1h, 1d
limit = 10 # how many candles to return max
method = 'watchOHLCV'
if (method in exchange.has) and exchange.has[method]:
max_iterations = 100000 # how many times to repeat the loop before exiting
for i in range(0, max_iterations):
try:
ohlcvs = await exchange.watch_ohlcv(symbol, timeframe, None, limit)
now = exchange.milliseconds()
print('\n===============================================================================')
print('Loop iteration:', i, 'current time:', exchange.iso8601(now), symbol, timeframe)
print('-------------------------------------------------------------------------------')
print(table([[exchange.iso8601(o[0])] + o[1:] for o in ohlcvs]))
except Exception as e:
print(type(e).__name__, str(e))
break
await exchange.close()
else:
print(exchange.id, method, 'is not supported or not implemented yet')
run(main())
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
from asyncio import run
import ccxt.pro as ccxt
class MyBinance(ccxt.binance):
def handle_order_book_message(self, client, message, orderbook):
asks = self.safe_value(message, 'a', [])
bids = self.safe_value(message, 'b', [])
# printing high-frequency updates is a resource-heavy task
# this print statement is here just to demonstrate the work of it
# replace it with you logic for processing individual updates
print('Updates:', {
'asks': asks,
'bids': bids,
})
return super(MyBinance, self).handle_order_book_message(client, message, orderbook);
async def main():
exchange = MyBinance()
symbol = 'BTC/USDT'
print('Watching', exchange.id, symbol)
while True:
try:
orderbook = await exchange.watch_order_book(symbol)
except Exception as e:
print(str(e))
# raise e # uncomment to break all loops in case of an error in any one of them
# break # you can also break just this one loop if it fails
await exchange.close()
run(main())
@@ -0,0 +1,63 @@
import ccxt.pro
from asyncio import run, gather
data = {
'orderbook': None,
'balance': None,
}
def common_handler(exchange, symbol):
market = exchange.market(symbol)
base = market['base']
quote = market['quote']
balance = data['balance']
orderbook = data['orderbook']
if balance and orderbook:
total = balance['total']
tip = [ orderbook['asks'][0], orderbook['bids'][0] ]
print(exchange.iso8601(exchange.milliseconds()), symbol, 'orderbook:', tip, 'balance:', total)
async def watch_order_book(exchange, symbol):
while True:
try:
data['orderbook'] = await exchange.watch_order_book(symbol)
common_handler(exchange, symbol)
except Exception as e:
print(type(e).__name__, str(e))
break # break this loop
async def watch_balance(exchange, symbol):
while True:
try:
data['balance'] = await exchange.watch_balance()
common_handler(exchange, symbol)
except Exception as e:
print(type(e).__name__, str(e))
break # break this loop
async def main():
exchange = ccxt.pro.binance({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
})
await exchange.load_markets()
symbol = 'BTC/USDT'
while True:
try:
loops = [
watch_order_book(exchange, symbol),
watch_balance(exchange, symbol)
]
await gather(*loops)
except Exception as e:
print(type(e).__name__, str(e))
break
await exchange.close()
run(main())
@@ -0,0 +1,59 @@
# Python
import ccxt.pro
from asyncio import run, gather
from pprint import pprint
async def place_delayed_order(exchange, symbol, amount, price):
try:
await exchange.sleep(5000) # wait a bit
order = await exchange.create_limit_buy_order(symbol, amount, price)
print(exchange.iso8601(exchange.milliseconds()), 'place_delayed_order')
pprint(order)
print('---------------------------------------------------------------')
except Exception as e:
# break
print(e)
async def watch_orders_loop(exchange, symbol):
while True:
try:
orders = await exchange.watch_orders(symbol)
print(exchange.iso8601(exchange.milliseconds()), 'watch_orders_loop', len(orders), ' last orders cached')
print('---------------------------------------------------------------')
except Exception as e:
# break
print(e)
async def watch_balance_loop(exchange):
while True:
try:
balance = await exchange.watch_balance()
print(exchange.iso8601(exchange.milliseconds()), 'watch_balance_loop')
pprint(balance)
print('---------------------------------------------------------------')
except Exception as e:
# break
print(e)
async def main():
exchange = ccxt.pro.binanceusdm({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
})
symbol = 'BTC/USDT'
amount = 0.001
price = 11111
loops = [
watch_orders_loop(exchange, symbol),
watch_balance_loop(exchange),
place_delayed_order(exchange, symbol, amount, price)
]
await gather(*loops)
await exchange.close()
run(main())
@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
from asyncio import run, gather
import os
import sys
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(root + '/python')
import ccxt.pro # noqa: E402
print('CCXT Version:', ccxt.__version__)
async def print_balance_continuously(exchange):
while True:
try:
print('-----------------------------------------------------------')
await exchange.load_markets()
balance = await exchange.watch_balance()
print(exchange.iso8601(exchange.milliseconds()), exchange.id)
for currency, value in balance['total'].items():
print(value, currency)
except Exception as e:
print('-----------------------------------------------------------')
print(exchange.iso8601(exchange.milliseconds()), exchange.id, type(e), e)
await exchange.sleep(300000) # sleep 5 minutes and retry
async def main():
config = {
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
}
exchange_ids = [
'binance',
'binanceusdm',
'binancecoinm',
]
exchanges = [getattr(ccxt.pro, exchange_id)(config) for exchange_id in exchange_ids]
printing_loops = [print_balance_continuously(exchange) for exchange in exchanges]
await gather(*printing_loops)
closing_tasks = [exchange.close() for exchange in exchanges]
await gather(*closing_tasks)
run(main())
@@ -0,0 +1,41 @@
import ccxt.pro
from asyncio import run
def table(values):
first = values[0]
keys = list(first.keys()) if isinstance(first, dict) else range(0, len(first))
widths = [max([len(str(v[k])) for v in values]) for k in keys]
string = ' | '.join(['{:<' + str(w) + '}' for w in widths])
return "\n".join([string.format(*[str(v[k]) for k in keys]) for v in values])
async def main():
exchange = ccxt.pro.bitmex({
# 'options': {
# 'OHLCVLimit': 1000, # how many candles to store in memory by default
# },
})
symbol = 'BTC/USD'
timeframe = '1m' # 5m, 1h, 1d
limit = 10 # how many candles to return max
method = 'watchOHLCV'
if (method in exchange.has) and exchange.has[method]:
max_iterations = 100 # how many times to repeat the loop before exiting
for i in range(0, max_iterations):
try:
ohlcvs = await exchange.watch_ohlcv(symbol, timeframe, None, limit)
now = exchange.milliseconds()
print('\n===============================================================================')
print('Loop iteration:', i, 'current time:', exchange.iso8601(now), symbol, timeframe)
print('-------------------------------------------------------------------------------')
print(table([[exchange.iso8601(o[0])] + o[1:] for o in ohlcvs]))
except Exception as e:
print(type(e).__name__, str(e))
break
await exchange.close()
else:
print(exchange.id, method, 'is not supported or not implemented yet')
run(main())
@@ -0,0 +1,73 @@
import ccxt.pro
from asyncio import run, gather
def table(values):
first = values[0]
keys = list(first.keys()) if isinstance(first, dict) else range(0, len(first))
widths = [max([len(str(v[k])) for v in values]) for k in keys]
string = ' | '.join(['{:<' + str(w) + '}' for w in widths])
return "\n".join([string.format(*[str(v[k]) for k in keys]) for v in values])
async def watch_ticker(color, duration, exchange, symbol):
method = 'watchTicker'
if (method in exchange.has) and exchange.has[method]:
start = exchange.milliseconds()
i = 1
while True:
ticker = await exchange.watch_ticker(symbol)
now = exchange.milliseconds()
# start color code
print(color, 'Ticker ========================================================================')
print(exchange.iso8601(now), symbol, 'iteration:', i, 'last price:', ticker['last'])
print('-------------------------------------------------------------------------------')
# pprint.pprint(ticker) # uncomment for a lengthy complete printout
print('\x1b[0m') # stop color code
i += 1
if (start + duration) < now:
break
else:
raise Exception(exchange.id + ' ' + method + ' is not supported or not implemented yet')
async def watch_ohlcv(color, duration, exchange, symbol, timeframe, limit):
method = 'watchOHLCV'
if (method in exchange.has) and exchange.has[method]:
start = exchange.milliseconds()
i = 1
while True:
ohlcvs = await exchange.watch_ohlcv(symbol, timeframe, None, limit)
now = exchange.milliseconds()
# start color code
print(color, 'OHLCV =========================================================================')
print(exchange.iso8601(now), symbol, timeframe, 'iteration:', i)
print('-------------------------------------------------------------------------------')
print(table([[exchange.iso8601(o[0])] + o[1:] for o in ohlcvs]))
print('\x1b[0m') # stop color code
i += 1
if (start + duration) < now:
break
else:
raise Exception(exchange.id + ' ' + method + ' is not supported or not implemented yet')
# =============================================================================
async def main():
exchange = ccxt.pro.bitmex()
await exchange.load_markets()
duration = 1200000 # run 20 minutes = 1200000 milliseconds
symbol = 'BTC/USD'
limit = 10
loops = [
watch_ticker('\033[35m', duration, exchange, symbol), # magenta
watch_ohlcv('\x1b[33m', duration, exchange, symbol, '1m', limit), # yellow
watch_ohlcv('\x1b[32m', duration, exchange, symbol, '5m', limit), # green
]
await gather(*loops)
await exchange.close()
run(main())
@@ -0,0 +1,23 @@
import ccxt.pro
from asyncio import run
print('CCXT Pro version', ccxt.pro.__version__)
async def main():
exchange = ccxt.pro.bitvavo()
await exchange.load_markets()
exchange.verbose = True
symbol = 'BTC/EUR'
while True:
try:
orderbook = await exchange.watch_order_book(symbol)
print(orderbook['nonce'], symbol, orderbook['asks'][0], orderbook['bids'][0])
except Exception as e:
print(type(e).__name__, str(e))
break
await exchange.close()
run(main())
@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
import asyncio
import ccxt.pro
print('CCXT Version:', ccxt.__version__)
async def loop(exchange, symbol, timeframe, complete_candles_only=False):
duration_in_seconds = exchange.parse_timeframe(timeframe)
duration_in_ms = duration_in_seconds * 1000
while True:
try:
trades = await exchange.watch_trades(symbol)
if len(trades) > 0:
current_minute = int(exchange.milliseconds() / duration_in_ms)
ohlcvc = exchange.build_ohlcvc(trades, timeframe)
if complete_candles_only:
ohlcvc = [candle for candle in ohlcvc if int(candle[0] / duration_in_ms) < current_minute]
if len(ohlcvc) > 0:
print('-----------------------------------------------------------')
print("Symbol:", symbol, "timeframe:", timeframe)
print(ohlcvc)
except Exception as e:
print(f"{type(e).__name__}: {(str(e))}")
# raise type(e)(str(e)) # uncomment to break all loops in case of an error in any one of them
# break # you can also break just this one loop if it fails
async def main():
# select the exchange
exchange = ccxt.pro.binance()
if exchange.has['watchTrades']:
markets = await exchange.load_markets()
# Change this value accordingly
timeframe = '1m'
limit = 5
selected_symbols = list(markets.values())[:limit]
# you can also specify the symbols manually
# selected_symbols = ['BTC/USDT', 'ETH/USDT']
# Use this variable to choose if only complete candles
# should be considered
complete_candles_only = True
await asyncio.gather(*[loop(exchange, symbol['symbol'], timeframe, complete_candles_only)
for symbol in selected_symbols])
await exchange.close()
else:
print(exchange.id, 'does not support watchTrades yet')
asyncio.run(main())
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
import ccxt.pro
from asyncio import run
async def main():
exchange = ccxt.pro.coinbase()
method = 'watchTrades'
print('CCXT Pro version', ccxt.pro.__version__)
if exchange.has[method]:
last_id = ''
while True:
try:
trades = await exchange.watch_trades('BTC/USD')
for trade in trades:
if trade['id'] > last_id:
print(exchange.iso8601(exchange.milliseconds()), trade['symbol'], trade['datetime'], trade['price'], trade['amount'])
last_id = trade['id']
except Exception as e:
# stop
await exchange.close()
raise e
# or retry
# pass
else:
raise Exception(exchange.id + ' ' + method + ' is not supported or not implemented yet')
run(main())
@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
import ccxt.pro
from asyncio import run
async def main():
exchange = ccxt.pro.coinbase()
method = 'watchTrades'
print('CCXT Pro version', ccxt.pro.__version__)
if exchange.has[method]:
while True:
try:
trades = await exchange.watch_trades('BTC/USD')
num_trades = len(trades)
trade = trades[-1]
print(exchange.iso8601(exchange.milliseconds()), trade['symbol'], trade['datetime'], trade['price'], trade['amount'], 'stored', num_trades, 'trades in cache')
except Exception as e:
# stop
await exchange.close()
raise e
# or retry
# pass
else:
raise Exception(exchange.id + ' ' + method + ' is not supported or not implemented yet')
run(main())
@@ -0,0 +1,22 @@
import ccxt.pro
from asyncio import run
async def consume_all_trades(exchange, symbol):
await exchange.load_markets()
while True:
try:
trades = await exchange.watch_trades(symbol)
print('----------------------------------------------------------------------')
print(exchange.iso8601(exchange.milliseconds()), 'received', len(trades), 'new', symbol, 'trades:')
for trade in trades:
print(exchange.id, symbol, trade['id'], trade['datetime'], trade['amount'], trade['price'])
exchange.trades[symbol].clear()
except Exception as e:
print(type(e).__name__, str(e))
await exchange.close()
exchange = ccxt.pro.bitmex()
symbol = 'BTC/USD'
run(consume_all_trades(exchange, symbol))
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
import asyncio
import ccxt.pro
from datetime import datetime
async def loop(exchange, symbol):
since = datetime.utcnow()
timestamp = int(since.timestamp() * 1000)
while True:
trades = await exchange.watch_trades(symbol, since=timestamp)
print('--------------------------------------------------------------')
print('Received', len(trades), 'after', exchange.iso8601 (timestamp))
print('waiting for next update...')
async def main():
exchange = ccxt.pro.gateio()
await loop(exchange, 'BTC/USDT')
await exchange.close()
if __name__ == '__main__':
asyncio.run(main())
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
from asyncio import run
import ccxt.pro
from pprint import pprint
class MyBinance(ccxt.pro.binance):
def handle_ohlcv(self, client, message):
# add your handling of the original message here
print('intercepted', message)
return super(MyBinance, self).handle_ohlcv(client, message)
async def main():
exchange = MyBinance()
symbol = 'BTC/USDT'
print('Watching', exchange.id, symbol)
while True:
try:
ohlcv = await exchange.watch_ohlcv(symbol, '1m')
except Exception as e:
print(str(e))
# raise e # uncomment to break all loops in case of an error in any one of them
# break # you can also break just this one loop if it fails
await exchange.close()
if __name__ == "__main__":
print('CCXT Version:', ccxt.__version__)
run(main())
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
import ccxt.pro
from asyncio import gather, run
async def symbol_loop(exchange, symbol):
print('Starting the', exchange.id, 'symbol loop with', symbol)
while True:
try:
orderbook = await exchange.watch_order_book(symbol)
now = exchange.milliseconds()
print(exchange.iso8601(now), exchange.id, symbol, orderbook['asks'][0], orderbook['bids'][0])
except Exception as e:
print(str(e))
# raise e # uncomment to break all loops in case of an error in any one of them
break # you can break just this one loop if it fails
async def main():
exchange = ccxt.pro.kucoin({
"apiKey": "YOUR_API_KEY",
"secret": "YOUR_API_SECRET",
"password": "YOUR_API_PASSWORD",
})
symbols = ['KDA/USDT', 'KDA/BTC', 'BTC/USDT']
loops = [symbol_loop(exchange, symbol) for symbol in symbols]
await gather(*loops)
await exchange.close()
run(main())
@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
import ccxt.pro
from asyncio import gather, run
async def symbol_loop(exchange, method, symbol):
print('Starting', exchange.id, method, symbol)
while True:
try:
response = await getattr(exchange, method)(symbol)
now = exchange.milliseconds()
iso8601 = exchange.iso8601(now)
if method == 'watchOrderBook':
print(iso8601, exchange.id, method, symbol, response['asks'][0], response['bids'][0])
elif method == 'watchTicker':
print(iso8601, exchange.id, method, symbol, response['high'], response['low'], response['bid'], response['ask'])
elif method == 'watchTrades':
print(iso8601, exchange.id, method, symbol, len(response), 'trades')
except Exception as e:
print(str(e))
# raise e # uncomment to break all loops in case of an error in any one of them
break # you can break just this one loop if it fails
async def method_loop(exchange, method, symbols):
print('Starting', exchange.id, method, symbols)
loops = [symbol_loop(exchange, method, symbol) for symbol in symbols]
await gather(*loops)
async def exchange_loop(exchange_id, methods):
print('Starting', exchange_id, methods)
exchange = getattr(ccxt.pro, exchange_id)()
loops = [method_loop(exchange, method, symbols) for method, symbols in methods.items()]
await gather(*loops)
await exchange.close()
async def main():
exchanges = {
'okex': {
'watchOrderBook': ['BTC/USDT', 'ETH/BTC', 'ETH/USDT'],
'watchTicker': ['BTC/USDT'],
},
'binance': {
'watchOrderBook': ['BTC/USDT', 'ETH/BTC'],
'watchTrades': [ 'ETH/BTC' ],
},
}
loops = [exchange_loop(exchange_id, methods) for exchange_id, methods in exchanges.items()]
await gather(*loops)
run(main())
@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
import asyncio
import ccxt.pro
orderbooks = {}
def handle_all_orderbooks(orderbooks):
print('We have the following orderbooks:')
for exchange_id, orderbooks_by_symbol in orderbooks.items():
for symbol in orderbooks_by_symbol.keys():
orderbook = orderbooks_by_symbol[symbol]
print(ccxt.pro.Exchange.iso8601(orderbook['timestamp']), exchange_id, symbol, orderbook['asks'][0], orderbook['bids'][0])
async def symbol_loop(exchange, symbol):
while True:
try:
orderbook = await exchange.watch_order_book(symbol)
orderbooks[exchange.id] = orderbooks.get(exchange.id, {})
orderbooks[exchange.id][symbol] = orderbook
print('===========================================================')
#
# here you can do what you want
# with the most recent versions of each orderbook you have so far
#
# you can also wait until all of them are available
# by just looking into all the orderbooks and counting them
#
# we just print them here to keep this example simple
#
handle_all_orderbooks(orderbooks)
except Exception as e:
print(str(e))
# raise e # uncomment to break all loops in case of an error in any one of them
break # you can break just this one loop if it fails
async def exchange_loop(exchange_id, symbols):
exchange = getattr(ccxt.pro, exchange_id)()
loops = [symbol_loop(exchange, symbol) for symbol in symbols]
await asyncio.gather(*loops)
await exchange.close()
async def main():
symbols = ['BTC/USDT', 'ETH/BTC']
# symbols = []
exchanges = {
'okex': symbols + ['ETH/USDT'],
'binance': symbols,
}
loops = [exchange_loop(exchange_id, symbols) for exchange_id, symbols in exchanges.items()]
await asyncio.gather(*loops)
asyncio.run(main())
@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
import ccxt.pro
from asyncio import run, gather, sleep
orderbooks = {}
def handle_all_orderbooks(orderbooks):
print('We have the following orderbooks:')
for exchange_id, orderbooks_by_symbol in orderbooks.items():
for symbol in orderbooks_by_symbol.keys():
orderbook = orderbooks_by_symbol[symbol]
print(ccxt.pro.Exchange.iso8601(orderbook['timestamp']), exchange_id, symbol, orderbook['asks'][0], orderbook['bids'][0])
async def handling_loop(orderbooks):
delay = 5
while True:
await sleep(delay)
handle_all_orderbooks(orderbooks)
async def symbol_loop(exchange, symbol):
while True:
try:
orderbook = await exchange.watch_order_book(symbol)
orderbooks[exchange.id] = orderbooks.get(exchange.id, {})
orderbooks[exchange.id][symbol] = orderbook
except Exception as e:
print(str(e))
# raise e # uncomment to break all loops in case of an error in any one of them
break # you can break just this one loop if it fails
async def exchange_loop(exchange_id, symbols):
exchange = getattr(ccxt.pro, exchange_id)()
loops = [symbol_loop(exchange, symbol) for symbol in symbols]
await gather(*loops)
await exchange.close()
async def main():
symbols = ['BTC/USDT', 'ETH/BTC']
# symbols = []
exchanges = {
'okex': symbols + ['ETH/USDT'],
'binance': symbols,
}
loops = [exchange_loop(exchange_id, symbols) for exchange_id, symbols in exchanges.items()]
loops += [handling_loop(orderbooks)]
await gather(*loops)
run(main())
@@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
import ccxt.pro
from asyncio import gather, run
async def symbol_loop(exchange, method, symbol):
print('Starting', exchange.id, method, symbol)
while True:
try:
response = await getattr(exchange, method)(symbol)
now = exchange.milliseconds()
iso8601 = exchange.iso8601(now)
if method == 'watchOrderBook':
print(iso8601, exchange.id, method, symbol, response['asks'][0], response['bids'][0])
elif method == 'watchTicker':
print(iso8601, exchange.id, method, symbol, response['high'], response['low'], response['bid'], response['ask'])
elif method == 'watchTrades':
print(iso8601, exchange.id, method, symbol, len(response), 'trades')
except Exception as e:
print(str(e))
# raise e # uncomment to break all loops in case of an error in any one of them
break # you can break just this one loop if it fails
async def symbols_method_loop(exchange, method, symbols):
print('Starting', exchange.id, method, symbols)
loops = [symbol_loop(exchange, method, symbol) for symbol in symbols]
await gather(*loops)
async def method_loop(exchange, method):
print('Starting', exchange.id, method)
while True:
try:
response = await getattr(exchange, method)()
now = exchange.milliseconds()
iso8601 = exchange.iso8601(now)
print(iso8601, exchange.id, method, response)
except Exception as e:
print(str(e))
# raise e # uncomment to break all loops in case of an error in any one of them
break # you can break just this one loop if it fails
async def exchange_loop(exchange_id, methods, config={}):
print('Starting', exchange_id, methods)
exchange = getattr(ccxt.pro, exchange_id)()
for attr, value in config.items():
setattr(exchange, attr, value)
loops = [symbols_method_loop(exchange, method, symbols) if len(symbols) else method_loop(exchange, method) for method, symbols in methods.items()]
await gather(*loops)
await exchange.close()
async def main():
keys = {
'okex': {
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
},
'binance': {
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
},
}
exchanges = {
'okex': {
'watchOrderBook': ['BTC/USDT', 'ETH/BTC', 'ETH/USDT'],
'watchTicker': ['BTC/USDT'],
'watchBalance': [],
},
'binance': {
'watchOrderBook': ['BTC/USDT', 'ETH/BTC'],
'watchTrades': [ 'ETH/BTC' ],
'watchBalance': [],
},
}
loops = [exchange_loop(exchange_id, methods, keys.get(exchange_id, {})) for exchange_id, methods in exchanges.items()]
await gather(*loops)
run(main())
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
import ccxt.pro
from asyncio import gather, run
async def symbol_loop(exchange, symbol):
print('Starting the', exchange.id, 'symbol loop with', symbol)
while True:
try:
orderbook = await exchange.watch_order_book(symbol)
now = exchange.milliseconds()
print(exchange.iso8601(now), exchange.id, symbol, orderbook['asks'][0], orderbook['bids'][0])
except Exception as e:
print(str(e))
# raise e # uncomment to break all loops in case of an error in any one of them
break # you can break just this one loop if it fails
async def exchange_loop(exchange_id, symbols):
print('Starting the', exchange_id, 'exchange loop with', symbols)
exchange = getattr(ccxt.pro, exchange_id)()
loops = [symbol_loop(exchange, symbol) for symbol in symbols]
await gather(*loops)
await exchange.close()
async def main():
exchanges = {
'okex': ['BTC/USDT', 'ETH/BTC', 'ETH/USDT'],
'binance': ['BTC/USDT', 'ETH/BTC'],
}
loops = [exchange_loop(exchange_id, symbols) for exchange_id, symbols in exchanges.items()]
await gather(*loops)
run(main())
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
import ccxt.pro
from asyncio import gather, run
async def symbol_loop(exchange, symbol):
print('Starting the', exchange.id, 'symbol loop with', symbol)
while True:
try:
trades = await exchange.watch_trades(symbol)
now = exchange.milliseconds()
print(exchange.iso8601(now), exchange.id, symbol, len(trades), trades[-1]['price'])
except Exception as e:
print(str(e))
# raise e # uncomment to break all loops in case of an error in any one of them
break # you can break just this one loop if it fails
async def exchange_loop(exchange_id, symbols):
print('Starting the', exchange_id, 'exchange loop with', symbols)
exchange = getattr(ccxt.pro, exchange_id)({
'newUpdates': True, # https://github.com/ccxt/ccxt/wiki/ccxt.pro.manual#incremental-data-structures
})
loops = [symbol_loop(exchange, symbol) for symbol in symbols]
await gather(*loops)
await exchange.close()
async def main():
exchanges = {
'okex': ['BTC/USDT', 'ETH/BTC', 'ETH/USDT'],
'binance': ['BTC/USDT', 'ETH/BTC'],
}
loops = [exchange_loop(exchange_id, symbols) for exchange_id, symbols in exchanges.items()]
await gather(*loops)
run(main())
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
import asyncio
import ccxt.pro
async def loop(exchange_id, symbol):
exchange = getattr(ccxt.pro, exchange_id)()
while True:
try:
orderbook = await exchange.watch_order_book(symbol)
now = exchange.milliseconds()
print(exchange.iso8601(now), exchange.id, symbol, orderbook['asks'][0], orderbook['bids'][0])
except Exception as e:
print(str(e))
# raise e # uncomment to break all loops in case of an error in any one of them
break # you can break just this one loop if it fails
await exchange.close()
async def main():
symbols = {
'kraken': 'BTC/USDT',
'binance': 'BTC/USDT',
'bitmex': 'XBT_USDT',
}
await asyncio.gather(*[loop(exchange_id, symbol) for exchange_id, symbol in symbols.items()])
asyncio.run(main())
@@ -0,0 +1,34 @@
import ccxt.pro
import asyncio
import time
async def watch_book(exchange, ticker):
last = None
while True:
try:
orderbook = await exchange.watch_order_book(ticker)
# TODO add timeout within a minute
# TODO add last
top_bid = orderbook['bids'][0][0]
if last != top_bid:
print(f'{int(time.time() * 1000)} top bid for celo on {exchange.name} is {top_bid}')
last = top_bid
except Exception as e:
print(f'{exchange.name} failed {type(e)} {e}')
async def main():
exchange_ids = ['coinbasepro', 'okcoin', 'bittrex']
exchanges = [getattr(ccxt.pro, exchange_id)() for exchange_id in exchange_ids]
try:
done, pending = await asyncio.wait({watch_book(exchange, 'CELO/USD') for exchange in exchanges}, return_when=asyncio.FIRST_EXCEPTION)
for completed in done:
# trigger the exception here
completed.result()
except Exception as e:
print(f'closing all exchanges because of exception {type(e)} {e}')
await asyncio.gather(*[exchange.close() for exchange in exchanges])
asyncio.run(main())
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
from asyncio import run
import ccxt.pro
print('CCXT Pro Version: ', ccxt.pro.__version__)
exchange = ccxt.pro.okex({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_API_SECRET',
'password': 'YOUR_API_PASSWORD',
'options': { 'defaultType': 'swap' },
})
async def main():
await exchange.load_markets()
# exchange.verbose = True # uncomment for debugging
# https://github.com/ccxt/ccxt/wiki/Manual#overriding-unified-params
# https://www.okex.com/docs/en/#swap-swap---orders
symbol = 'BTC/USDT:USDT'
amount = 1 # how may contracts
price = None # or your limit price
side = 'buy' # or 'sell'
future_type = '1' # 1 open long, 2 open short, 3 close long, 4 close short for futures
order_type = '4' # 0 = limit order, 4 = market order
try:
# open long market price order
order = await exchange.create_order(symbol, 'market', side, amount, price, {'type': future_type})
# --------------------------------------------------------------------
# open long market price order
# const order = await exchange.create_order(symbol, type, side, amount, price, {'order_type': order_type})
# --------------------------------------------------------------------
# close short market price order
# const order = await exchange.create_order(symbol, 'market', side, amount, price, {'type': future_type, 'order_type': order_type})
# --------------------------------------------------------------------
# close short market price order
# const order = await exchange.create_order(symbol, '4', side, amount, price, {'order_type': order_type})
# ...
print(order)
except Exception as e:
print(type(e).__name__, str(e))
await exchange.close()
run(main())
@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
import asyncio
import ccxt.pro as ccxt
from pprint import pprint
async def main():
exchange = ccxt.pro.okex({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
# okex requires this: https://github.com/ccxt/ccxt/wiki/Manual#authentication
'password': 'YOUR_API_PASSWORD',
# comment it out if you don't want debug output
# this is for the demo purpose only (to show the communication)
'verbose': True,
})
while True:
try:
balance = await exchange.watch_balance({
# okex watch_balance requires a symbol or an instrument_id
'symbol': 'BTC/USDT',
'type': 'margin',
})
# it will print the balance update when the balance changes
# if the balance remains unchanged the exchange will not send it
pprint(balance)
except Exception as e:
print('watch_balance() failed')
print(e)
break
await exchange.close()
asyncio.run(main())
@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
import asyncio
import ccxt.pro as ccxt
from pprint import pprint
async def main():
exchange = ccxt.pro.okex({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
# okex requires this: https://github.com/ccxt/ccxt/wiki/Manual#authentication
'password': 'YOUR_API_PASSWORD',
'options': {
'watchBalance': 'margin',
},
# comment it out if you don't want debug output
# this is for the demo purpose only (to show the communication)
'verbose': True,
})
while True:
try:
balance = await exchange.watch_balance({
# okex watch_balance requires a symbol or an instrument_id
'symbol': 'BTC/USDT',
})
# it will print the balance update when the balance changes
# if the balance remains unchanged the exchange will not send it
pprint(balance)
except Exception as e:
print('watch_balance() failed')
print(e)
break
await exchange.close()
asyncio.run(main())
@@ -0,0 +1,36 @@
import ccxt.pro
from asyncio import run
print('CCXT Pro version', ccxt.pro.__version__)
async def main():
exchange = ccxt.pro.okx({
'options': {
'watchOrderBook': {
'depth': 'bbo-tbt', # tick-by-tick best bidask
},
},
})
markets = await exchange.load_markets()
# exchange.verbose = True # uncomment for debugging purposes if necessary
symbol = 'BTC/USDT'
while True:
try:
# -----------------------------------------------------------------
# use this:
# orderbook = await exchange.watch_order_book(symbol)
# print(orderbook['datetime'], symbol, orderbook['asks'][0], orderbook['bids'][0])
# -----------------------------------------------------------------
# or this:
ticker = await exchange.watch_ticker(symbol)
print(ticker['datetime'], symbol, [ticker['ask'], ticker['askVolume']], [ticker['bid'], ticker['bidVolume']])
# -----------------------------------------------------------------
except Exception as e:
print(type(e).__name__, str(e))
break
await exchange.close()
run(main())
@@ -0,0 +1,55 @@
import ccxt.pro
from asyncio import run, ensure_future
from pprint import pprint
print('CCXT Version:', ccxt.__version__)
# on_connected() is called when a client connection is established
# note that the exchange will reuse the same client connection
# some exchanges might require two or more public/private connections
# therefore on_connected() may be called more than once
class MyBinance(ccxt.pro.binance):
def on_connected(self, client, message=None):
print('Connected to', client.url)
ensure_future(create_order(self))
async def create_order(exchange):
symbol = 'BTC/USDT'
type = 'limit'
side = 'buy'
amount = 123.45 # change for your values
price = 54.321 # change for your values
params = {}
try:
order = await exchange.create_order(symbol, type, side, amount, price, params)
print('--------------------------------------------------------------')
print('create_order():')
pprint(order)
except Exception as e:
print(type(e).__name__, str(e))
async def watch_orders(exchange):
while True:
try:
orders = await exchange.watch_orders()
print('--------------------------------------------------------------')
print('watch_orders():')
pprint(orders)
except Exception as e:
print(type(e).__name__, str(e))
break
await exchange.close()
exchange = MyBinance({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
})
run(watch_orders(exchange))
@@ -0,0 +1,35 @@
import ccxt.pro
import asyncio
async def watch_order_book(exchange, symbol):
while True:
orderbook = await exchange.watch_order_book(symbol)
print(orderbook['datetime'], symbol, orderbook['asks'][0], orderbook['bids'][0])
async def watch_trades(exchange, symbol):
while True:
trades = await exchange.watch_trades(symbol)
last = trades[-1]
print(last['datetime'], last['price'], last['amount'])
async def main():
exchange = ccxt.pro.bitstamp()
await exchange.load_markets()
symbol = 'BTC/USD'
while True:
try:
loops = [
watch_order_book(exchange, symbol),
watch_trades(exchange, symbol)
]
await asyncio.gather(*loops)
except Exception as e:
print(type(e).__name__, str(e))
break
await exchange.close()
asyncio.run(main())
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from asyncio import run
import ccxt.pro
async def loop(exchange, symbol):
await exchange.throttle(10)
while True:
try:
orderbook = await exchange.watch_order_book(symbol)
now = exchange.milliseconds()
print(exchange.iso8601(now), symbol, orderbook['asks'][0], orderbook['bids'][0])
except Exception as e:
print(str(e))
# raise e # uncomment to break all loops in case of an error in any one of them
# break # you can also break just this one loop if it fails
async def main():
exchange = ccxt.pro.ftx()
symbols = ['BTC/USDT', 'ETH/USDT', 'ETH/BTC']
await asyncio.gather(*[loop(exchange, symbol) for symbol in symbols])
await exchange.close()
run(main())
@@ -0,0 +1,23 @@
import ccxt.pro
from asyncio import run
from pprint import pprint
print('CCXT Version:', ccxt.__version__)
async def main():
exchange = ccxt.pro.phemex({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
})
markets = await exchange.load_markets()
# exchange.verbose = True # uncomment for debugging purposes if necessary
try:
symbol = 'UNI/USDT'
response = await exchange.cancel_all_orders(symbol)
pprint(response)
except Exception as e:
print(type(e).__name__, str(e))
await exchange.close()
run(main())
@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
import asyncio
import sys
import os
# ------------------------------------------------------------------------------
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
sys.path.append(root + '/python')
# ------------------------------------------------------------------------------
import ccxt.pro
print('CCXT Version:', ccxt.pro.__version__)
orderbooks = {}
def handle_all_orderbooks(exchange, orderbooks, spot, future):
if spot in orderbooks and future in orderbooks:
spot_order_book = orderbooks[spot]
future_order_book = orderbooks[future]
timestamp = exchange.milliseconds()
spot_lag = abs(timestamp - spot_order_book['timestamp']) if spot_order_book['timestamp'] else 10000
future_lag = abs(timestamp - future_order_book['timestamp']) if future_order_book['timestamp'] else 10000
if spot_lag >= 10000 or future_lag >= 10000:
print('Lag > 10 seconds')
async def symbol_loop(exchange, symbol, spot, future):
while True:
try:
orderbook = await exchange.watch_order_book(symbol)
orderbooks[symbol] = orderbook
print(exchange.id, '{:13s}'.format(symbol), orderbook['datetime'], orderbook['asks'][0], orderbook['bids'][0])
#
# here you can do what you want
# with the most recent versions of each orderbook you have so far
#
# you can also wait until all of them are available
# by just looking into all the orderbooks and counting them
#
# we just print them here to keep this example simple
#
handle_all_orderbooks(exchange, orderbooks, spot, future)
except Exception as e:
print(str(e))
# raise e # uncomment to break all loops in case of an error in any one of them
break # you can break just this one loop if it fails
async def main():
spot = 'BTC/USDT'
future = 'BTC/USDT:USDT'
symbols = [ spot, future ]
exchange = ccxt.pro.bitmart()
loops = [
symbol_loop(exchange, spot, spot, future),
symbol_loop(exchange, future, spot, future),
]
await asyncio.gather(*loops)
await exchange.close()
asyncio.run(main())
@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
import asyncio
import ccxt.pro
async def loop(exchange, symbol, n):
i = 0
while True:
try:
orderbook = await exchange.watch_order_book(symbol)
# print every 100th bidask to avoid wasting CPU cycles on printing
if not i % 100:
# i = how many updates there were in total
# n = the number of the pair to count subscriptions
now = exchange.milliseconds()
print(exchange.iso8601(now), n, symbol, i, orderbook['asks'][0], orderbook['bids'][0])
i += 1
except Exception as e:
print(str(e))
# raise e # uncomment to break all loops in case of an error in any one of them
# break # you can also break just this one loop if it fails
async def main():
exchange = ccxt.pro.kraken()
await exchange.load_markets()
markets = list(exchange.markets.values())
symbols = [market['symbol'] for market in markets if not market['darkpool']]
await asyncio.gather(*[loop(exchange, symbol, n) for n, symbol in enumerate(symbols)])
await exchange.close()
asyncio.run(main())
@@ -0,0 +1,45 @@
import asyncio
import ccxt.pro
class MyBinance(ccxt.pro.binance):
def handle_mini_ticker(self, client, message):
market_id = self.safe_string_lower(message, 's')
message_hash = market_id + '@miniTicker'
client.resolve(message, message_hash)
def handle_message(self, client, message):
handlers = {
'24hrMiniTicker': self.handle_mini_ticker,
# add other custom handlers here
}
e = self.safe_string(message, 'e')
method = self.safe_value(handlers, e)
if method:
return method(client, message)
else:
return super(MyBinance, self).handle_message(client, message)
async def main():
exchange = MyBinance({
'enableRateLimit': False,
'options': {
'defaultType': 'future'
}
})
await exchange.load_markets()
# exchange.verbose = True # uncomment for debugging purposes
market = exchange.market('BTC/USDT')
message_hash = market['lowercaseId'] + '@miniTicker'
while True:
try:
print(await exchange.watch_public(message_hash))
except Exception as e:
print(type(e).__name__, str(e))
await exchange.close()
if __name__ == '__main__':
asyncio.run(main())
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
from asyncio import run, gather
import ccxt.pro
print('CCXT Version:', ccxt.__version__)
async def exchange_loop(exchange_id, symbols):
exchange = getattr(ccxt.pro, exchange_id)()
markets = await exchange.load_markets()
await gather(*[watch_ticker_loop(exchange, symbol) for symbol in symbols])
await exchange.close()
async def watch_ticker_loop(exchange, symbol):
# exchange.verbose = True # uncomment for debugging purposes if necessary
while True:
try:
ticker = await exchange.watch_ticker(symbol)
now = exchange.milliseconds()
print(exchange.iso8601(now), exchange.id, symbol, 'bid:', ticker['bid'], 'ask:', ticker['ask'], 'last:', ticker['last'], 'on', ticker['datetime'])
except Exception as e:
print(str(e))
# raise e # uncomment to break all loops in case of an error in any one of them
break # you can break just this one loop if it fails
async def main():
exchanges = {
'binance': [ 'BTC/USDT', 'ETH/USDT' ],
'ftx': [ 'BTC/USD', 'ETH/USD' ],
}
loops = [exchange_loop(exchange_id, symbols) for exchange_id, symbols in exchanges.items()]
await gather(*loops)
run(main())
@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
from asyncio import gather, run
import ccxt.pro
from pprint import pprint
async def watch_ticker_continuously(exchange, symbol):
filename = exchange.id + '-' + symbol.replace('/', '-') + '.csv'
print('Watching', exchange.id, symbol, filename)
keys = ['index', 'exchange', 'symbol', 'timestamp', 'open', 'high', 'low', 'close', 'baseVolume']
with open(filename, 'w') as file:
file.write(','.join(keys) + "\n")
index = 0
while True:
try:
ticker = await exchange.watch_ticker(symbol)
values = [str(index), exchange.id] + [str(ticker[key]) for key in keys[2:]]
print(*values)
with open(filename, 'a') as file:
file.write(','.join(values) + "\n")
index += 1
except Exception as e:
print(e)
async def watch_tickers_continuously(exchange_id, overrides, symbols):
exchange_class = getattr(ccxt.pro, exchange_id)
exchange = exchange_class(overrides)
coroutines = [watch_ticker_continuously(exchange, symbol) for symbol in symbols]
await gather(*coroutines)
await exchange.close()
async def main():
exchanges = {
'binance': {'options': {'defaultType': 'future'}},
'huobipro': {}
}
symbols = ['BTC/USDT', 'ETH/USDT', 'LTC/USDT', 'XRP/USDT', 'BCH/USDT']
coroutines = [watch_tickers_continuously(exchange_id, exchanges[exchange_id], symbols) for exchange_id in exchanges.keys()]
return await gather(*coroutines)
run(main())
@@ -0,0 +1,46 @@
<!DOCTYPE HTML>
<html>
<head>
<title>CCXT Basic example for the browser</title>
<script type="text/javascript" src="https://unpkg.com/ccxt"></script>
<script>
document.addEventListener ("DOMContentLoaded", function () {
alert ('ccxt version ' + ccxt.version);
const exchange = new ccxt.bittrex ({
'proxyUrl': 'https://cors-anywhere.herokuapp.com/'
})
const symbol = 'BTC/USDT'
exchange.fetchTicker (symbol).then (ticker => {
const text = [
exchange.id,
symbol,
JSON.stringify (ticker, undefined, '\n\t')
]
document.getElementById ('content').innerHTML = text.join (' ')
}).catch (e => {
const text = [
e.constructor.name,
e.message,
]
document.getElementById ('content').innerHTML = text.join (' ')
})
})
</script>
</head>
<body>
<h1>Hello, CCXT!</h1>
<pre id="content"></pre>
</body>
</html>
@@ -0,0 +1,47 @@
<!DOCTYPE HTML>
<html>
<head>
<title>CCXT Basic example for the browser</title>
<script type="text/javascript" src="https://unpkg.com/ccxt"></script>
<style type="text/css">
#contentA { background-color: #ccccff; }
#contentB { background-color: #ffcccc; }
</style>
<script>'use strict'
class MyExchange extends ccxt.coinbasepro {
async fetchTicker (symbol, params = {}) {
alert ("I'm about to call the parent method from the overrided class, woohooo!")
// just call the parent method and that's it
return super.fetchTicker (symbol, params);
}
}
document.addEventListener ("DOMContentLoaded", function () {
const exchange = new MyExchange ()
const symbol = 'ETH/BTC'
const showFetchedTicker = function (ticker, elementId) {
const text = [
exchange.id,
symbol,
JSON.stringify (ticker, undefined, '\n\t')
]
document.getElementById (elementId).innerHTML = text.join (' ')
}
exchange.fetchTicker (symbol).then (ticker => showFetchedTicker (ticker, 'content'))
})
</script>
</head>
<body>
<h1>Hello, CCXT!</h1>
<pre id="content"></pre>
</body>
</html>
+52
View File
@@ -0,0 +1,52 @@
<!DOCTYPE HTML>
<html>
<head>
<title>CCXT Basic example for the browser</title>
<script type="text/javascript" src="https://unpkg.com/ccxt"></script>
<script>
document.addEventListener ("DOMContentLoaded", function () {
alert ('ccxt version ' + ccxt.version + ' supporting ');
const pollTickerContinuously = async function (exchange, symbol) {
while (true) {
try {
const ticker = await exchange.fetchTicker (symbol)
const text = [
exchange.id,
symbol,
JSON.stringify (ticker, undefined, '\n\t')
]
document.getElementById ('content').innerHTML = text.join (' ');
} catch (e) {
const text = [
e.constructor.name,
e.message,
]
document.getElementById ('content').innerHTML = text.join (' ');
}
}
}
const exchange = new ccxt.binance ({ enableRateLimit: true })
const symbol = 'ETH/BTC'
pollTickerContinuously (exchange, symbol)
})
</script>
</head>
<body>
<h1>Hello, CCXT!</h1>
<pre id="content"></pre>
</body>
</html>
@@ -0,0 +1,49 @@
<!DOCTYPE HTML>
<html>
<head>
<title>CCXT Basic example for the browser</title>
<script type="text/javascript" src="https://unpkg.com/ccxt"></script>
<script>
document.addEventListener ("DOMContentLoaded", async function () {
alert ('ccxt version ' + ccxt.version);
const enableRateLimit = true
const exchange = new ccxt.poloniex ({ enableRateLimit })
const symbol = 'ETH/BTC'
while (true) {
let text = []
try {
const ticker = await exchange.fetchTicker (symbol)
text = [
exchange.id,
symbol,
JSON.stringify (exchange.omit (ticker, 'info'), undefined, '\t')
]
} catch (e) {
text = [
e.constructor.name,
e.message,
]
}
document.getElementById ('content').innerHTML = text.join (' ')
}
})
</script>
</head>
<body>
<h1>Hello, CCXT!</h1>
<pre id="content"></pre>
</body>
</html>
+43
View File
@@ -0,0 +1,43 @@
<!DOCTYPE HTML>
<html>
<head>
<title>CCXT Basic example for the browser</title>
<script type="text/javascript" src="https://unpkg.com/ccxt"></script>
<script>
document.addEventListener ("DOMContentLoaded", function () {
alert ('CCXT' + (ccxt.isBrowser ? ' browser' : '') + ' version ' + ccxt.version);
const exchange = new ccxt.coinbasepro ({ enableRateLimit: true })
const symbol = 'ETH/BTC'
exchange.fetchTicker (symbol).then (ticker => {
const text = [
exchange.id,
symbol,
JSON.stringify (ticker, undefined, '\n\t')
]
document.getElementById ('content').innerHTML = text.join (' ')
}).catch (e => {
const text = [
e.constructor.name,
e.message,
]
document.getElementById ('content').innerHTML = text.join (' ')
})
})
</script>
</head>
<body>
<h1>Hello, CCXT!</h1>
<pre id="content"></pre>
</body>
</html>
@@ -0,0 +1,28 @@
<!DOCTYPE HTML>
<html>
<head>
<title>CCXT Basic example for the browser</title>
<script type="text/javascript" src="https://unpkg.com/ccxt"></script>
<script>
document.addEventListener ("DOMContentLoaded", function () {
async function main () {
const exchange = new ccxt.binance ({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_API_SECRET',
'proxyUrl': 'https://cors-anywhere.herokuapp.com/',
})
const response = await exchange.fetchBalance ()
console.log (response)
}
main ()
})
</script>
</head>
<body>
<h1>Hello, CCXT!</h1>
<pre id="content"></pre>
</body>
</html>
@@ -0,0 +1,7 @@
// JavaScript CORS Proxy
// Save this in a file like cors.js and run with `node cors [port]`
// It will listen for your requests on the port you pass in command line or port 8080 by default
const port = (process.argv.length > 2) ? parseInt (process.argv[2]) : 8080 // default
require ('cors-anywhere').createServer ({
setHeaders: { 'origin': 'https://www.bitmex.com' }
}).listen (port, '0.0.0.0')
+46
View File
@@ -0,0 +1,46 @@
<!DOCTYPE HTML>
<html>
<head>
<title>CCXT Basic example for the browser</title>
<script type="text/javascript" src="https://unpkg.com/ccxt"></script>
<script>
document.addEventListener ("DOMContentLoaded", function () {
alert ('ccxt version ' + ccxt.version);
const exchange = new ccxt.bitmex ({
'proxyUrl': 'http://127.0.0.1:8080/',
})
const symbol = 'BTC/USDT'
exchange.fetchTicker (symbol).then (ticker => {
const text = [
exchange.id,
symbol,
JSON.stringify (ticker, undefined, '\n\t')
]
document.getElementById ('content').innerHTML = text.join (' ')
}).catch (e => {
const text = [
e.constructor.name,
e.message,
]
document.getElementById ('content').innerHTML = text.join (' ')
})
})
</script>
</head>
<body>
<h1>Hello, CCXT!</h1>
<pre id="content"></pre>
</body>
</html>
@@ -0,0 +1,61 @@
<!DOCTYPE HTML>
<html>
<head>
<title>CCXT Basic example for the browser</title>
<script type="text/javascript" src="https://unpkg.com/ccxt"></script>
<script type="text/javascript" src="https://unpkg.com/lightweight-charts@3.4.0/dist/lightweight-charts.standalone.production.js"></script>
<script>
document.addEventListener ("DOMContentLoaded", async function () {
try {
const exchange = new ccxt.coinbasepro ({ enableRateLimit: true });
const symbol = 'ETH/BTC';
const timeframe = '1d';
const since = undefined;
const limit = 600;
const config = {
width: 600,
height: 300,
priceScale: {
position: 'left',
mode: 1,
autoScale: true,
invertScale: false,
},
timeScale: {
fixLeftEdge: true,
lockVisibleTimeRangeOnResize: true,
rightBarStaysOnScroll: true,
visible: true,
timeVisible: true,
secondsVisible: true,
},
crosshair: {
mode: window.LightweightCharts.CrosshairMode.Normal,
},
};
const chart = window.LightweightCharts.createChart(document.body, config);
const candleSeries = chart.addCandlestickSeries();
while (true) {
try {
const response = await exchange.fetchOHLCV(symbol, timeframe, since, limit);
const last = response[response.length - 1]
const [ timestamp, open, high, low, close ] = last;
const data = response.map (([ timestamp, open, high, low, close ]) => ({ time: exchange.iso8601 (timestamp), open, high, low, close }));
candleSeries.setData(data);
} catch (e) {
console.log (e.constructor.name, e.message);
}
await exchange.sleep (1000);
}
} catch (e) {
alert (e.constructor.name + ' ' + e.message);
}
})
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,97 @@
<!DOCTYPE html>
<html>
<body>
<h2>CCXT running on a Webworker example</h2>
<p>This example uses a web worker to continuously call <b>fetchTicker</b> on a user-defined exchange, symbol and interval. Then, the web worker reaches back with the results.</p>
<h3>Parameters</h3>
<label for="exchange">Exchange:</label>
<input type="text" id="exchange" name="exchange" ><br><br>
<label for="symbol">Symbol: </label>
<input type="text" id="symbol" name="symbol"><br><br>
<label for="interval">Interval (ms): </label>
<input type="text" id="interval" name="interval"><br><br>
<button onclick="startWorker()">Start CCXT worker</button>
<button onclick="stopWorker()">Stop CCXT worker</button>
<h3> Received Results:</h3>
<output id="result"></output>
<table id="resultsTable" style="left:300px;width:400px; border:1px solid black;" >
<thead>
<tr>
<th>Symbol</th>
<th>Last Price</th>
<th>Base Volume</th>
<th>Exchange Ts</th>
<th>Local Ts</th>
</tr>
</thead>
<tbody id="tbody">
</tbody>
</table>
</body>
<script>
// fill in default values
document.addEventListener('DOMContentLoaded', function(event) {
document.getElementById('exchange').value = 'coinbasepro';
document.getElementById('symbol').value = 'BTC/USDT';
document.getElementById('interval').value = '1000';
});
var ccxtWorker;
function startWorker() {
if (typeof(Worker) !== "undefined") {
// if the worker does not exist yet, creates it
if (typeof(ccxtWorker) == "undefined") {
ccxtWorker = new Worker("worker.js");
}
// reads user-defined parameters
var exchange = document.getElementById('exchange').value
var symbol = document.getElementById('symbol').value
var interval = document.getElementById('interval').value
// send a message with those values
ccxtWorker.postMessage([exchange,symbol, interval]);
ccxtWorker.onmessage = function(event) {
// every time the ccxtworker posts a message
// this handler will be invoked
handleReceivedData(event.data)
};
} else {
document.getElementById("result").innerHTML = "Sorry! No Web Worker support.";
}
}
function handleReceivedData(data) {
var [symbol, last, baseVolume, timestamp, ourTimestamp] = data;
var tableRef = document.getElementById('resultsTable');
var tbody = document.getElementById('tbody');
row = tbody.insertRow(0);
cellSymbol = row.insertCell();
cellSymbol.innerHTML = symbol;
cellPrice = row.insertCell();
cellPrice.innerHTML = last;
cellPrice = row.insertCell();
cellPrice.innerHTML = baseVolume;
cellTimestamp = row.insertCell();
cellTimestamp.innerHTML = timestamp;
cellOurtimestamp = row.insertCell();
cellOurtimestamp.innerHTML = ourTimestamp;
}
function stopWorker() {
if (ccxtWorker !== undefined) {
ccxtWorker.terminate();
ccxtWorker = undefined;
}
}
</script>
</body>
</html>
@@ -0,0 +1,43 @@
self.importScripts('https://unpkg.com/ccxt@1.79.2/dist/ccxt.browser.js');
console.log("Loaded ccxt version:", self.ccxt.version);
var exchangeInstance = undefined;
// handler of received messages
self.onmessage = async function handler(msg) {
await handleMessageFromMain(msg)
}
// get messages from the main script
async function handleMessageFromMain(msg) {
console.log(msg.data);
var [exchange, symbol, interval] = msg.data;
console.log('Worker received:', symbol,exchange, interval)
interval = parseInt(interval)
await processTicker(symbol, exchange)
// schedule process ticker execution
setInterval (async () => {
await processTicker(symbol, exchange)
}, interval)
}
async function processTicker(symbol, exchangeId) {
if (exchangeInstance === undefined) {
exchangeInstance = new ccxt[exchangeId]
}
var result = await fetchTicker(symbol)
var symbol = result['symbol']
var last = result['last']
var timestamp = result['timestamp']
var baseVolume = result['baseVolume']
var ourTimestamp = Date.now()
// send the data back to the main script
postMessage([symbol, last, baseVolume, timestamp, ourTimestamp]);
}
async function fetchTicker(symbol){
// use ccxt to fetch ticker info
var result = await exchangeInstance.fetchTicker(symbol)
return result;
}

Some files were not shown because too many files have changed in this diff Show More