Author SHA1 Message Date
discountry 2a1cf24510 Enhance Binance depth health monitoring and defense mode logic
- Updated translations for Binance depth status messages to include depth window information.
- Modified `MakerPointsEngine` to incorporate health checks for the Binance depth tracker, including handling of unhealthy states.
- Improved defense mode activation logic to respond to Binance depth health status, ensuring appropriate logging and notifications.
- Added integration tests for defense mode behavior based on Binance depth health, validating transitions into and out of defense mode.
- Refactored `BinanceDepthTracker` to support health checks and improved connection management.
2026-02-07 21:54:22 +08:00
DisneyandGitHub fa82d45bfb fix size (#20) 2026-02-04 13:26:06 +08:00
discountry d0154e5721 fix den 2026-02-03 18:48:06 +08:00
discountry 52b6a8a076 Add invitation links for Nado registration in trading tutorial 2026-02-03 12:04:36 +08:00
discountry db6a9cfc68 Add Nado trading tutorial for ritmex-bot 2026-02-03 12:02:55 +08:00
DisneyandGitHub 03b8e53d30 Merge pull request #19 from discountry/feat/arb
Feat/arb
2026-02-01 11:15:42 +08:00
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
discountry 7635b7d467 feat: 更新 Lighter 适配器的订单过期逻辑,支持即时或取消订单类型的处理 2025-10-03 03:29:54 +08:00
discountry f565c399e3 feat: 更新 LighterSigner 配置,baseUrl 变为可选以支持离线签名和测试 2025-10-03 03:25:30 +08:00
discountry b43eaa0c64 feat: 在文档中添加 CLI 参数说明,支持通过命令行临时指定交易所 2025-10-03 03:16:44 +08:00
discountry 9a419c36ac feat: 更新 Lighter 适配器的订单过期逻辑,使用绝对未来时间戳以符合链的预期 2025-10-03 03:08:41 +08:00
discountry 0e5026aaa1 feat: 优化 Lighter 适配器的账户消息处理逻辑,支持部分更新和市场订单类型的处理 2025-10-03 02:54:50 +08:00
discountry 6273f3b94a feat: 在 Lighter 适配器中添加订单簿序列跟踪,优化 WebSocket 消息处理逻辑 2025-10-03 02:40:09 +08:00
discountry e478fb4330 feat: 添加 Lighter 适配器的调试日志功能,优化错误处理和订单簿数据规范化 2025-10-03 02:27:58 +08:00
discountry 599b4ec3f3 chore: 移除 grvt-pysdk 项目中的所有文件,清理代码库 2025-10-03 02:12:45 +08:00
discountry 42061017f6 chore: 移除 Lighter 和 Poseidon Crypto 项目中的所有文件,清理代码库 2025-10-03 01:54:08 +08:00
discountry b7cd6eed99 feat: 重构 Lighter 策略,更新签名桥接路径,移除不必要的文件,添加新的签名库支持 2025-10-03 01:51:55 +08:00
discountry abb56b917a chore: 更新 .env.example 文件,调整 Maker 策略的损失限制参数以优化风险管理 2025-10-03 01:34:32 +08:00
discountry 9124c06315 chore: 移除 Maker 策略中的价格追逐阈值配置,简化订单计划逻辑 2025-10-03 01:13:06 +08:00
discountry 56bcaddb22 chore: 更新 .env.example 文件,调整风险管理参数以优化交易策略 2025-10-03 01:01:23 +08:00
discountry 4c4abb3b09 feat: 添加 Lighter 签名桥接和公钥派生脚本,支持新的签名库和环境检测功能 2025-10-01 18:32:02 +08:00
discountry c8b0ab1c8e feat: 添加 Lighter 适配器及相关功能,支持 trailing stops 和新的交易逻辑 2025-09-30 22:08:05 +08:00
discountry e83bdfccf9 docs: 更新 lighter-python-main 文档,添加示例代码和 API 说明,重构部分内容以提高可读性 2025-09-30 18:35:14 +08:00
discountry d6533733c9 docs: 更新 README 文件,添加静默启动和后台运行的说明,包括命令行用法和 pm2 配置示例 2025-09-28 01:39:49 +08:00
discountry 5c1283fac2 feat: 添加命令行参数解析和策略启动功能,支持静默模式和帮助信息 2025-09-28 01:26:00 +08:00
discountry 0e4ae67573 docs: 更新 AGENTS.md 文件,重构项目结构和开发命令部分,移除旧版文档信息并添加新的策略和测试指南 2025-09-28 01:03:25 +08:00
discountry 6ab0759d3d feat: 引入事件发射器和会话交易量跟踪器,重构策略引擎以支持更灵活的事件处理和交易量计算 2025-09-28 00:43:49 +08:00
discountry 25ab1ababf chore: 重构策略引擎,将核心逻辑迁移至策略目录,删除旧的引擎文件并更新相关引用 2025-09-28 00:03:48 +08:00
discountry e60b32a7f8 docs: 更新 README 文件,添加策略运行所需资金、杠杆设置及时间同步的常见问题解答 2025-09-27 23:33:00 +08:00
discountry 46c0551133 fix: 更新 AsterRestClient 和 AsterGateway 中的订单处理逻辑,支持字符串类型的订单 ID 2025-09-27 23:23:53 +08:00
discountry 7e2d312fe3 chore: 更新 bun.lock 文件,添加多个依赖以支持以太坊相关功能 2025-09-27 22:59:28 +08:00
discountry e8dd36f540 chore: 删除 GRVT 适配器的 README 文件,清理项目文档 2025-09-27 22:58:56 +08:00
discountry 47d44c7cb5 chore: 移除 README 中关于旧版文档的说明,以简化文档结构 2025-09-27 22:58:38 +08:00
discountry 048dfb1698 fix: 更新 ethereum-cryptography 依赖至 2.1.3 以支持最新功能 2025-09-27 22:52:57 +08:00
discountry fc1a74174c feat: 在 package.json 中添加 ethereum-cryptography 依赖以支持以太坊相关功能 2025-09-27 22:51:28 +08:00
discountry b45539b7c2 feat: 添加英文文档,更新中文文档以提升用户体验和使用指南的清晰度 2025-09-27 22:46:32 +08:00
discountry a998b3d3a1 fix: 更新最小布林带宽度配置,调整为更小的阈值以支持新策略 2025-09-27 22:33:32 +08:00
discountry d392f54f21 feat: 添加布林带宽度计算功能及相关配置项,优化趋势引擎以支持布林带宽度过滤 2025-09-27 22:29:56 +08:00
1670 changed files with 280028 additions and 16154 deletions
+129 -7
View File
@@ -1,20 +1,45 @@
# UI language (zh | en)
LANG=zh
# Exchange selection # 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 credentials
ASTER_API_KEY= ASTER_API_KEY=
ASTER_API_SECRET= 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 # Core trading symbol and sizing
TRADE_SYMBOL=BTCUSDT # Trading pair symbol TRADE_SYMBOL=BTCUSDT # Trading pair symbol
TRADE_AMOUNT=0.001 # Base order quantity (base asset, e.g. BTC) TRADE_AMOUNT=0.001 # Base order quantity (base asset, e.g. BTC)
# Swing Trading
SWING_DIRECTION=short # short | long | both
SWING_STOP_LOSS_PCT=0.05 # 0.05 = 5%
# Risk management (USD amounts unless noted) # Risk management (USD amounts unless noted)
LOSS_LIMIT=0.03 # Max loss per trade in USDT before forced close LOSS_LIMIT=0.04 # Max loss per trade in USDT before forced close
TRAILING_PROFIT=0.2 # Trailing stop activation profit (USDT) TRAILING_PROFIT=0.2 # Trailing stop activation profit (USDT)
TRAILING_CALLBACK_RATE=0.2 # Trailing callback percent (e.g. 0.2 => 0.2%) TRAILING_CALLBACK_RATE=0.2 # Trailing callback percent (e.g. 0.2 => 0.2%)
PROFIT_LOCK_TRIGGER_USD=0.1 # Start moving base stop once unrealized PnL > this (USDT) PROFIT_LOCK_TRIGGER_USD=0.08 # Start moving base stop once unrealized PnL > this (USDT)
PROFIT_LOCK_OFFSET_USD=0.05 # Base stop offset from entry after trigger (USDT) PROFIT_LOCK_OFFSET_USD=0.04 # Base stop offset from entry after trigger (USDT)
BOLLINGER_LENGTH=20 # SMA window (minutes) used for Bollinger bandwidth
BOLLINGER_STD_MULTIPLIER=2 # Standard deviation multiplier for Bollinger bands
MIN_BOLLINGER_BANDWIDTH=0.001 # Require bandwidth >= this ratio before new entries
# Precision (per-symbol exchange filters) # Precision (per-symbol exchange filters)
PRICE_TICK=0.1 # Price tick size (e.g. BTCUSDT uses 0.1) PRICE_TICK=0.1 # Price tick size (e.g. BTCUSDT uses 0.1)
@@ -27,14 +52,29 @@ KLINE_INTERVAL=1m # Kline interval (e.g., 1m/3m/5m)
MAX_CLOSE_SLIPPAGE_PCT=0.05 # Max allowed deviation vs mark when closing (0.05 => 5%) MAX_CLOSE_SLIPPAGE_PCT=0.05 # Max allowed deviation vs mark when closing (0.05 => 5%)
# Maker-only settings # Maker-only settings
MAKER_LOSS_LIMIT=0.03 # Maker loss cap (USDT). Defaults to LOSS_LIMIT if unset MAKER_LOSS_LIMIT=0.05 # Maker loss cap (USDT). Defaults to LOSS_LIMIT if unset
MAKER_PRICE_CHASE=0.3 # Price chase threshold (USDT)
MAKER_BID_OFFSET=0 # Bid quote offset from top bid (USDT) MAKER_BID_OFFSET=0 # Bid quote offset from top bid (USDT)
MAKER_ASK_OFFSET=0 # Ask quote offset from top ask (USDT) MAKER_ASK_OFFSET=0 # Ask quote offset from top ask (USDT)
MAKER_REFRESH_INTERVAL_MS=1500 # Maker refresh cadence (ms) MAKER_REFRESH_INTERVAL_MS=500 # Maker refresh cadence (ms)
MAKER_MAX_CLOSE_SLIPPAGE_PCT=0.05 # Maker close slippage guard (fallbacks to MAX_CLOSE_SLIPPAGE_PCT) MAKER_MAX_CLOSE_SLIPPAGE_PCT=0.05 # Maker close slippage guard (fallbacks to MAX_CLOSE_SLIPPAGE_PCT)
MAKER_PRICE_TICK=0.1 # Maker price tick size (defaults to PRICE_TICK) MAKER_PRICE_TICK=0.1 # Maker price tick size (defaults to PRICE_TICK)
# Grid strategy defaults
GRID_LOWER_PRICE=25000 # Grid lower bound price (quote currency)
GRID_UPPER_PRICE=35000 # Grid upper bound price
GRID_LEVELS=10 # Number of grid levels between bounds (>=2)
GRID_ORDER_SIZE=0.001 # Quantity per grid order (base asset units)
GRID_MAX_POSITION_SIZE=0.01 # Max inventory the grid may hold (base units)
GRID_REFRESH_INTERVAL_MS=1000 # Grid evaluation cadence (ms)
GRID_MAX_LOG_ENTRIES=200 # Grid trade log length (defaults to MAX_LOG_ENTRIES when unset)
GRID_DIRECTION=both # Order direction: both | long | short
GRID_STOP_LOSS_PCT=0.01 # Stop loss trigger percentage beyond bounds (0.01 => 1%)
GRID_RESTART_TRIGGER_PCT=0.01 # Restart buffer percentage inside bounds
GRID_AUTO_RESTART_ENABLED=true # Automatically resume grid when price re-enters range
GRID_MAX_CLOSE_SLIPPAGE_PCT=0.05 # Close-order slippage guard relative to mark price
# GRID_PRICE_TICK=0.1 # Optional override for grid price tick (falls back to PRICE_TICK)
# GRID_QTY_STEP=0.001 # Optional override for grid quantity step (falls back to QTY_STEP)
# GRVT authentication (set when EXCHANGE=grvt) # GRVT authentication (set when EXCHANGE=grvt)
GRVT_API_KEY= GRVT_API_KEY=
GRVT_API_SECRET= GRVT_API_SECRET=
@@ -47,3 +87,85 @@ GRVT_ENV=prod
# GRVT_COOKIE="gravity=..." # Pre-provisioned session cookie (auto-refresh uses API key when absent) # GRVT_COOKIE="gravity=..." # Pre-provisioned session cookie (auto-refresh uses API key when absent)
# GRVT_ACCOUNT_ID= # Populated automatically after login # GRVT_ACCOUNT_ID= # Populated automatically after login
# GRVT_SIGNER_PATH=./grvt-signer.cjs # Custom signature provider module # GRVT_SIGNER_PATH=./grvt-signer.cjs # Custom signature provider module
# Lighter authentication (set when EXCHANGE=lighter)
LIGHTER_ACCOUNT_INDEX=
LIGHTER_API_PRIVATE_KEY= # 40-byte hex private key (e.g., 0x...)
LIGHTER_API_KEY_INDEX=0 # API key slot (default 0)
LIGHTER_SYMBOL=BTCUSDT # Trading pair (defaults to TRADE_SYMBOL when omitted)
LIGHTER_ENV=testnet # mainnet | testnet | staging | dev
# LIGHTER_BASE_URL=https://testnet.zklighter.elliot.ai
# LIGHTER_CHAIN_ID=300 # Override inferred chain id when needed
# LIGHTER_MARKET_ID=1 # Prefer explicit market id when symbols differ
# LIGHTER_PRICE_DECIMALS=3 # Manual override for price decimals (optional)
# LIGHTER_SIZE_DECIMALS=3 # Manual override for size decimals (optional)
# Backpack exchange configuration
# Fill these with your Backpack API credentials and preferences
BACKPACK_API_KEY=
BACKPACK_API_SECRET=
BACKPACK_PASSWORD=
BACKPACK_SUBACCOUNT=
# Use sandbox environment: "true" to enable, otherwise leave as false
BACKPACK_SANDBOX=false
# Default trading symbol (falls back to TRADE_SYMBOL or BTCUSDC)
BACKPACK_SYMBOL=BTC_USD_PERP
# Enable verbose adapter logging: set to "1" or "true"
BACKPACK_DEBUG=false
# Paradex exchange configuration
# Provide the EVM private key & wallet address for onboarded accounts.
# When EXCHANGE=paradex these values are used automatically.
PARADEX_SYMBOL=BTC-USD-PERP
PARADEX_PRIVATE_KEY=
PARADEX_WALLET_ADDRESS=
# Enable testnet endpoints by setting to "true"; defaults to false (mainnet).
# PARADEX_SANDBOX=false
# Force disabling ccxt.pro websocket usage by setting to "false" (pro is preferred when installed).
# PARADEX_USE_PRO=true
# Optional reconnect delay override (milliseconds, e.g., 2000). Leave blank for default.
# PARADEX_RECONNECT_DELAY_MS=
# Enable verbose adapter logging: set to "1" or "true"
# PARADEX_DEBUG=false
# 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 # Finder (MacOS) folder config
.DS_Store .DS_Store
.tmp
.tmp/*
+22 -6
View File
@@ -1,19 +1,35 @@
# Repository Guidelines # Repository Guidelines
## Project Structure & Module Organization ## Project Structure & Module Organization
The Bun-based entry point (`index.ts`) lives at the root for quick smoke checks, alongside `tsconfig.json` and the Bun lockfile. The production-ready trading agents reside in `legacy/`, which is a pnpm-managed workspace. Inside `legacy/`, strategy scripts (`trendV2.ts`, `maker.ts`, `bot.ts`) and the CLI live at the top level, shared helpers are under `legacy/utils/`, and exchange adapters plus tests sit in `legacy/exchanges/`. Reference environment samples are provided in `legacy/env.example`, and longer-form docs are kept in `legacy/docs/`. The Bun entry point (`index.ts`) lives at the repo root for quick CLI smoke checks. All production code is under `src/`:
- `src/strategy/` gathers every live trading engine (`maker`, `offset-maker`, `trend`). Shared helpers for strategy wiring sit in `src/strategy/common/`.
- `src/core/` keeps order coordination plus shared libs used by multiple strategies.
- `src/exchanges/` exposes the adapters and REST/websocket clients.
- `src/ui/` implements the Ink dashboards for each strategy (they remain independent dashboards).
- `src/logging/` contains the trade log helper.
- `src/utils/` and `src/config.ts` hold cross-cutting utilities and runtime config.
- `docs/` stores reference material, while `tests/` contains Vitest suites.
## Build, Test, and Development Commands ## Build, Test, and Development Commands
Run `bun install` at the root to satisfy the lightweight Bun demo. Change into `legacy/` for real work: `pnpm install` bootstraps dependencies, `pnpm start` launches the default trend strategy, `pnpm maker` starts the market-making loop, `pnpm cli:start` triggers the dual-exchange hedging flow, and `pnpm test` executes the full Vitest suite. Use `pnpm aster:test` when you only need the Aster adapter checks. - `bun install` install dependencies.
- `bun run index.ts` launch the CLI menu.
- `bun x vitest run` execute the full test suite; `bun x vitest --watch` for incremental runs.
Strategy-specific scripts still execute through the CLI; there is no separate `legacy/` workspace.
## Coding Style & Naming Conventions ## Coding Style & Naming Conventions
All code is modern TypeScript using native ES modules. Follow the existing two-space indentation, keep imports sorted from external to local, and prefer `camelCase` for variables/functions with `PascalCase` for classes and enums. Strategy files stay in the project root with descriptive verbs (for example `maker.ts`), while shared utilities belong under `legacy/utils/`. Comments should stay concise and explain non-obvious trading logic. Use modern TypeScript with ES modules, two-space indentation, and sorted imports (external → internal). Favor `camelCase` for variables/functions and `PascalCase` for classes/enums. Place new strategies under `src/strategy/`, shared utilities under `src/utils/` or `src/strategy/common/` when they only apply to strategies. Keep comments focused on non-obvious trading logic.
## Testing Guidelines ## Testing Guidelines
Vitest powers unit and integration checks. Place new suites beside their subjects using the `<feature>.test.ts` pattern (e.g., `legacy/exchanges/aster.test.ts`). Ensure strategies ship with coverage for order flow, risk guardrails, and websocket edge cases before opening a pull request. Run `pnpm test --watch` during development to keep feedback tight. Vitest powers unit/integration tests. Co-locate new tests next to their subject using `<feature>.test.ts`. Strategies should have coverage for order lifecycle, risk guards, and websocket edge cases. Run `bun x vitest --watch` during development for fast feedback.
## Commit & Pull Request Guidelines ## Commit & Pull Request Guidelines
The repository history is empty, so adopt a lightweight Conventional Commits style (for example `feat: add hedging status panel`) to keep future changelogs clear. Commits should stay scoped to one strategy or module. Pull requests need a concise summary, reproduction or validation notes (commands run, environments touched), and screenshots or log excerpts when behavior changes. Link tracking issues or tasks to help downstream coordination. Follow lightweight Conventional Commits (e.g. `feat: add hedging status panel`). Scope each commit to a single module or strategy. PRs should include:
- Summary of changes.
- Validation notes (commands run, environments touched).
- Relevant logs or screenshots for behavior changes.
Link issues/tasks when available.
## Environment & Secrets ## Environment & Secrets
Copy `legacy/env.example` to `.env` and populate API keys for Bitget and AsterDex before running live strategies. Never commit secrets; rely on local dotenv files or your deployment platform's secret manager. Rotate keys immediately if logs or configs leave the sandbox. Duplicate `.env.example` to `.env` and populate API keys (Aster/GRVT) before running strategies. Do not commit secrets—use local `.env` or deployment secret managers. Rotate keys if they leak into logs or backups.
+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**
+195 -178
View File
@@ -1,219 +1,236 @@
# ritmex-bot # ritmex-bot
一个基于 Bun 的 Aster 永续合约终端机器人,内置趋势跟随(SMA30)与做市策略,使用 websocket 实时行情,命令行界面由 Ink 驱动,可在断线后自动恢复运行。 > For English users, please see [README_en.md](README_en.md).
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)
## 文档索引
- [简明上手指南(零基础)](simple-readme.md)
- [基础网格策略使用教程](grid-trading.md)
## 核心特性
- **实时行情与风控**Websocket + REST 自动同步账户、挂单与仓位,断线后自动恢复。
- **趋势策略**:SMA30 穿越入场,内置止损、移动止盈、布林带带宽过滤与步进锁盈。
- **Guardian 策略**:不主动开单,实时监听账户仓位并强制补挂/移动止损与动态止盈,防止裸奔。
- **做市策略**:支持双边追价、风险阈值控制与订单自愈。
- **模块化架构**:策略引擎、交易所适配器与 Ink CLI 相互解耦,新增交易所或策略更容易。
## 支持的交易所
| 交易所 | 合约类型 | 必填环境变量 | 备注 |
| --- | --- | --- | --- |
| 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`
## 系统要求
- Bun ≥ 1.2(需同时包含 `bun``bunx` 命令)
- macOS、Linux 或 Windows (推荐 WSL)
- Node.js 仅在部分工具链场景需要,可选
## 快速上手 ## 快速上手
### 一键脚本(macOS / Linux / WSL
使用优惠码获取 30% 手续费折扣:[注册 Aster 获取手续费优惠](https://www.asterdex.com/zh-CN/referral/4665f3)
如果你完全不懂代码,可以查看 **[小白教程](simple-readme.md) 了解使用方法。**
遇到Bug,反馈问题,请到 [Telegram群组](https://t.me/+4fdo0quY87o4Mjhh)
### 一键脚本
一键安装并启动(macOS / Linux):
```bash ```bash
curl -fsSL https://github.com/discountry/ritmex-bot/raw/refs/heads/main/setup.sh | bash curl -fsSL https://github.com/discountry/ritmex-bot/raw/refs/heads/main/setup.sh | bash
``` ```
脚本会自动安装 Bun、安装依赖、引导输入 API Key/Secret,生成 `.env` 并启动程序 脚本会安装 Bun、项目依赖,收集 Aster API 凭证,生成 `.env` 并启动 CLI。运行前请准备好对应交易所的 API Key/Secret
Windows 使用 WSL(推荐): ### 手动安装
1. 先安装并启用 WSL,参考微软官方文档:[在 Windows 上安装 WSL](https://learn.microsoft.com/zh-cn/windows/wsl/install) 1. **获取代码**
2. 在命令行或者 PowerShell 输入 `wsl` 并按下回车打开 WSL。
3. 在 WSL 中运行一键脚本:
```bash ```bash
curl -fsSL https://github.com/discountry/ritmex-bot/raw/refs/heads/main/setup.sh | bash git clone https://github.com/discountry/ritmex-bot.git
cd ritmex-bot
``` ```
按提示输入 ASTER_API_KEY / ASTER_API_SECRET,脚本将自动安装 Bun、依赖并启动程序 不便使用 Git 时,可在仓库页面下载 ZIP 后手动解压
2. **安装 Bun**
## 手动安装 - macOS / Linux`curl -fsSL https://bun.sh/install | bash`
- Windows PowerShell`powershell -c "irm bun.sh/install.ps1 | iex"`
1. **下载代码** 安装完成后重新打开终端,确认 `bun -v` 正常输出版本号。
- 如果会使用 Git`git clone https://github.com/discountry/ritmex-bot.git` 3. **安装依赖**
- 如果不会使用 Git:点击仓库页面的 `Code` → `Download ZIP`,将压缩包解压到如 `桌面/ritmex-bot` 的目录。
2. **打开命令行并进入项目目录**
- macOS:通过 Spotlight (`⌘ + 空格`) 搜索 “Terminal” 并打开。
- Windows:在开始菜单搜索 “PowerShell” 或 “Windows Terminal” 并打开。
- 使用 `cd` 切换到项目目录,例如:
```bash
# macOS / Linux
cd ~/Desktop/ritmex-bot
# Windows
cd C:\Users\用户名\Desktop\ritmex-bot
```
3. **安装 [Bun](https://bun.com) ≥ 1.2**
- macOS / Linux
```bash
curl -fsSL https://bun.sh/install | bash
```
- WindowsPowerShell):
```powershell
powershell -c "irm bun.sh/install.ps1 | iex"
```
安装完成后关闭并重新打开终端,运行 `bun -v` 确认命令可用。
如果上述命令无法完成安装,请尝试 [bun官网](https://bun.com/get) 提供的各种安装方式。
Windows 用户如果无法正常安装,可以尝试先[安装 nodejs](https://nodejs.org/en/download)
然后使用 `npm` 安装 `bun`
```bash
npm install -g bun
```
4. **安装依赖**
```bash ```bash
bun install bun install
``` ```
5. **配置环境变量** 4. **复制环境变量模板并填写**
复制 `.env.example` 为 `.env` 并填入你的 Aster API Key/Secret
```bash ```bash
cp .env.example .env cp .env.example .env
``` ```
然后根据需要修改 `.env` 中的配置项: 按下文指南修改 `.env`,至少需要正确配置一个交易所的凭证。
- API KEY 获取地址 [https://www.asterdex.com/zh-CN/api-management](https://www.asterdex.com/zh-CN/api-management) 5. **运行 CLI**
- `ASTER_API_KEY` / `ASTER_API_SECRET`:Aster 交易所提供的 API 凭证。
- `TRADE_SYMBOL`:策略运行的交易对(默认 `BTCUSDT`),需与 API 权限范围一致。
- `TRADE_AMOUNT`:单次下单数量(合约张数折算后单位为标的货币,例如 BTC)。
- `LOSS_LIMIT`:单笔允许的最大亏损(USDT),触发即强制平仓。
- `TRAILING_PROFIT` / `TRAILING_CALLBACK_RATE`:趋势策略的动态止盈触发值(单位 USDT)与回撤百分比(百分数,如 0.2 表示 0.2%)。
- `PROFIT_LOCK_TRIGGER_USD` / `PROFIT_LOCK_OFFSET_USD`:达到一定浮盈后,将基础止损上调(做多)或下调(做空)到开仓价的偏移量(单位 USDT)。
- `PRICE_TICK` / `QTY_STEP`:交易对的最小价格变动单位与最小下单数量步长(例如 BTCUSDT 分别为 0.1 与 0.001)。
- `MAKER_*` 参数:做市策略追价阈值、报价偏移、刷新频率等,可按流动性需求调节。
6. **运行机器人**
```bash ```bash
bun run index.ts bun run index.ts
``` ```
在终端中按 ↑/↓ 选择 “趋势策略” 或 “做市策略”,回车启动。按 `Esc` 返回选择菜单,`Ctrl+C` 退出。 方向键选择策略并回车启动`Esc` 返回菜单,`Ctrl+C` 退出。
7. **风险提示**
建议先在小额或仿真环境中测试策略;真实资金操作前请确认 API 仅开启必要权限,并逐步验证配置。
A Bun-powered trading workstation for Aster perpetual contracts. The project ships two production strategies—an SMA30 trend follower and a dual-sided maker—that share a modular gateway, UI, and runtime state derived entirely from the exchange. Everything runs in the terminal via Ink, with live websocket refresh and automatic recovery from restarts or network failures. ## 通用环境变量
`.env.example` 提供了所有默认键值,下表概括最常用参数:
## Features | 变量 | 说明 |
- **Live data over websockets** with REST fallbacks and automatic re-sync after reconnects. | --- | --- |
- **Trend strategy**: SMA30 crossover entries, automated stop-loss / trailing-stop, and P&L tracking. | `EXCHANGE` | 选择交易所(`aster`/`standx`/`grvt`/`lighter`/`backpack`/`paradex`/`nado` |
- **Maker strategy**: adaptive bid/ask chasing, risk stops, and target order introspection. | `TRADE_SYMBOL` | 交易对(默认 `BTCUSDT` |
- **Extensibility**: exchange gateway, engines, and UI components are modular for new venues or strategies. | `TRADE_AMOUNT` | 单笔下单数量(标的资产计) |
| `LOSS_LIMIT` | 单笔最大亏损触发的强平额度(USDT) |
| `TRAILING_PROFIT` / `TRAILING_CALLBACK_RATE` | 动态止盈触发值(USDT)与回撤百分比 |
| `PROFIT_LOCK_TRIGGER_USD` / `PROFIT_LOCK_OFFSET_USD` | 浮盈超过阈值后上调止损的触发金额与偏移 |
| `BOLLINGER_*` | 趋势策略布林带过滤参数 |
| `PRICE_TICK` / `QTY_STEP` | 交易所要求的最小报价与数量精度 |
| `POLL_INTERVAL_MS` | 趋势策略循环间隔(毫秒) |
| `MAX_CLOSE_SLIPPAGE_PCT` | 平仓时相对标记价允许的最大偏差 |
| `MAKER_*` | 做市策略专属参数(追价阈值、报价偏移、刷新频率等) |
## Requirements > 可通过命令行临时覆盖交易所与策略(优先级高于 `.env`):
- [Bun](https://bun.com) ≥ 1.2 > ```bash
- Node.js (optional, only if you prefer `npm` tooling) > bun run index.ts --exchange grvt --strategy maker
- Valid Aster API credentials with futures access > bun run index.ts -e lighter -s offset-maker --silent
> ```
## Installation ## 交易所配置指南
### Aster
1. 将 `EXCHANGE` 保持为 `aster`(默认值)。
2. 填写 `ASTER_API_KEY` 与 `ASTER_API_SECRET`。
3. 根据交易对调整 `TRADE_SYMBOL`、`PRICE_TICK`、`QTY_STEP` 等精度参数。
4. 一键脚本会自动写入这些变量,手动部署时需自行维护。
### 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 ```bash
bun install bun run index.ts # 启动 CLI(默认入口)
bun run start # 等价于运行 index.ts
bun run dev # 调试模式
bun x vitest run # 执行全部测试
``` ```
## Configuration ## 静默启动与后台运行
Create an `.env` (or export environment variables) with at least: ### 直接静默启动
无需进入 Ink 菜单,可用命令行直接拉起指定策略:
```bash ```bash
ASTER_API_KEY=your_key bun run index.ts --strategy trend --silent
ASTER_API_SECRET=your_secret bun run index.ts --strategy maker --silent
TRADE_SYMBOL=BTCUSDT # optional, defaults to BTCUSDT bun run index.ts --strategy offset-maker --silent
TRADE_AMOUNT=0.001 # position size used by both strategies
LOSS_LIMIT=0.03 # per-trade USD loss cap
TRAILING_PROFIT=0.2 # trailing activation profit in USDT
TRAILING_CALLBACK_RATE=0.2 # trailing callback in percent, e.g. 0.2 => 0.2%
PROFIT_LOCK_TRIGGER_USD=0.1 # profit threshold to start moving base stop (USDT)
PROFIT_LOCK_OFFSET_USD=0.05 # base stop offset from entry after trigger (USDT)
PRICE_TICK=0.1 # price tick size; set per symbol
QTY_STEP=0.001 # quantity step size; set per symbol
``` ```
Additional maker-specific knobs (`MAKER_*`) live in `src/config.ts` and may be overridden via env vars: 如需同时指定交易所,可叠加 `--exchange/-e` 参数。
### 项目内置脚本
`package.json` 提供了便捷脚本:
```bash ```bash
# Maker-specific (units in USDT unless noted) bun run start:trend:silent
MAKER_LOSS_LIMIT=0.03 # override maker risk stop; defaults to LOSS_LIMIT bun run start:maker:silent
MAKER_PRICE_CHASE=0.3 # chase threshold bun run start:offset:silent
MAKER_BID_OFFSET=0 # bid offset from top bid (USDT)
MAKER_ASK_OFFSET=0 # ask offset from top ask (USDT)
MAKER_REFRESH_INTERVAL_MS=1500 # maker refresh cadence (ms)
MAKER_MAX_CLOSE_SLIPPAGE_PCT=0.05 # allowed deviation vs mark when closing
MAKER_PRICE_TICK=0.1 # maker tick size; defaults to PRICE_TICK
``` ```
To switch the entire CLI to GRVT instead of Aster, set `EXCHANGE=grvt` and provide the programmable API credentials: ### 使用 pm2 守护并自动重启
安装 `pm2`(示例:`bun add -d pm2`)后,可在项目内直接运行:
```bash ```bash
EXCHANGE=grvt bunx pm2 start bun --name ritmex-trend --cwd . --restart-delay 5000 -- run index.ts --strategy trend --silent
GRVT_API_KEY=your_api_key
GRVT_API_SECRET=0xabc123... # private key used for EIP-712 order signatures
GRVT_SUB_ACCOUNT_ID=your_sub_account # sub account that actually trades
GRVT_INSTRUMENT=BTC_USDT_Perp # instrument as defined by GRVT
GRVT_SYMBOL=BTCUSDT # optional display symbol; defaults to instrument sans underscores
GRVT_ENV=prod # optional environment switch (prod/testnet/staging/dev)
# Optional overrides
# GRVT_SIGNER_PATH=./grvt-signer.cjs # replace the built-in signer if you run an external service
# GRVT_COOKIE="gravity=..." # pre-provisioned session cookie (auto-fetched via API key when absent)
# GRVT_ACCOUNT_ID=... # populated automatically after login
``` ```
或调用预置脚本:
The adapter logs in with `GRVT_API_KEY`, refreshes cookies automatically, and signs orders locally using `GRVT_API_SECRET` following GRVT's EIP712 schema. If you prefer to delegate signing to another process, set `GRVT_SIGNER_PATH`; the module should export a function (default export also works) that receives a context object (unsigned order, nonce/expiration, instrument metadata, chain id, etc.) and returns `{ signer, r, s, v, expiration, nonce }`. If you leave `GRVT_SIGNER_PATH` unset, the built-in signer uses `GRVT_API_SECRET` directly.
### 切换到 GRVT 交易所
将环境变量 `EXCHANGE` 设为 `grvt` 后,所有策略会改用 GRVT 适配器:
```bash ```bash
EXCHANGE=grvt bun run pm2:start:trend
GRVT_API_KEY=你的APIKey bun run pm2:start:maker
GRVT_API_SECRET=0xabc123... # 用于订单签名的私钥 bun run pm2:start:offset
GRVT_SUB_ACCOUNT_ID=your_sub_account_id # 具体交易子账号
GRVT_INSTRUMENT=BTC_USDT_Perp # 交易品种,需与策略一致
GRVT_SYMBOL=BTCUSDT # 可选,内部用于展示,默认由 instrument 推导
GRVT_ENV=prod # 可选:prod / testnet / staging / dev,默认 prod
# 可选覆盖项
# GRVT_SIGNER_PATH=./grvt-signer.cjs # 如果你希望由外部服务签名
# GRVT_COOKIE="gravity=..." # 预先获取到的 Cookie(若未提供,将使用 API Key 自动登录)
# GRVT_ACCOUNT_ID=... # 登录后自动填充
``` ```
完成配置后可执行 `pm2 save` 持久化进程列表。
适配器会基于 `GRVT_API_SECRET` 自动完成 EIP‑712 签名并提交订单。如果你需要自定义签名流程,可通过 `GRVT_SIGNER_PATH` 指定模块(CommonJS 或 ESM 均可)。模块需导出一个函数,接收签名上下文(未签名订单、nonce/expiration、合约元信息、链 ID 等)并返回包含 `signer/r/s/v/expiration/nonce` 字段的对象,例如: ## 测试
项目使用 Vitest
```js
// grvt-signer.cjs
module.exports = async function signOrder(context) {
// 调用你自己的签名服务,或者使用本地私钥完成签名
const signature = await mySigner(context);
return {
signer: signature.signer,
r: signature.r,
s: signature.s,
v: signature.v,
expiration: signature.expiration,
nonce: signature.nonce,
};
};
```
如未自定义 `GRVT_SIGNER_PATH`,适配器会直接使用 `GRVT_API_SECRET` 完成签名。
## Running the CLI
```bash ```bash
bun run index.ts # or: bun run dev / bun run start bun run test
bun x vitest --watch
``` ```
Pick a strategy with the arrow keys. Press `Esc` to return to the menu. The dashboard shows live order books, holdings, pending orders, and recent events. 状态完全以交易所数据为准,重新启动时会自动同步账户和挂单。
## Testing ## 常见问题
```bash - 至少准备 50–100 USDT 资金以覆盖策略运行需求。
bun run test # bun x vitest run - 杠杆需在交易所提前设置(建议 ~50 倍),程序不会自动调整。
bun run test:watch # stay in watch mode - 请确保服务器/电脑时间同步真实世界时间,避免签名过期。
``` - 账户需保持单向持仓模式。
Current tests cover the order coordinator utilities and strategy helpers; add unit tests beside new modules as you extend the system. - `.env` 未读取:确认文件位于项目根目录且变量名无误。
- API 拒绝访问:检查交易所后台权限,确保开启合约读写。
- 精度错误:同步交易对的最小价格与数量步长。
更多排查细节可参见 [简明上手指南](simple-readme.md)。
## Project Layout ## 社区与支持
- `src/config.ts` shared runtime configuration - Telegram 交流群:[https://t.me/+4fdo0quY87o4Mjhh](https://t.me/+4fdo0quY87o4Mjhh)
- `src/core/` trend & maker engines plus order coordination - 欢迎通过 Issue 或 PR 提交反馈、特性建议
- `src/exchanges/` Aster REST/WS gateway and adapters
- `src/ui/` Ink components and strategy dashboards
- `src/utils/` math helpers and strategy utilities
- `tests/` Vitest suites for critical modules
## Troubleshooting ## 风险提示
- **Websocket reconnect loops**: ensure outbound access to `wss://fstream.asterdex.com/ws` and REST endpoints. 量化交易具备风险。请先在仿真或小额账户中验证策略表现,妥善保管 API 密钥,仅开启必要权限。
- **429 or 5xx responses**: the gateway backs off automatically, but check your rate limits and credentials.
- **CLI input errors**: run in a real TTY; non-interactive shells disable keyboard shortcuts but the UI still renders.
## Contributing
Issues and PRs are welcome. When adding strategies or exchanges, follow the modular patterns in `src/core` and add tests under `tests/`.
+232
View File
@@ -0,0 +1,232 @@
# ritmex-bot
**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
- [Beginner-friendly Quick Start](simple-readme.md)
- [Grid Trading Strategy Guide](grid-trading.md)
## Highlights
- **Live data & risk sync** via websockets with REST fallbacks and full reconciliation on restart.
- **Trend strategy** featuring SMA30 entries, fixed stop loss, trailing stop, Bollinger bandwidth gate, and profit-lock stepping.
- **Guardian strategy** that never opens trades but mirrors your live exposure, ensuring every position has a synced stop loss and trailing stop.
- **Market-making loop** with dual-sided quote chasing, loss caps, and automatic order healing.
- **Modular architecture** decoupling engines, exchange adapters, and the Ink CLI for easy venue or strategy extensions.
## Supported Exchanges
| Exchange | Contract Type | Required Environment Variables | Notes |
| --- | --- | --- | --- |
| Aster | USDT perpetuals | `ASTER_API_KEY`, `ASTER_API_SECRET` | Default venue; works with the bootstrap script |
| 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 (both `bun` and `bunx` on PATH)
- macOS, Linux, or Windows via WSL (native Windows works but WSL is recommended)
- Node.js is optional unless your tooling requires it
## Quick Start
### One-line bootstrap (macOS / Linux / WSL)
```bash
curl -fsSL https://github.com/discountry/ritmex-bot/raw/refs/heads/main/setup.sh | bash
```
The script installs Bun, project dependencies, collects Aster API credentials, generates `.env`, and launches the CLI. Prepare the relevant exchange API keys before running it.
### Manual installation
1. **Clone the repository**
```bash
git clone https://github.com/discountry/ritmex-bot.git
cd ritmex-bot
```
Alternatively, download the ZIP from GitHub and extract it manually.
2. **Install Bun**
- macOS / Linux: `curl -fsSL https://bun.sh/install | bash`
- Windows PowerShell: `powershell -c "irm bun.sh/install.ps1 | iex"`
Re-open the terminal and verify `bun -v` prints a version.
3. **Install dependencies**
```bash
bun install
```
4. **Create your environment file**
```bash
cp .env.example .env
```
Edit `.env` with the exchange credentials and overrides you plan to use.
5. **Launch the CLI**
```bash
bun run index.ts
```
Use the arrow keys to pick a strategy, `Enter` to start, `Esc` to go back, and `Ctrl+C` to exit.
## Shared Configuration
`.env.example` captures all defaults; the most common settings are summarised below.
| Variable | Purpose |
| --- | --- |
| `EXCHANGE` | Choose the venue (`aster` / `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 in USDT before forced close |
| `TRAILING_PROFIT` / `TRAILING_CALLBACK_RATE` | Trailing stop trigger (USDT) and pullback percentage |
| `PROFIT_LOCK_TRIGGER_USD` / `PROFIT_LOCK_OFFSET_USD` | Profit lock trigger and offset thresholds |
| `BOLLINGER_*` | Bollinger bandwidth filters for the trend engine |
| `PRICE_TICK` / `QTY_STEP` | Exchange precision filters for price and quantity |
| `POLL_INTERVAL_MS` | Trend engine polling cadence in milliseconds |
| `MAX_CLOSE_SLIPPAGE_PCT` | Allowed deviation vs mark price when closing |
| `MAKER_*` | Maker-specific knobs (quote offsets, refresh cadence, slippage guard, etc.) |
> CLI flags override environment variables at runtime:
> ```bash
> bun run index.ts --exchange grvt --strategy maker
> bun run index.ts -e lighter -s offset-maker --silent
> ```
## Exchange Setup Guides
### Aster
1. Keep `EXCHANGE=aster` (default value).
2. Supply `ASTER_API_KEY` and `ASTER_API_SECRET`.
3. Adjust `TRADE_SYMBOL`, `PRICE_TICK`, and `QTY_STEP` to match the requested market.
4. The bootstrap script auto-populates these variables; manual installs must maintain them.
### 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 (default entrypoint)
bun run start # Alias for bun run index.ts
bun run dev # Development entrypoint
bun x vitest run # Execute the full Vitest suite
```
## Silent & Background Execution
### Direct silent launch
Skip the Ink menu and start a strategy directly:
```bash
bun run index.ts --strategy trend --silent
bun run index.ts --strategy maker --silent
bun run index.ts --strategy offset-maker --silent
```
Combine with `--exchange/-e` to pin the venue for that run.
### Package scripts
Convenience aliases exposed via `package.json`:
```bash
bun run start:trend:silent
bun run start:maker:silent
bun run start:offset:silent
```
### Daemonising with pm2
Install `pm2` locally (e.g. `bun add -d pm2`) and launch the process:
```bash
bunx pm2 start bun --name ritmex-trend --cwd . --restart-delay 5000 -- run index.ts --strategy trend --silent
```
You can also call the bundled scripts:
```bash
bun run pm2:start:trend
bun run pm2:start:maker
bun run pm2:start:offset
```
Run `pm2 save` afterwards if you want the process list to survive reboots.
## Testing
Powered by Vitest:
```bash
bun run test
bun x vitest --watch
```
## Troubleshooting
- Keep at least 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 requests
## Disclaimer
Algorithmic trading carries risk. Validate strategies with paper trading or small capital first, safeguard your API keys, and only grant the minimum required permissions.
+95 -22
View File
@@ -1,15 +1,22 @@
{ {
"lockfileVersion": 1, "lockfileVersion": 1,
"configVersion": 0,
"workspaces": { "workspaces": {
"": { "": {
"name": "ritmex-bot", "name": "ritmex-bot",
"dependencies": { "dependencies": {
"@grvt/client": "^1.6.4", "@grvt/client": "^1.6.4",
"@nadohq/client": "^0.1.0-alpha.41",
"@noble/ed25519": "^3.0.0",
"axios": "^1.12.2", "axios": "^1.12.2",
"ccxt": "^4.5.5", "bignumber.js": "^9.3.1",
"ccxt": "^4.5.12",
"dotenv": "^17.2.2", "dotenv": "^17.2.2",
"ethereum-cryptography": "^2.1.3",
"ink": "^6.3.1", "ink": "^6.3.1",
"react": "^19.1.1", "react": "^19.1.1",
"trading-signals": "^7.4.3",
"viem": "^2.43.1",
"ws": "^8.18.3", "ws": "^8.18.3",
}, },
"devDependencies": { "devDependencies": {
@@ -22,7 +29,9 @@
}, },
}, },
"packages": { "packages": {
"@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=="], "@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="],
"@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.4", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-HTgrrTgZ9Jgeo6Z3oqbQ7lifOVvRR14vaDuBGPPUxk9Thm+vObaO4QfYYYWw4Zo5CWQDBEfsinFA6Gre+AqwNQ=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="],
@@ -76,10 +85,28 @@
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.10", "", { "os": "win32", "cpu": "x64" }, "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw=="], "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.10", "", { "os": "win32", "cpu": "x64" }, "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw=="],
"@grvt/client": ["@grvt/client@1.6.4", "", { "dependencies": { "axios": "^1.12.2" } }, "sha512-yZfEvsC/BtkcdMjoB7Eh6gshBraz/9810qCllvNXDx6OmdxR+NfxPO+ucNlPbyjEt1HfSVzHBWB1yGnVl1usNA=="], "@grvt/client": ["@grvt/client@1.6.25", "", { "dependencies": { "axios": "^1.13.2" } }, "sha512-LG3oZSJDq1Qz6mLiHDmg+v/Hp+Nd+yOp8GIvkBVyIdOcPVzuzC8UQD7IxvS+g5EfQQKLiKzXSjKLc39a11Uj1A=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@nadohq/client": ["@nadohq/client@0.1.0-alpha.45", "", { "dependencies": { "@nadohq/engine-client": "^0.1.0-alpha.45", "@nadohq/indexer-client": "^0.1.0-alpha.45", "@nadohq/shared": "^0.1.0-alpha.45", "@nadohq/trigger-client": "^0.1.0-alpha.45", "ts-mixer": "*" }, "peerDependencies": { "viem": "*" } }, "sha512-63S7eT5xSk3hxEXRLGDI94mPeu/zd1Nu4oUOZrY8wzhaT7Gl7PVP2snb8JH/PduVQzgs7+qblr4aTBI/j6QvLQ=="],
"@nadohq/engine-client": ["@nadohq/engine-client@0.1.0-alpha.45", "", { "dependencies": { "@nadohq/shared": "^0.1.0-alpha.45", "axios": "*", "ts-mixer": "*" }, "peerDependencies": { "viem": "*" } }, "sha512-GqaRrsB0Z2EgNY6wu4nG5OTc5hF+buNRMc0+jPLlOQonlPJAC4OiMCgCigm4nJecYwsdiQD05W3Z4YUqggxbyw=="],
"@nadohq/indexer-client": ["@nadohq/indexer-client@0.1.0-alpha.45", "", { "dependencies": { "@nadohq/engine-client": "^0.1.0-alpha.45", "@nadohq/shared": "^0.1.0-alpha.45", "axios": "*", "ts-mixer": "*" }, "peerDependencies": { "viem": "*" } }, "sha512-8PzG9taYh380lsKT8i4UvGfhZiPpt+zPisx+ErVqMifyhcJAhgMeYNcfijwMQFNJdgTaBvtI0R5JK98l4Y0YQw=="],
"@nadohq/shared": ["@nadohq/shared@0.1.0-alpha.45", "", { "peerDependencies": { "bignumber.js": "^9.3.0", "viem": "*" } }, "sha512-4Ml8dO9mkwnIidflpunwVMLX2KJA79b53T9xTBkH1Yzy0iOnbEE27r25wvgJgyszU0aznbPBv2++FZ0d0B5Zhg=="],
"@nadohq/trigger-client": ["@nadohq/trigger-client@0.1.0-alpha.45", "", { "dependencies": { "@nadohq/engine-client": "^0.1.0-alpha.45", "@nadohq/shared": "^0.1.0-alpha.45", "axios": "*", "ts-mixer": "*" }, "peerDependencies": { "viem": "*" } }, "sha512-Xt8W9zxOZhMvoXSRnx9kUR26wWuHc3EgTVTBbUXt7SlI9v3IP9I3idS6SU3j2f08flcvNqRmC2EbXyYDC2XeyQ=="],
"@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=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.0", "", { "os": "android", "cpu": "arm" }, "sha512-VxDYCDqOaR7NXzAtvRx7G1u54d2kEHopb28YH/pKzY6y0qmogP3gG7CSiWsq9WvDFxOQMpNEyjVAHZFXfH3o/A=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.0", "", { "os": "android", "cpu": "arm64" }, "sha512-pqDirm8koABIKvzL59YI9W9DWbRlTX7RWhN+auR8HXJxo89m4mjqbah7nJZjeKNTNYopqL+yGg+0mhCpf3xZtQ=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.0", "", { "os": "android", "cpu": "arm64" }, "sha512-pqDirm8koABIKvzL59YI9W9DWbRlTX7RWhN+auR8HXJxo89m4mjqbah7nJZjeKNTNYopqL+yGg+0mhCpf3xZtQ=="],
@@ -124,7 +151,13 @@
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.0", "", { "os": "win32", "cpu": "x64" }, "sha512-6iKDCVSIUQ8jPMoIV0OytRKniaYyy5EbY/RRydmLW8ZR3cEBhxbWl5ro0rkUNe0ef6sScvhbY79HrjRm8i3vDQ=="], "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.0", "", { "os": "win32", "cpu": "x64" }, "sha512-6iKDCVSIUQ8jPMoIV0OytRKniaYyy5EbY/RRydmLW8ZR3cEBhxbWl5ro0rkUNe0ef6sScvhbY79HrjRm8i3vDQ=="],
"@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="], "@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="],
"@scure/bip32": ["@scure/bip32@1.4.0", "", { "dependencies": { "@noble/curves": "~1.4.0", "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg=="],
"@scure/bip39": ["@scure/bip39@1.3.0", "", { "dependencies": { "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ=="],
"@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="], "@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
@@ -138,8 +171,6 @@
"@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="], "@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="],
"@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="],
"@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
"@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="], "@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="],
@@ -154,7 +185,9 @@
"@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], "@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
"ansi-escapes": ["ansi-escapes@7.1.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-YdhtCd19sKRKfAAUsrcC1wzm4JuzJoiX4pOJqIoW2qmKj5WzG/dL8uUJ0361zaXtHqK7gEhOwtAtz7t3Yq3X5g=="], "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.2.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw=="],
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
@@ -166,15 +199,17 @@
"auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="], "auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="],
"axios": ["axios@1.12.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw=="], "axios": ["axios@1.13.4", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg=="],
"bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="], "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
"bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"ccxt": ["ccxt@4.5.5", "", { "dependencies": { "ws": "^8.8.1" } }, "sha512-AyhwTFLkx4sO985ImIOfumEBox7AHD/iqk5tPGICObUSZG6wTXg0aRzU8Hjz974aCMG4msFwLk3A/iXPKAU4wA=="], "ccxt": ["ccxt@4.5.35", "", { "dependencies": { "ws": "^8.8.1" } }, "sha512-aFn1jq/vR9wD3Vxu/2UFoS8snqlvfYn/iYrNhyZZfzB3N+kAHhP5+sAgO6ZwNHkHiUT9IPPahC1ZSFdPtjpIYA=="],
"chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
@@ -186,7 +221,7 @@
"cli-cursor": ["cli-cursor@4.0.0", "", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="], "cli-cursor": ["cli-cursor@4.0.0", "", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="],
"cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="], "cli-truncate": ["cli-truncate@5.1.1", "", { "dependencies": { "slice-ansi": "^7.1.0", "string-width": "^8.0.0" } }, "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A=="],
"code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="], "code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="],
@@ -194,15 +229,13 @@
"convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="], "convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"dotenv": ["dotenv@17.2.2", "", {}, "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q=="], "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
@@ -228,6 +261,10 @@
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
"ethereum-cryptography": ["ethereum-cryptography@2.2.1", "", { "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", "@scure/bip32": "1.4.0", "@scure/bip39": "1.3.0" } }, "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg=="],
"eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
"expect-type": ["expect-type@1.2.2", "", {}, "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA=="], "expect-type": ["expect-type@1.2.2", "", {}, "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
@@ -256,12 +293,14 @@
"indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
"ink": ["ink@6.3.1", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.0", "ansi-escapes": "^7.0.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^4.0.0", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.32.0", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", "string-width": "^7.2.0", "type-fest": "^4.27.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": "^6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-3wGwITGrzL6rkWsi2gEKzgwdafGn4ZYd3u4oRp+sOPvfoxEHlnoB5Vnk9Uy5dMRUhDOqF3hqr4rLQ4lEzBc2sQ=="], "ink": ["ink@6.6.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.1", "ansi-escapes": "^7.2.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", "string-width": "^8.1.0", "type-fest": "^4.27.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": "^6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-QDt6FgJxgmSxAelcOvOHUvFxbIUjVpCH5bx+Slvc5m7IEcpGt3dYwbz/L+oRnqEGeRvwy1tineKK4ect3nW1vQ=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], "is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
"is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="], "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=="], "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
"loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
@@ -282,6 +321,8 @@
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"ox": ["ox@0.11.3", "", { "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-1bWYGk/xZel3xro3l8WGg6eq4YEKlaqvyMtVhfMFpbJzK2F6rj4EDRtqDCWVEJMkzcmEi9uW2QxsqELokOlarw=="],
"patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="], "patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
@@ -296,15 +337,15 @@
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="], "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
"react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="], "react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="],
"restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="], "restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],
"rollup": ["rollup@4.52.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.0", "@rollup/rollup-android-arm64": "4.52.0", "@rollup/rollup-darwin-arm64": "4.52.0", "@rollup/rollup-darwin-x64": "4.52.0", "@rollup/rollup-freebsd-arm64": "4.52.0", "@rollup/rollup-freebsd-x64": "4.52.0", "@rollup/rollup-linux-arm-gnueabihf": "4.52.0", "@rollup/rollup-linux-arm-musleabihf": "4.52.0", "@rollup/rollup-linux-arm64-gnu": "4.52.0", "@rollup/rollup-linux-arm64-musl": "4.52.0", "@rollup/rollup-linux-loong64-gnu": "4.52.0", "@rollup/rollup-linux-ppc64-gnu": "4.52.0", "@rollup/rollup-linux-riscv64-gnu": "4.52.0", "@rollup/rollup-linux-riscv64-musl": "4.52.0", "@rollup/rollup-linux-s390x-gnu": "4.52.0", "@rollup/rollup-linux-x64-gnu": "4.52.0", "@rollup/rollup-linux-x64-musl": "4.52.0", "@rollup/rollup-openharmony-arm64": "4.52.0", "@rollup/rollup-win32-arm64-msvc": "4.52.0", "@rollup/rollup-win32-ia32-msvc": "4.52.0", "@rollup/rollup-win32-x64-gnu": "4.52.0", "@rollup/rollup-win32-x64-msvc": "4.52.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g=="], "rollup": ["rollup@4.52.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.0", "@rollup/rollup-android-arm64": "4.52.0", "@rollup/rollup-darwin-arm64": "4.52.0", "@rollup/rollup-darwin-x64": "4.52.0", "@rollup/rollup-freebsd-arm64": "4.52.0", "@rollup/rollup-freebsd-x64": "4.52.0", "@rollup/rollup-linux-arm-gnueabihf": "4.52.0", "@rollup/rollup-linux-arm-musleabihf": "4.52.0", "@rollup/rollup-linux-arm64-gnu": "4.52.0", "@rollup/rollup-linux-arm64-musl": "4.52.0", "@rollup/rollup-linux-loong64-gnu": "4.52.0", "@rollup/rollup-linux-ppc64-gnu": "4.52.0", "@rollup/rollup-linux-riscv64-gnu": "4.52.0", "@rollup/rollup-linux-riscv64-musl": "4.52.0", "@rollup/rollup-linux-s390x-gnu": "4.52.0", "@rollup/rollup-linux-x64-gnu": "4.52.0", "@rollup/rollup-linux-x64-musl": "4.52.0", "@rollup/rollup-openharmony-arm64": "4.52.0", "@rollup/rollup-win32-arm64-msvc": "4.52.0", "@rollup/rollup-win32-ia32-msvc": "4.52.0", "@rollup/rollup-win32-x64-gnu": "4.52.0", "@rollup/rollup-win32-x64-msvc": "4.52.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g=="],
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
@@ -320,7 +361,7 @@
"std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="], "std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="],
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], "string-width": ["string-width@8.1.1", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw=="],
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
@@ -338,12 +379,18 @@
"tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="], "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=="], "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
"undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="], "undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
"viem": ["viem@2.45.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.11.3", "ws": "8.18.3" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-LN6Pp7vSfv50LgwhkfSbIXftAM5J89lP9x8TeDa8QM7o41IxlHrDh0F9X+FfnCWtsz11pEVV5sn+yBUoOHNqYA=="],
"vite": ["vite@7.1.7", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA=="], "vite": ["vite@7.1.7", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA=="],
"vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="], "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
@@ -356,12 +403,38 @@
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], "ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
"yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
"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=="],
"cli-truncate/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], "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=="],
"viem/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"widest-line/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"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>

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