172 Commits
Author SHA1 Message Date
discountry 8d79ace8b3 Refactor triggerType handling in order placement logic
- Updated the triggerType assignment in placeStopLossOrder and related functions to default to "STOP_LOSS" instead of conditionally setting it based on the order side.
- This change simplifies the logic for stop market orders across the order coordinator and GRVT exchange gateway, ensuring consistent behavior.
2026-02-01 11:15:00 +08:00
discountry 1fb6d3d62d Add swing trading configuration options to .env.example
- Added new environment variables for swing trading, including SWING_DIRECTION and SWING_STOP_LOSS_PCT.
- Updated documentation in .env.example to reflect the new swing trading parameters for better clarity and usability.
2026-01-31 16:19:19 +08:00
discountry c4559cb0d7 Add swing trading strategy with RSI signals and Binance integration
- Introduced a new swing trading strategy utilizing the RSI indicator on the ETHBTC pair from Binance.
- Implemented the `SwingEngine` to manage trading logic, including entry and exit conditions based on RSI thresholds.
- Added configuration options for swing direction, trade amount, and RSI parameters in `config.ts`.
- Created new documentation for the swing strategy, detailing its behavior and configuration.
- Enhanced CLI to support the new swing strategy option.
- Added tests for swing logic to ensure correct behavior under various market conditions.
2026-01-31 16:15:07 +08:00
discountry 1d88ddefb5 Enhance account snapshot handling and staleness checks in MakerPointsEngine
- Updated `emitAccountSnapshot` method in `StandxGateway` to accept an optional `updateTime` parameter, allowing for more accurate timestamping.
- Introduced logic to determine the appropriate `updateTime` based on the latest position or balance data.
- Added `time` property to `StandxPosition` interface for improved timestamp management.
- Implemented `applyAccountSnapshot` method in `MakerPointsEngine` to streamline account snapshot processing and ensure accurate time tracking.
- Added tests to validate the behavior of account staleness checks and defense mode activation based on account data freshness.
2026-01-24 23:53:36 +08:00
discountry 683352f737 Add changeMarginMode method to ExchangeAdapter and Standx classes
- Introduced `changeMarginMode` method in `ExchangeAdapter` interface to allow margin mode adjustments.
- Implemented the `changeMarginMode` method in `StandxExchangeAdapter` to interact with the gateway for changing margin modes.
- Added corresponding `changeMarginMode` method in `StandxGateway` to handle API requests for margin mode changes.
- Enhanced `MakerPointsEngine` to ensure isolated margin mode before order placement, with appropriate logging and defense mode activation if the change fails.
- Created tests for margin mode functionality to validate behavior under different scenarios.
2026-01-24 22:57:03 +08:00
discountry a629bc940c Enhance environment variable parsing and account snapshot validation
- Introduced `normalizeEnvValue` function to improve handling of environment variable values, including trimming, unquoting, and stripping inline comments.
- Updated `resolveSymbolFromEnv` and parsing functions to utilize the new normalization logic.
- Added `validateAccountSnapshotForSymbol` function to validate account snapshots, ensuring numeric fields are correctly formatted and flagging any issues.
- Implemented tests for environment variable parsing and account snapshot validation to ensure robustness and correctness.
2026-01-24 22:46:14 +08:00
discountry fe7b8eb6f3 Update Binance WebSocket configuration and enhance depth handling
- Changed WebSocket base URL to support both spot and futures trading.
- Adjusted depth tracking parameters for improved performance, increasing the ratio and reducing speed.
- Enhanced payload parsing to accommodate additional data structures from Binance, ensuring robust handling of bids and asks.
- Updated comments for clarity on connection behavior and heartbeat monitoring.
2026-01-22 10:23:07 +08:00
discountry 24339929dc Enhance MakerPoints functionality and configuration
- Updated `filterMinDepth` in `config.ts` from 1 to 50 to improve depth filtering logic.
- Added new translation entries for band depth display in `i18n/index.ts`.
- Introduced `bandDepths` to `MakerPointsSnapshot` in `maker-points-engine.ts` to track depth across different bands.
- Enhanced `BinanceDepthTracker` to support dynamic depth levels and speed settings.
- Updated `MakerPointsApp` to display band depth information, improving user interface clarity.
2026-01-22 02:27:45 +08:00
discountry ed855f6859 Refine data staleness checks in MakerPointsEngine
- Updated the logic to only consider depth data for staleness checks, excluding account data from the criteria.
- Removed unnecessary account staleness checks from defense mode activation, streamlining the data validation process.
- Enhanced comments for clarity on the rationale behind the changes.
2026-01-21 16:30:51 +08:00
discountry e144c1822f Implement data staleness defense mode in MakerPointsEngine
- Introduced a defense mode that activates when data from StandX or Binance is stale for over 5 seconds.
- Added methods to check data freshness, enter and exit defense mode, and cancel all orders during defense mode.
- Enhanced logging to provide insights into data staleness and defense mode transitions.
- Updated connection state management for clarity and consistency.
2026-01-21 16:24:17 +08:00
discountry 69271d33ca Refactor MakerPointsEngine and BinanceDepthTracker for improved connection management
- Renamed connection state variable in MakerPointsEngine for clarity.
- Added connection state change listeners in BinanceDepthTracker to handle connection status updates.
- Implemented heartbeat monitoring and connection duration checks in BinanceDepthTracker to enhance WebSocket reliability.
- Introduced data staleness checks and improved error handling for WebSocket connections.
- Enhanced logging for connection events to provide better insights into connection status changes.
2026-01-21 16:03:34 +08:00
discountry f1140f106a Enhance WebSocket connection management and data handling
- Introduced constants for WebSocket reconnection delays, heartbeat timeout, and data staleness thresholds.
- Implemented heartbeat monitoring to ensure timely reconnections on inactivity.
- Added data staleness checks to trigger REST API calls when market or account data is outdated.
- Enhanced the StandxGateway class with methods for managing heartbeat and data checks, improving overall connection reliability and data integrity.
2026-01-21 15:40:28 +08:00
discountry 3b935b7979 Refactor MakerPoints configuration and depth handling
- Renamed `band0To10MinDepth` to `filterMinDepth` in `config.ts` for clarity.
- Updated `MakerPointsEngine` to utilize the new `filterMinDepth` for depth checks across all bands.
- Introduced a method to track depth status changes, enhancing order placement logic based on market depth.
- Improved logging for depth-related order skips to provide clearer insights into trading decisions.
2026-01-21 11:26:21 +08:00
discountry 00388f9166 add filter 2026-01-21 11:11:58 +08:00
discountry a32efa2ba0 Refine target price calculation in LiquidityMakerEngine
- Updated target price logic to consider entry price when no recent fills are available, enhancing order placement accuracy.
- Adjusted conditions to ensure target prices are set appropriately based on market conditions and entry prices, preventing potential losses.
- Improved comments for clarity on the logic behind target price adjustments.
2026-01-20 01:28:24 +08:00
discountry 76704b6bdd Enhance entry price logic in Maker and Liquidity Maker strategies
- Added `entryDepthLevel` configuration option to `MakerConfig` and `LiquidityMakerConfig` for specifying order entry levels.
- Implemented `getPricesAtLevel` utility function to retrieve bid and ask prices at specified depth levels.
- Updated `MakerEngine`, `LiquidityMakerEngine`, and `OffsetMakerEngine` to utilize the new entry level logic for determining opening prices based on market depth.
- Improved price handling to ensure more accurate order placements in varying market conditions.
2026-01-20 01:03:38 +08:00
discountry 168d8cbb08 Add Claude instructions and enhance stop-loss logic
- Introduced a new `CLAUDE.md` file with instructions for using Bun as the package manager.
- Adjusted stop-loss cooldown and check intervals in `MakerPointsEngine` for improved responsiveness.
- Implemented a new method to compute real-time PnL using live depth data, enhancing stop-loss decision-making.
- Added retry logic for stop-loss execution to ensure positions are closed effectively, with detailed logging for failures.
2026-01-20 00:51:15 +08:00
discountry 9629c22496 Enhance MakerPoints configuration and logic
- Added new configuration options for band-specific order amounts in `config.ts`.
- Implemented conditional logic in `MakerPointsEngine` to utilize the new band amounts based on the Binance depth cancel setting.
- Refactored order amount handling to improve clarity and maintainability.
2026-01-18 01:49:36 +08:00
discountry a34d06f9b4 fix slprice 2026-01-16 23:05:30 +08:00
discountry 2ba3e80ad9 fix sl 2026-01-16 22:59:38 +08:00
discountry 12e8e3e064 Update API token creation date in documentation and configuration
- Revised the `.env.example` and `maker-points-guide.md` to reflect the updated token creation date from 2025-01-15 to 2026-01-15.
- Enhanced the `order-coordinator.ts`, `order-schema.ts`, and `types.ts` files to support stop-loss and take-profit price parameters in order intents.
- Updated the `StandxGateway` and `order.ts` to handle new stop-loss and take-profit parameters in order creation.
- Improved the `MakerPointsEngine` to calculate stop-loss prices based on order type, enhancing order management capabilities.
2026-01-16 11:29:26 +08:00
discountry aa24995d28 Enhance WebSocket and API documentation; implement connection protection features
- Added a note in the HTTP API documentation regarding the non-guaranteed sequence of price levels in order book responses.
- Updated WebSocket documentation to include a connection duration limit and a note on local sorting requirements for price levels.
- Introduced connection event handling in the ExchangeAdapter interface, allowing for disconnection and reconnection events.
- Implemented connection protection logic in the StandxExchangeAdapter and MakerPointsEngine to manage order states during connection disruptions.
- Enhanced the StandxGateway with methods for querying open orders and forcefully canceling all orders, improving reliability during network issues.
2026-01-16 10:49:16 +08:00
discountry d493642935 update doc 2026-01-15 22:06:10 +08:00
discountry 86670486a6 Update StandX API documentation and configuration
- Revised `.env.example` to reflect new API token generation process, emphasizing the use of creation date and validity days for token expiry management.
- Enhanced `auth.md` with detailed instructions for obtaining API tokens and signing transactions for both EVM and Solana wallets.
- Updated `maker-points-guide.md` to clarify the API token retrieval process and the significance of the Ed25519 private key.
- Refactored `config.ts` and `gateway.ts` to support new token expiry configuration methods and improved private key handling, including Base58 decoding.
- Improved overall documentation clarity and user guidance for new and existing users.
2026-01-15 16:11:08 +08:00
discountry 6496011d8f Add Nado exchange support to README
- Included details for the Nado USDC perpetuals, specifying required environment variables and configuration options.
- Updated both English and Chinese versions of the README to reflect the new exchange integration, enhancing user guidance and clarity.
2026-01-14 18:28:14 +08:00
discountry 792351ab8a Add Liquidity Maker strategy and related configurations
- Introduced a new `LiquidityMakerConfig` interface and corresponding configuration settings in `config.ts`.
- Updated CLI argument handling to include the new "liquidity-maker" strategy option.
- Implemented the `LiquidityMakerEngine` class to manage the liquidity making strategy, including order handling and risk management.
- Added a new `LiquidityMakerApp` component for user interaction and display of strategy status.
- Enhanced internationalization support with translations for the liquidity maker strategy.
- Updated the main application to integrate the new liquidity maker strategy into the existing framework.
2026-01-14 00:56:29 +08:00
discountry 4915dc574e Implement precision error handling in MakerPointsEngine
- Added a new `isPrecisionError` function to identify precision-related errors in the error utility module.
- Updated the MakerPointsEngine to handle precision errors by logging warnings and synchronizing precision when such errors occur during order processing and stop-loss execution.
- Enhanced the `syncPrecision` method to allow forced synchronization, improving the handling of precision-related issues.
2026-01-13 20:23:18 +08:00
discountry 9866e8068f Clarify instructions in Maker Points guide regarding the proxy wallet private key format and environment variable setup. Emphasize that the private key should be copied as is, without the '0x' prefix, to enhance user understanding and security practices. 2026-01-12 18:03:26 +08:00
discountry 099af3ce01 Update Maker Points guide to clarify proxy wallet private key format and environment variable instructions. Specify that the private key should generally not include the '0x' prefix, enhancing user understanding and security practices. 2026-01-12 18:02:05 +08:00
discountry 445e634aa1 Refactor Telegram notification handling and remove unused functions
- Removed deprecated functions for masking sensitive data and previewing text, streamlining the Telegram notification process.
- Simplified logging by eliminating unnecessary console outputs related to notification configuration and sending.
- Updated the `TelegramNotifier` class to enhance clarity and maintainability, focusing on essential notification functionality.
2026-01-12 12:39:37 +08:00
discountry 4bb1fee995 Refactor Telegram notification handling in MakerPointsEngine
- Introduced a dedicated `notify` method to streamline notification sending and improve logging for Telegram notifications.
- Added a new environment variable check for enabling debug logging of Telegram notifications.
- Enhanced logging to include detailed information about notification attempts, including masked sensitive data for security.
- Updated various notification calls to utilize the new `notify` method, ensuring consistent logging and functionality.
2026-01-12 12:23:24 +08:00
discountry aad14395e0 Enhance Telegram notification functionality
- Introduced functions to mask sensitive information and preview notification text for improved logging and security.
- Added detailed logging for notification sending process, including configuration details and response handling.
- Implemented checks to prevent sending notifications when bot token or chat ID is missing, with appropriate warnings logged.
2026-01-12 12:12:14 +08:00
discountry 598f2a0eb6 Add token expiry and Telegram notification features
- Introduced `STANDX_TOKEN_EXPIRY` configuration to manage token expiration, including handling logic for active, expired, and silent states.
- Implemented Telegram notifications for key events such as order filled, position opened/closed, stop loss triggered, and token expiration.
- Updated Maker Points engine to integrate token expiry checks and notification sending, enhancing user awareness of trading conditions.
- Enhanced documentation to include details on configuring token expiry and Telegram notifications for improved user guidance.
2026-01-10 12:44:52 +08:00
discountry cb1cef6f1b Revise Maker Points guide to provide a comprehensive step-by-step tutorial for new users. Update installation instructions for Bun, enhance clarity on obtaining StandX login credentials, and improve environment variable configuration details. Add safety tips and common troubleshooting questions to support user onboarding. 2026-01-09 01:11:10 +08:00
discountry fd034d493f Enhance README with language setting instructions, updated referral links, and additional documentation for StandX and Nado exchanges. Clarify environment variable setup and improve formatting for better readability. 2026-01-07 23:39:40 +08:00
discountry 2551670874 Update README and Maker Points guide to clarify the export of StandX login credentials, specifying token and proxy wallet private key for enhanced user security. 2026-01-07 02:46:38 +08:00
discountry 3ec7e9b8d6 Update Maker Points guide to include details on exporting the generated proxy wallet private key along with the token, enhancing security instructions for users. 2026-01-06 21:49:28 +08:00
discountry 499ee692da Merge branch 'main' into feat/mm 2026-01-06 20:17:49 +08:00
discountry de13142950 Add StandX Maker Points strategy tutorial and update README with configuration details. Include steps for obtaining login token and environment variable setup for new users, enhancing documentation clarity and usability. 2026-01-06 20:08:38 +08:00
DisneyandGitHub 597e41f053 Merge pull request #16 from discountry/feat/mm
Feat/mm
2026-01-06 17:50:38 +08:00
discountry b0a33a58d1 Remove market maker documentation from StandX. This deletion includes all content related to Maker Points, order management, and WebSocket API details, streamlining the documentation for clarity and focus. 2026-01-06 17:49:29 +08:00
discountry 65b9f21981 Add new PM2 start command for Maker Points strategy in package.json. This includes a dedicated command for 'maker-points' with specific exchange settings, enhancing deployment options for the application. 2026-01-06 16:37:51 +08:00
discountry 33b5407245 Refactor Maker Points logic by removing dislocation calculations and related UI elements. Update MakerPointsEngine to utilize new price fetching methods and streamline order synchronization. Adjust translations and tests accordingly to reflect these changes. 2026-01-06 16:18:54 +08:00
discountry 7aafc3b69d Add Maker Points strategy support in StandX. Introduce new configuration for Maker Points, including point bands and order management logic. Implement MakerPointsEngine for handling order placement and tracking. Update CLI and UI components to integrate Maker Points functionality, enhancing user experience and strategy options. 2026-01-06 16:01:10 +08:00
discountry 9a093459bc Add debugging options and enhance WebSocket handling in StandxGateway. Introduce debugWs and debugWsRaw flags for improved logging of WebSocket events and payloads. Implement decrossDepthBook function for better order book management and update message handling to support multiple JSON payloads. 2025-12-21 16:54:14 +08:00
discountry aa36b0cfdc Implement depth level normalization in StandxGateway for improved order book handling. Refactor depth data processing to utilize the new normalizeDepthLevels function, ensuring consistent bid and ask sorting. Update symbol handling to accommodate fallback logic. 2025-12-21 16:15:44 +08:00
discountry 93c6409688 Integrate StandX exchange support by updating configuration files, adding environment variables, and enhancing documentation. Include new API endpoints and authentication details for StandX in README and dedicated documentation files. Update CLI and adapter logic to accommodate StandX functionalities. 2025-12-21 15:37:03 +08:00
discountry 84d5e1f3d7 Update README files to include Nado referral link and detailed setup instructions for Nado integration, enhancing user guidance for configuration and usage. 2025-12-20 14:53:25 +08:00
discountry 6e5413ec1e Refactor order status handling in NadoGateway and MakerEngine. Introduce isOrderActiveStatus utility to streamline order filtering logic. Add tests for error handling and order status utilities. 2025-12-20 13:10:48 +08:00
discountry 84e8ce1d43 Enhance NadoGateway with min size policy handling and related utility functions. Update .env.example to include NADO_MIN_SIZE_POLICY configuration option. 2025-12-19 11:11:35 +08:00
discountry c69ea72860 Add Nado documentation and examples, including new API endpoints, FAQs, and guides for using the TypeScript SDK. Update .env.example with additional configuration options. 2025-12-19 01:38:09 +08:00
discountry 624fecfa70 add nado packages 2025-12-18 03:14:20 +08:00
discountry 6dcf13481d Enhance README.md with a detailed description of the Bun-powered multi-exchange perpetuals workstation, highlighting key features such as the SMA30 trend engine, Guardian stop sentinel, and market-making modes. 2025-12-09 00:38:23 +08:00
discountry 3fd0f715a1 Update README.md to instruct users to set LANG=en in .env for English interface support. 2025-12-09 00:36:50 +08:00
discountry 85e7f245c0 Implement internationalization support by adding translation functionality and updating UI components to use translated strings. Add language configuration in .env.example and integrate translations across various strategy and UI components. 2025-12-09 00:35:28 +08:00
discountry 03df1006cc Update README.md to include a link for English users and remove the outdated English README reference. 2025-12-09 00:00:28 +08:00
DisneyandGitHub 3099cb1319 Merge pull request #13 from discountry/feat/lighter-spot
Feat/lighter spot
2025-12-08 23:57:07 +08:00
discountry 49b8f0bcf1 fix perp 2025-12-08 23:55:13 +08:00
discountry a23eb91f04 Remove lighter-go-main directory and associated files, including client, types, and documentation, to streamline the project structure. Update .gitignore to exclude temporary files. 2025-12-08 23:44:05 +08:00
discountry 974d3017e0 fix position 2025-12-08 19:57:57 +08:00
discountry 668c3a57d3 fix open orders 2025-12-08 01:28:33 +08:00
discountry 819ce93a90 fix stoploss 2025-12-07 23:35:10 +08:00
discountry 061a8dd8f1 fix: improve lighter ws reconnect 2025-12-07 23:18:29 +08:00
discountry 49df78c075 fix nonce 2025-12-07 22:48:23 +08:00
discountry 6a6b61c491 fix nonce 2025-12-07 22:36:37 +08:00
discountry 7612e25dd1 add stoploss 2025-12-07 22:33:56 +08:00
discountry 705e489c17 fix nonce 2025-12-07 22:28:48 +08:00
discountry 9a418aba41 add trend skip 2025-12-07 22:15:34 +08:00
discountry 2cb6c4fef1 feat: add minBaseAmount and minQuoteAmount to ExchangePrecision and LighterGateway for improved trading logic 2025-12-07 21:52:35 +08:00
discountry 180f47b6e0 support lighter spot 2025-12-07 21:37:58 +08:00
discountry 5f79b134f2 refactor: restructure Lighter SDK by removing deprecated Go files and enhancing Python documentation with new examples and models 2025-12-07 19:14:56 +08:00
discountry 85705dd27c chore: update referral links in README files for Lighter and Aster 2025-11-29 01:57:44 +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
1503 changed files with 262278 additions and 2035 deletions
+110 -2
View File
@@ -1,14 +1,36 @@
# UI language (zh | en)
LANG=zh
# Exchange selection
EXCHANGE=aster # Pick aster (default) or grvt
EXCHANGE=aster # Pick aster (default) or standx/grvt/lighter/backpack/paradex/nado
# Aster API credentials
ASTER_API_KEY=
ASTER_API_SECRET=
# StandX authentication (set when EXCHANGE=standx)
STANDX_TOKEN=
STANDX_SYMBOL=BTC-USD
# STANDX_BASE_URL=https://perps.standx.com
# STANDX_WS_URL=wss://perps.standx.com/ws-stream/v1
# STANDX_SESSION_ID=
# Optional: request signing key (ed25519 private key, supports hex or base58 format)
# STANDX_REQUEST_PRIVATE_KEY=
# Token expiry configuration (recommended method: creation date + validity days)
# Get these values when generating API token at https://standx.com/user/session
# STANDX_TOKEN_CREATE_DATE=2026-01-15 # Token creation date (YYYY-MM-DD format)
# STANDX_TOKEN_VALIDITY_DAYS=30 # Token validity period in days
# Legacy method: direct expiry timestamp (Unix seconds)
# STANDX_TOKEN_EXPIRY=1737092800
# Core trading symbol and sizing
TRADE_SYMBOL=BTCUSDT # Trading pair symbol
TRADE_AMOUNT=0.001 # Base order quantity (base asset, e.g. BTC)
# Swing Trading
SWING_DIRECTION=short # short | long | both
SWING_STOP_LOSS_PCT=0.05 # 0.05 = 5%
# Risk management (USD amounts unless noted)
LOSS_LIMIT=0.04 # Max loss per trade in USDT before forced close
TRAILING_PROFIT=0.2 # Trailing stop activation profit (USDT)
@@ -33,10 +55,26 @@ MAX_CLOSE_SLIPPAGE_PCT=0.05 # Max allowed deviation vs mark when clo
MAKER_LOSS_LIMIT=0.05 # Maker loss cap (USDT). Defaults to LOSS_LIMIT if unset
MAKER_BID_OFFSET=0 # Bid quote offset from top bid (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_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_API_KEY=
GRVT_API_SECRET=
@@ -61,3 +99,73 @@ LIGHTER_ENV=testnet # mainnet | testnet | staging | dev
# 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
# Nado exchange configuration (Ink mainnet)
# Requires a linked signer private key + your original subaccount owner EVM address.
# When EXCHANGE=nado these values are used automatically.
NADO_ENV=inkMainnet # inkMainnet | inkTestnet
NADO_SYMBOL=BTC-PERP # Trading product symbol (e.g., BTC-PERP / ETH-PERP)
NADO_SIGNER_PRIVATE_KEY= # 32-byte 0x-prefixed private key (0x...)
NADO_SUBACCOUNT_OWNER= # EVM address of the subaccount owner (0x...)
NADO_SUBACCOUNT_NAME=default # Subaccount name (bytes12, default "default")
# Optional: market-order slippage buffer (used for IOC limit-as-market, e.g. 0.01 => 1%)
NADO_MARKET_SLIPPAGE_PCT=0.01
# Optional: stop trigger source for STOP_MARKET orders (oracle | last | mid)
NADO_STOP_TRIGGER_SOURCE=oracle
# Optional: how to handle orders smaller than Nado min_size (USDT0 notional)
# - adjust: round quantity up to the minimum allowed size (default)
# - reject: throw an error instead of auto-adjusting
NADO_MIN_SIZE_POLICY=adjust
# Optional endpoint overrides
# NADO_GATEWAY_WS_URL=wss://gateway.prod.nado.xyz/v1/ws
# NADO_SUBSCRIPTIONS_WS_URL=wss://gateway.prod.nado.xyz/v1/subscribe
# NADO_ARCHIVE_URL=https://archive.prod.nado.xyz/v1
# NADO_TRIGGER_URL=https://trigger.prod.nado.xyz/v1
# NADO_DEBUG=false
# Telegram notification configuration
# TELEGRAM_BOT_TOKEN= # Telegram bot token from @BotFather
# TELEGRAM_CHAT_ID= # Chat ID to receive notifications
# TELEGRAM_ACCOUNT_LABEL= # Account label to distinguish multiple bot instances (e.g., "Account-A")
+3
View File
@@ -33,3 +33,6 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Finder (MacOS) folder config
.DS_Store
.tmp
.tmp/*
+12
View File
@@ -0,0 +1,12 @@
# RitMEX Bot - Claude Instructions
## Package Manager
**必须使用 Bun** - 这个项目使用 Bun 作为包管理器和运行时。所有能用 bun 执行的命令都必须使用 bun:
- 安装依赖: `bun install`
- 运行脚本: `bun run <script>`
- 执行测试: `bun test`
- 类型检查: `bun run typecheck`
**不要使用 npm、yarn 或 npx**
+143 -62
View File
@@ -1,42 +1,71 @@
# ritmex-bot
基于 Bun 的 Aster 永续合约量化终端,内置趋势跟随(SMA30)与做市策略,支持快速恢复、实时行情订阅与日志追踪。
> For English users, please see [README_en.md](README_en.md).
* [Aster 30% 手续费优惠注册链接](https://www.asterdex.com/zh-CN/referral/4665f3)
Please set `LANG=en` in `.env` for English interface.
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.
基于 Bun 的多交易所永续合约量化终端,内置趋势跟随(SMA30)、Guardian 防守与做市策略,支持快速恢复、实时行情订阅、日志追踪与 CLI 仪表盘。
如果您希望获取优惠并支持本项目,请考虑使用以下注册链接:
* [Lighter 手续费优惠注册链接](https://app.lighter.xyz/?referral=111909FA)
* [Aster 手续费优惠注册链接](https://www.asterdex.com/zh-CN/referral/4665f3)
* [StandX 手续费优惠注册链接](https://standx.com/referral?code=xingxingjun)
* [Binance 手续费优惠注册链接](https://www.binance.com/join?ref=KNKCA9XC)
* [GRVT 手续费优惠注册链接](https://grvt.io/exchange/sign-up?ref=sea)
* [Nado 手续费优惠注册链接](https://app.nado.xyz?join=LKbIUs5)
* [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 自动同步账户、挂单与仓位。
## 核心特性
- **实时行情与风控**Websocket + REST 自动同步账户、挂单与仓位,断线后自动恢复
- **趋势策略**:SMA30 穿越入场,内置止损、移动止盈、布林带带宽过滤与步进锁盈。
- **做市策略**:支持双边追价、风险阈值与订单自愈
- **模块化设计**:适配器、策略引擎与 CLI 解耦,方便扩展新交易所或策略
- **Guardian 策略**:不主动开单,实时监听账户仓位并强制补挂/移动止损与动态止盈,防止裸奔
- **做市策略**:支持双边追价、风险阈值控制与订单自愈
- **模块化架构**:策略引擎、交易所适配器与 Ink CLI 相互解耦,新增交易所或策略更容易。
## 环境要求
- Bun ≥ 1.2(含 `bun``bunx` 命令)
- macOS、Linux 或 Windows (WSL 推荐)
- Node.js 仅在某些安装路径需要,可选
## 支持的交易所
| 交易所 | 合约类型 | 必填环境变量 | 备注 |
| --- | --- | --- | --- |
| Aster | USDT 永续 | `ASTER_API_KEY`, `ASTER_API_SECRET` | 默认交易所;兼容脚本引导
| StandX | USD 永续 | `STANDX_TOKEN` | 使用 JWT Token 登录,优先走 WebSocket 推送
| 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` 使用测试网
| Nado | USDC 永续 | `NADO_SIGNER_PRIVATE_KEY`, `NADO_SUBACCOUNT_OWNER` | `NADO_ENV` 可切换 `inkMainnet`/`inkTestnet`
## 快速启动脚本(macOS / Linux / WSL
## 系统要求
- Bun ≥ 1.2(需同时包含 `bun``bunx` 命令)
- macOS、Linux 或 Windows (推荐 WSL)
- Node.js 仅在部分工具链场景需要,可选
## 快速上手
### 一键脚本(macOS / Linux / WSL
```bash
curl -fsSL https://github.com/discountry/ritmex-bot/raw/refs/heads/main/setup.sh | bash
```
脚本会安装 Bun、依赖,收集 Aster API Key/Secret,生成 `.env` 并启动 CLI。运行前请准备好 API 凭证
脚本会安装 Bun、项目依赖,收集 Aster API 凭证,生成 `.env` 并启动 CLI。运行前请准备好对应交易所的 API Key/Secret
## 手动安装步骤
### 手动安装
1. **获取代码**
```bash
git clone https://github.com/discountry/ritmex-bot.git
cd ritmex-bot
```
便使用 Git 时,可在仓库页面下载 ZIP 手动解压。
不便使用 Git 时,可在仓库页面下载 ZIP 手动解压。
2. **安装 Bun**
- macOS / Linux`curl -fsSL https://bun.sh/install | bash`
- Windows PowerShell`powershell -c "irm bun.sh/install.ps1 | iex"`
安装后重新打开终端,确认 `bun -v` 正常输出版本号。
安装完成后重新打开终端,确认 `bun -v` 正常输出版本号。
3. **安装依赖**
```bash
bun install
@@ -45,67 +74,124 @@ curl -fsSL https://github.com/discountry/ritmex-bot/raw/refs/heads/main/setup.sh
```bash
cp .env.example .env
```
按下文说明修改 `.env`,至少需要正确配置 Aster 或 GRVT 的 API
按下文指南修改 `.env`,至少需要正确配置一个交易所的凭证
5. **运行 CLI**
```bash
bun run index.ts
```
方向键选择策略回车启动;`Esc` 返回菜单,`Ctrl+C` 退出。
方向键选择策略回车启动;`Esc` 返回菜单,`Ctrl+C` 退出。
## 环境变量配置指南
核心变量在 `.env.example` 中给出默认值
## 通用环境变量
`.env.example` 提供了所有默认键值,下表概括最常用参数
| 变量 | 说明 |
| --- | --- |
| `ASTER_API_KEY` / `ASTER_API_SECRET` | Aster API 凭证,运行策略必填 |
| `EXCHANGE` | 选择交易所(`aster`/`standx`/`grvt`/`lighter`/`backpack`/`paradex`/`nado` |
| `TRADE_SYMBOL` | 交易对(默认 `BTCUSDT` |
| `TRADE_AMOUNT` | 单笔下单数量(标的资产计) |
| `LOSS_LIMIT` | 单笔最大亏损触发的强平额度(USDT) |
| `TRAILING_PROFIT` / `TRAILING_CALLBACK_RATE` | 动态止盈触发值(USDT)与回撤百分比 |
| `PROFIT_LOCK_TRIGGER_USD` / `PROFIT_LOCK_OFFSET_USD` | 浮盈超过阈值后上调止损的触发金额与偏移 |
| `BOLLINGER_LENGTH` / `BOLLINGER_STD_MULTIPLIER` | 布林带宽度判定的窗口长度与标准差倍数 |
| `MIN_BOLLINGER_BANDWIDTH` | 仅当带宽 ≥ 此比例时才触发入场信号 |
| `BOLLINGER_*` | 趋势策略布林带过滤参数 |
| `PRICE_TICK` / `QTY_STEP` | 交易所要求的最小报价与数量精度 |
| `POLL_INTERVAL_MS` | 趋势策略循环间隔(毫秒) |
| `MAX_CLOSE_SLIPPAGE_PCT` | 平仓时相对标记价允许的最大偏差 |
| `MAKER_*` 系列 | 做市策略独有参数(追价阈值、报价偏移、刷新频率等) |
| `MAKER_*` | 做市策略专属参数(追价阈值、报价偏移、刷新频率等) |
切换到 GRVT 时,将 `EXCHANGE=grvt` 并补齐 `GRVT_API_KEY`、`GRVT_API_SECRET`、`GRVT_SUB_ACCOUNT_ID` 等变量;详情见 `.env.example`。
> 提示:你也可以通过命令行参数临时指定交易所(优先级高于环境变量):
> 可通过命令行临时覆盖交易所与策略(优先级高于 `.env`):
> ```bash
> bun run index.ts --exchange grvt
> bun run index.ts -e lighter
> bun run index.ts --exchange grvt --strategy maker
> bun run index.ts -e lighter -s offset-maker --silent
> ```
## 常用命令
## 交易所配置指南
### Aster
1. 将 `EXCHANGE` 保持为 `aster`(默认值)。
2. 填写 `ASTER_API_KEY` 与 `ASTER_API_SECRET`。
3. 根据交易对调整 `TRADE_SYMBOL`、`PRICE_TICK`、`QTY_STEP` 等精度参数。
4. 一键脚本会自动写入这些变量,手动部署时需自行维护。
### StandX
* [StandX 做市策略教程](docs/standx/maker-points-guide.md)
策略需要 StandX 的 API Token 和签名私钥才能下单。
**获取方式(使用 StandX 官方 API 生成功能):**
1. 打开 StandX 官方 API 创建页面:https://standx.com/user/session
2. 连接钱包并登录
3. 点击 **"Generate API Token"** 按钮
4. 页面会显示以下信息:
- **Token**(以 `eyJ` 开头的 JWT 字符串)→ 填入 `STANDX_TOKEN`
- **Ed25519 Private Key**Base58 格式私钥,类似 `HdsyJD7oWgT...`)→ 填入 `STANDX_REQUEST_PRIVATE_KEY`
- **创建日期** 和 **有效期天数** → 用于配置 Token 过期提醒
> Ed25519 Private Key 是系统自动生成的签名私钥,仅用于交易请求签名,你的资产仍在主钱包中,非常安全。
请妥善保存这些凭证,不要分享给他人。
**配置步骤:**
1. 设置 `EXCHANGE=standx`。
2. 填写 `STANDX_TOKEN`Perps API 的 JWT Token)。
3. 填写 `STANDX_REQUEST_PRIVATE_KEY`Ed25519 签名私钥,Base58 格式)。
4. 设置 `STANDX_SYMBOL`(默认 `BTC-USD`),并校准 `PRICE_TICK` / `QTY_STEP`。
5. 推荐配置 Token 过期时间:
- `STANDX_TOKEN_CREATE_DATE`(创建日期,格式 `YYYY-MM-DD`
- `STANDX_TOKEN_VALIDITY_DAYS`(有效期天数)
6. 可选:`STANDX_BASE_URL`、`STANDX_WS_URL`、`STANDX_SESSION_ID` 用于自定义环境。
### 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` 或调试开关。
### Nado
1. 设置 `EXCHANGE=nado`。
2. 在 Nado 官网(交易界面)打开开发者工具(F12)→ 切换到 `Application` → `Local Storage`,找到 `nado.userSettings`,在其内容中取出 `privateKey` 字段并填入 `.env` 的 `NADO_SIGNER_PRIVATE_KEY`。
3. 提供 `NADO_SUBACCOUNT_OWNER`(或 `NADO_EVM_ADDRESS`)。
4. 选择网络 `NADO_ENV=inkMainnet`(主网)或 `inkTestnet`(测试网)。
5. 设置交易品种 `NADO_SYMBOL`(交易对格式类似 `BTC-PERP`;也支持输入 `BTCUSDT0`,会自动映射为 `BTC-PERP`)。
## 命令速查
```bash
bun run index.ts # 启动 CLI(默认)
bun run start # 同上
bun run dev # 调试模式,等价于运行 index.ts
bun x vitest run # 执行单元测试
bun run index.ts # 启动 CLI(默认入口
bun run start # 等价于运行 index.ts
bun run dev # 调试模式
bun x vitest run # 执行全部测试
```
## 静默启动与后台运行
### 直接静默启动
无需进入 Ink 菜单,可用命令行直接拉起指定策略:
```bash
bun run index.ts --strategy trend --silent # 启动趋势策略
bun run index.ts --strategy maker --silent # 启动做市策略
bun run index.ts --strategy offset-maker --silent # 启动偏移做市策略
```
如需同时指定交易所,可叠加 `--exchange/-e`(将覆盖 `.env` 中的 `EXCHANGE`/`TRADE_EXCHANGE`):
```bash
bun run index.ts --exchange grvt --strategy maker --silent
bun run index.ts -e lighter -s offset-maker --silent
bun run index.ts --strategy trend --silent
bun run index.ts --strategy maker --silent
bun run index.ts --strategy offset-maker --silent
```
如需同时指定交易所,可叠加 `--exchange/-e` 参数。
### 项目内置脚本
`package.json` 提供了便捷脚本:
```bash
bun run start:trend:silent
bun run start:maker:silent
@@ -113,43 +199,38 @@ bun run start:offset:silent
```
### 使用 pm2 守护并自动重启
`pm2` 安装到项目中(示例:`bun add -d pm2`,之后即可在不安装全局 pm2 的情况下运行:
安装 `pm2`(示例:`bun add -d pm2`后,可在项目内直接运行:
```bash
bunx pm2 start bun --name ritmex-trend --cwd . --restart-delay 5000 -- run index.ts --strategy trend --silent
```
亦可直接调用脚本:
或调用预置脚本:
```bash
bun run pm2:start:trend
bun run pm2:start:maker
bun run pm2:start:offset
```
根据需要调整 `--name`、`--cwd`、`--restart-delay` 等参数,完成后可执行 `pm2 save` 持久化进程列表。
完成配置后可执行 `pm2 save` 持久化进程列表。
## 测试
项目使用 Vitest
```bash
bun run test # 运行全部测试
bun run test
bun x vitest --watch
```
## 常见问题
- 你需要至少 50-100 USDT 的资金才能运行策略
- 请在交易所自行设置 50 倍左右的杠杆,本策略不包含杠杆设置
- 请确保你电脑/服务器的时间是准确的真实世界时间
- 持仓方式需要保持单向持仓
- 至少准备 50–100 USDT 资金以覆盖策略运行需求。
- 杠杆需在交易所提前设置(建议 ~50 倍),程序不会自动调整。
- 请确保服务器/电脑时间同步真实世界时间,避免签名过期。
- 账户需保持单向持仓模式。
- `.env` 未读取:确认文件位于项目根目录且变量名无误。
- API 拒绝访问:检查交易所后台权限,确保开启合约读写。
- 精度错误:同步交易对的最小价格与数量步长。
更多排查步骤可参 [简明上手指南](simple-readme.md)。
更多排查细节可参 [简明上手指南](simple-readme.md)。
## 社区与支持
- Telegram 交流群:[https://t.me/+4fdo0quY87o4Mjhh](https://t.me/+4fdo0quY87o4Mjhh)
- 反馈或新特性建议请提交 Issue 或 PR
- 欢迎通过 Issue 或 PR 提交反馈、特性建议
## 风险提示
量化交易具备风险。建议在仿真或小额账户中验证策略表现,妥善保管 API 密钥,仅开启必要权限。
量化交易具备风险。请先在仿真或小额账户中验证策略表现,妥善保管 API 密钥,仅开启必要权限。
+148 -67
View File
@@ -1,39 +1,67 @@
# ritmex-bot
A Bun-powered trading workstation for Aster perpetual contracts that ships two production-ready agents: an SMA30 trend follower and a dual-sided market maker. The CLI is built with Ink, synchronises risk state from the exchange, and automatically recovers from restarts or disconnects.
**Language Setting**: Set `LANG=en` in your `.env` file to display the CLI interface in English.
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.
If you'd like to support this project and get fee discounts, please consider using these referral links:
* [Lighter referral link](https://app.lighter.xyz/?referral=111909FA)
* [Aster referral link](https://www.asterdex.com/en/referral/4665f3)
* [StandX referral link](https://standx.com/referral?code=xingxingjun)
* [Binance referral link](https://www.binance.com/join?ref=KNKCA9XC)
* [GRVT referral link](https://grvt.io/exchange/sign-up?ref=sea)
* [Nado referral link](https://app.nado.xyz?join=LKbIUs5)
* [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/SEA)
## Documentation Map
- [中文 README](README.md)
- [Beginner-friendly Quick Start](simple-readme.md)
- [Grid Trading Strategy Guide](grid-trading.md)
## Highlights
- **Live market data & risk sync** via websocket feeds with REST fallbacks, full reconciliation on restart.
- **Trend engine** featuring SMA30 entries, fixed stop loss, trailing stop, Bollinger bandwidth gate, and profit-lock stepping.
- **Market-making loop** with adaptive quote chasing, loss caps, and automatic order healing.
- **Extensible architecture** decoupling exchange adapters, engines, and the Ink CLI for easy venue or strategy additions.
- **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 |
| StandX | USD perpetuals | `STANDX_TOKEN` | Uses JWT token auth; prefer websocket streams |
| 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 |
| Nado | USDC perpetuals | `NADO_SIGNER_PRIVATE_KEY`, `NADO_SUBACCOUNT_OWNER` | Switch `NADO_ENV` between `inkMainnet` and `inkTestnet` |
## Requirements
- Bun 1.2 (`bun`, `bunx` available on PATH)
- 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 environment requires it for tooling
- Node.js is optional unless your tooling requires it
## One-Line Bootstrap (macOS / Linux / WSL)
## 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 your API Key/Secret before running.
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
### 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.
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 confirm `bun -v` prints a version.
Re-open the terminal and verify `bun -v` prints a version.
3. **Install dependencies**
```bash
bun install
@@ -42,67 +70,124 @@ The script installs Bun, project dependencies, collects Aster API credentials, g
```bash
cp .env.example .env
```
Edit `.env` with your exchange credentials and overrides.
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 return to the menu, and `Ctrl+C` to exit.
Use the arrow keys to pick a strategy, `Enter` to start, `Esc` to go back, and `Ctrl+C` to exit.
## Environment Variables
The most important settings shipped in `.env.example` are summarised below:
## Shared Configuration
`.env.example` captures all defaults; the most common settings are summarised below.
| Variable | Purpose |
| --- | --- |
| `ASTER_API_KEY` / `ASTER_API_SECRET` | Required Aster exchange credentials |
| `TRADE_SYMBOL` | Contract symbol, defaults to `BTCUSDT` |
| `EXCHANGE` | Choose the venue (`aster` / `standx` / `grvt` / `lighter` / `backpack` / `paradex` / `nado`) |
| `TRADE_SYMBOL` | Contract symbol (defaults to `BTCUSDT`) |
| `TRADE_AMOUNT` | Order size in base asset units |
| `LOSS_LIMIT` | Max per-trade loss (USDT) before forced close |
| `TRAILING_PROFIT` / `TRAILING_CALLBACK_RATE` | Trailing stop trigger amount (USDT) and pullback percentage |
| `PROFIT_LOCK_TRIGGER_USD` / `PROFIT_LOCK_OFFSET_USD` | Move the base stop once unrealised PnL exceeds this trigger |
| `BOLLINGER_LENGTH` / `BOLLINGER_STD_MULTIPLIER` | Window size and std-dev multiplier for bandwidth filtering |
| `MIN_BOLLINGER_BANDWIDTH` | Minimum bandwidth ratio required before opening a new position |
| `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 strategy knobs: chase threshold, quote offsets, refresh cadence, etc. |
| `MAKER_*` | Maker-specific knobs (quote offsets, refresh cadence, slippage guard, etc.) |
To trade on GRVT, set `EXCHANGE=grvt` and populate `GRVT_API_KEY`, `GRVT_API_SECRET`, `GRVT_SUB_ACCOUNT_ID`, plus any optional overrides documented in `.env.example`.
> Tip: you can temporarily override the exchange via CLI flags (takes precedence over environment):
> CLI flags override environment variables at runtime:
> ```bash
> bun run index.ts --exchange grvt
> bun run index.ts -e lighter
> bun run index.ts --exchange grvt --strategy maker
> bun run index.ts -e lighter -s offset-maker --silent
> ```
## Common Commands
## 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.
### StandX
* [StandX Maker Points Strategy Guide](docs/standx/maker-points-guide.md)
The strategy requires a StandX API Token and signing private key to place orders.
**How to obtain (using StandX's official API generation feature):**
1. Open the StandX official API creation page: https://standx.com/user/session
2. Connect your wallet and log in
3. Click the **"Generate API Token"** button
4. The page will display the following information:
- **Token** (JWT string starting with `eyJ`) → Fill in `STANDX_TOKEN`
- **Ed25519 Private Key** (Base58 format, like `HdsyJD7oWgT...`) → Fill in `STANDX_REQUEST_PRIVATE_KEY`
- **Creation date** and **Validity days** → Used to configure token expiry reminders
> The Ed25519 Private Key is an auto-generated signing key used only for trade request signatures. Your assets remain in your main wallet and are completely safe.
Please keep these credentials safe and do not share them with anyone.
**Configuration steps:**
1. Set `EXCHANGE=standx`.
2. Provide `STANDX_TOKEN` (JWT token for perps API).
3. Provide `STANDX_REQUEST_PRIVATE_KEY` (Ed25519 signing private key, Base58 format).
4. Set `STANDX_SYMBOL` (defaults to `BTC-USD`) and align `PRICE_TICK` / `QTY_STEP`.
5. Recommended: configure token expiry settings:
- `STANDX_TOKEN_CREATE_DATE` (creation date, format `YYYY-MM-DD`)
- `STANDX_TOKEN_VALIDITY_DAYS` (validity days)
6. Optional: `STANDX_BASE_URL`, `STANDX_WS_URL`, or `STANDX_SESSION_ID` for custom endpoints.
### 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). `LIGHTER_ACCOUNT_INDEX` is your account index, which you can find by opening DevTools (F12) on the official website and observing API requests. `LIGHTER_API_PRIVATE_KEY` is your API 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 (defaults to main account ID).
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`. Note: These are your EVM wallet address and private key. It is recommended to create a brand new wallet and avoid storing unrelated assets in it.
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.
### Nado
1. Set `EXCHANGE=nado`.
2. On the Nado web app (trading interface), open DevTools (F12) -> switch to the `Application` tab -> `Local Storage`, locate `nado.userSettings`, then grab the `privateKey` field from its JSON value and paste it into `.env` as `NADO_SIGNER_PRIVATE_KEY`.
3. Provide `NADO_SUBACCOUNT_OWNER` (or `NADO_EVM_ADDRESS`).
4. Select network via `NADO_ENV=inkMainnet` (mainnet) or `inkTestnet` (testnet).
5. Set `NADO_SYMBOL` using Nado product symbols like `BTC-PERP` (it also accepts `BTCUSDT0` and maps it to `BTC-PERP`).
## Command Cheatsheet
```bash
bun run index.ts # Launch the CLI
bun run start # Same as above
bun run dev # Development entry point
bun x vitest run # Execute the Vitest suite
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 straight from the CLI:
Skip the Ink menu and start a strategy directly:
```bash
bun run index.ts --strategy trend --silent # Trend engine
bun run index.ts --strategy maker --silent # Maker engine
bun run index.ts --strategy offset-maker --silent # Offset maker engine
```
Combine with `--exchange/-e` to explicitly choose the venue (overrides `EXCHANGE`/`TRADE_EXCHANGE` from `.env`):
```bash
bun run index.ts --exchange grvt --strategy maker --silent
bun run index.ts -e lighter -s offset-maker --silent
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 are exposed in `package.json`:
Convenience aliases exposed via `package.json`:
```bash
bun run start:trend:silent
bun run start:maker:silent
@@ -110,42 +195,38 @@ bun run start:offset:silent
```
### Daemonising with pm2
Install `pm2` locally (e.g. `bun add -d pm2`) and launch without a global install:
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 reuse the bundled scripts:
You can also call the bundled scripts:
```bash
bun run pm2:start:trend
bun run pm2:start:maker
bun run pm2:start:offset
```
Adjust `--name`, `--cwd`, or `--restart-delay` to suit your environment and run `pm2 save` if you want the process to auto-start after reboot.
Run `pm2 save` afterwards if you want the process list to survive reboots.
## Testing
Vitest powers the unit tests:
Powered by Vitest:
```bash
bun run test
bun x vitest --watch
```
## Troubleshooting
- You need at least 50100 USDT of capital before deploying a live strategy.
- Set leverage on the exchange beforehand (around 50x is recommended); the bot does not change it for you.
- Keep server/desktop time in sync with real-world time to avoid signature errors.
- Make sure the exchange account is in one-way position mode.
- **Env not loading**: ensure `.env` resides in the repository root and variable names are spelled correctly.
- **Order rejected for precision**: align `PRICE_TICK`, `QTY_STEP`, and `TRADE_SYMBOL` with the exchange filters.
- **Permission or auth errors**: double-check exchange API scopes.
More step-by-step guidance is available in [simple-readme.md](simple-readme.md).
- Keep at least 50-100 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 ideas
- Issues and PRs are welcome for bug reports and feature requests
## Disclaimer
Algorithmic trading carries risk. Validate strategies with paper accounts or small capital first, safeguard your API keys, and only grant the minimum required permissions.
Algorithmic trading carries risk. Validate strategies with paper trading or small capital first, safeguard your API keys, and only grant the minimum required permissions.
+64 -2
View File
@@ -1,16 +1,22 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "ritmex-bot",
"dependencies": {
"@grvt/client": "^1.6.4",
"@nadohq/client": "^0.1.0-alpha.41",
"@noble/ed25519": "^3.0.0",
"axios": "^1.12.2",
"ccxt": "^4.5.5",
"bignumber.js": "^9.3.1",
"ccxt": "^4.5.12",
"dotenv": "^17.2.2",
"ethereum-cryptography": "^2.1.3",
"ink": "^6.3.1",
"react": "^19.1.1",
"trading-signals": "^7.4.3",
"viem": "^2.43.1",
"ws": "^8.18.3",
},
"devDependencies": {
@@ -23,6 +29,8 @@
},
},
"packages": {
"@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="],
"@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-qI/5TaaaCZE4yeSZ83lu0+xi1r88JSxUjnH4OP/iZF7+KKZ75u3ee5isd0LxX+6N8U0npL61YrpbthILHB6BnA=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="],
@@ -81,8 +89,22 @@
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@nadohq/client": ["@nadohq/client@0.1.0-alpha.41", "", { "dependencies": { "@nadohq/engine-client": "^0.1.0-alpha.41", "@nadohq/indexer-client": "^0.1.0-alpha.41", "@nadohq/shared": "^0.1.0-alpha.41", "@nadohq/trigger-client": "^0.1.0-alpha.41", "ts-mixer": "*" }, "peerDependencies": { "viem": "*" } }, "sha512-BbJjG32AsaBJtFA9VPH+3/sfR/pUCXVJ0spgairReb+FaZY2rYsasWGPi07y7OROXL7ZS9/LewntC6iu2NVzQg=="],
"@nadohq/engine-client": ["@nadohq/engine-client@0.1.0-alpha.41", "", { "dependencies": { "@nadohq/shared": "^0.1.0-alpha.41", "axios": "*", "ts-mixer": "*" }, "peerDependencies": { "viem": "*" } }, "sha512-PR2/MaZ2rprd42572UL26B6zBzUFLUVbvAwPFGf6N733RysUxMRixEJmUfSrPVKgZkA4gxqqEhLB0iZA08DBjw=="],
"@nadohq/indexer-client": ["@nadohq/indexer-client@0.1.0-alpha.41", "", { "dependencies": { "@nadohq/engine-client": "^0.1.0-alpha.41", "@nadohq/shared": "^0.1.0-alpha.41", "axios": "*", "ts-mixer": "*" }, "peerDependencies": { "viem": "*" } }, "sha512-5oXM+r3xQmbAHx00MlZmZj64VfcVNNWCtZgJu1Hx+A6F7zZtt0zBmDR6WnY1qWQBM/yZc/xa/Rr3ypXN5VSyQg=="],
"@nadohq/shared": ["@nadohq/shared@0.1.0-alpha.41", "", { "peerDependencies": { "bignumber.js": "^9.3.0", "viem": "*" } }, "sha512-c6rRU/9RjIRV0RSHwmliNOE3i3xi65zKGqW9O0M4Oc8a0SQXB5SpskQeSFOaZ1b9BHGi0TYg1xaLBjtYzbysJQ=="],
"@nadohq/trigger-client": ["@nadohq/trigger-client@0.1.0-alpha.41", "", { "dependencies": { "@nadohq/engine-client": "^0.1.0-alpha.41", "@nadohq/shared": "^0.1.0-alpha.41", "axios": "*", "ts-mixer": "*" }, "peerDependencies": { "viem": "*" } }, "sha512-PxHeqi+aGUHs6S2jHRz2GC9OQD3Mb25sqqwgvDz3EoXWAfv+tYAaSDsLL8FmYEPsbTswjfXm0PoThPDyImyU9A=="],
"@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="],
"@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=="],
@@ -165,6 +187,8 @@
"@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=="],
"abitype": ["abitype@1.2.3", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg=="],
"ansi-escapes": ["ansi-escapes@7.1.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-YdhtCd19sKRKfAAUsrcC1wzm4JuzJoiX4pOJqIoW2qmKj5WzG/dL8uUJ0361zaXtHqK7gEhOwtAtz7t3Yq3X5g=="],
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
@@ -179,13 +203,15 @@
"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=="],
"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=="],
"ccxt": ["ccxt@4.5.5", "", { "dependencies": { "ws": "^8.8.1" } }, "sha512-AyhwTFLkx4sO985ImIOfumEBox7AHD/iqk5tPGICObUSZG6wTXg0aRzU8Hjz974aCMG4msFwLk3A/iXPKAU4wA=="],
"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=="],
@@ -241,6 +267,8 @@
"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=="],
"eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
"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=="],
@@ -275,6 +303,8 @@
"is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="],
"isows": ["isows@1.0.7", "", { "peerDependencies": { "ws": "*" } }, "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg=="],
"js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
"loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
@@ -295,6 +325,8 @@
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"ox": ["ox@0.10.5", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-mXJRiZswmX46abrzNkJpTN9sPJ/Rhevsp5Dfg0z80D55aoLNmEV4oN+/+feSNW593c2CnHavMqSVBanpJ0lUkQ=="],
"patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
@@ -351,12 +383,18 @@
"tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="],
"trading-signals": ["trading-signals@7.4.3", "", {}, "sha512-kOyzd85qhuhU7yGkB5z74MVP6j30xBCfZy5+bgBnvfxY1ZdvaQsdD+C5j+CoIRtCAgSOuxB7jmPpBrqO6h17sQ=="],
"ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
"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=="],
"undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
"viem": ["viem@2.43.1", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", "ox": "0.10.5", "ws": "8.18.3" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-S33pBNlRvOlVv4+L94Z8ydCMDB1j0cuHFUvaC28i6OTxw3uY1P4M3h1YDFK8YC1H9/lIbeBTTvCRhi0FqU/2iw=="],
"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=="],
@@ -375,6 +413,30 @@
"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=="],
"ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="],
"ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
"ox/@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="],
"ox/@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="],
"viem/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="],
"viem/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
"viem/@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="],
"viem/@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="],
"cli-truncate/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="],
"ox/@scure/bip32/@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="],
"ox/@scure/bip39/@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="],
"viem/@scure/bip32/@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="],
"viem/@scure/bip39/@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="],
}
}
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>

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