├── .env.example ├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── README_zh.md ├── assets └── wechat.jpg ├── base ├── __init__.mojo ├── c.mojo ├── far.mojo ├── fixed.mojo ├── globals.mojo ├── httpclient.mojo ├── log.mojo ├── mo.mojo ├── moutil.mojo ├── rate_limiter.mojo ├── redis.mojo ├── requests.mojo ├── sj_dom.mojo ├── sj_ondemand.mojo ├── sonic.mojo ├── ssmap.mojo ├── str.mojo ├── str_cache.mojo ├── thread.mojo ├── ti.mojo ├── websocket.mojo ├── yyjson.mojo └── yyjsonbase.mojo ├── build-all.sh ├── build.sh ├── config.example.toml ├── copy-libmoxt.sh ├── core ├── __init__.mojo ├── binanceclient.mojo ├── binancemodel.mojo ├── binancews.mojo ├── bybitclient.mojo ├── bybitclientjson.mojo ├── bybitmodel.mojo ├── bybitws.mojo ├── env.mojo ├── models.mojo ├── okxclient.mojo ├── okxconsts.mojo └── sign.mojo ├── download_libmoxt.sh ├── experiments ├── test_0001.mojo ├── test_global_values.mojo ├── test_rebind.mojo ├── test_tuple.mojo └── test_v0_6_1.mojo ├── magic.lock ├── main.mojo ├── mojo.Dockerfile ├── mojoproject.toml ├── morrow ├── __init__.mojo ├── _libc.mojo ├── _py.mojo ├── constants.mojo ├── formatter.mojo ├── morrow.mojo ├── timedelta.mojo ├── timezone.mojo └── util.mojo ├── scripts ├── ld └── mojoc ├── setup.sh ├── strategies ├── __init__.mojo ├── grid_strategy.mojo ├── grid_strategy_pm.mojo ├── grid_utils.mojo └── runner.mojo ├── test_all.mojo ├── test_binance.mojo ├── test_bybit.mojo ├── test_dict.mojo ├── test_skiplist.mojo ├── test_trade.mojo ├── trade ├── __init__.mojo ├── backend_interactor.mojo ├── base_strategy.mojo ├── config.mojo ├── executor.mojo ├── helpers.mojo ├── platform.mojo ├── pos.mojo └── types.mojo └── ylstdlib ├── __init__.mojo ├── coroutine.mojo ├── dict.mojo ├── floatutils.mojo ├── list.mojo ├── queue.mojo ├── time.mojo └── twofish.mojo /.env.example: -------------------------------------------------------------------------------- 1 | OKEX_API_KEY= 2 | OKEX_API_SECRET= 3 | OKEX_API_PASSPHRASE= 4 | 5 | BYBIT_API_KEY= 6 | BYBIT_API_SECRET= 7 | 8 | BINANCE_API_KEY= 9 | BINANCE_API_SECRET= 10 | 11 | testnet=true 12 | access_key= 13 | secret_key= 14 | category=linear 15 | symbols=BTCUSDT 16 | depth=1 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: f0cii 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | # Ignore all files 163 | * 164 | # Do not ignore files with extensions 165 | !*.* 166 | # Do not ignore directories 167 | !*/ 168 | 169 | # pixi environments 170 | .pixi 171 | *.egg-info 172 | # magic environments 173 | .magic 174 | 175 | # Ignore special files 176 | libmoxt.so 177 | .env 178 | config.toml 179 | *.Identifier 180 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 壹楽 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

3 | English | 4 | 简体中文 5 |

6 |

7 | 8 | # Deprecated Project 9 | 10 | **Note:** This project is deprecated and has been replaced by the new project available at [furnace-examples](https://github.com/furnace-dev/furnace-examples), which is based on Mojo and Rust. 11 | 12 | # MOXT 13 | 14 | A high-performance trading library, written in Mojo and C++, designed to simplify quantitative trading. 15 | 16 | ## Features 17 | 18 | 1. **Concise and Efficient Syntax** 19 | - **Mojo Programming Language**: Inspired by the ease of use of Python, the Mojo programming language has been designed with a concise and efficient syntax aimed at delivering performance comparable to that of C/C++/Rust, while maintaining code readability and ease of use. 20 | 21 | 2. **Integration with Multiple Exchanges** 22 | - **Broad Support**: Easily integrate with mainstream exchanges, such as OKX, Bybit, Binance, etc. 23 | 24 | 3. **Extensive Support for Technical Indicators** 25 | - **[TulipIndicators](https://tulipindicators.org/)**: Integrates over 104 technical indicators, offering powerful tools for market analysis and strategy development. 26 | 27 | 4. **Support for Multiple Strategies** 28 | - **Flexible Application**: Run and manage multiple trading strategies simultaneously, improving resource utilization and investment efficiency. 29 | 30 | 5. **Event-Driven Model** 31 | - **Efficient Response**: Optimize strategy execution and signal processing to ensure timely responses to market changes. 32 | 33 | 6. **Integration with [Photon](https://github.com/alibaba/PhotonLibOS) High-Performance Coroutine Library** 34 | - **Concurrent Processing**: Enhance the program's concurrency capabilities and optimize execution efficiency and resource utilization with the efficient Photon coroutine library. 35 | 36 | 7. **Low-Latency HTTP Client Component** 37 | - **Rapid Communication**: Ensure real-time and accurate data transmission and strategy execution with the low-latency HTTP client component. 38 | 39 | 8. **High-Performance WebSocket Module** 40 | - **Real-Time Data Stream**: Achieve real-time data exchange with exchanges and ensure immediate information updates and rapid strategy execution with the high-performance WebSocket module. 41 | 42 | 9. **Integration with [simdjson](https://github.com/simdjson/simdjson) Parsing Library** 43 | - **Efficient Parsing**: Utilize the simdjson parsing library for rapid JSON processing capabilities, ensuring efficient and accurate data parsing. 44 | 45 | ## System Requirements 46 | 47 | To ensure optimal performance and compatibility, please make sure your system meets the following requirements: 48 | 49 | - Operating System: Ubuntu 20.04+ (amd64) 50 | - Python Environment: Python 3.9+ is required. It is recommended to manage it using [Miniconda](https://docs.anaconda.com/free/miniconda/index.html). 51 | - Mojo Programming Language Version: 24.3.0 52 | 53 | For information on how to install Mojo, please refer to the [Mojo Official Installation Guide](https://docs.modular.com/mojo/manual/get-started/). Make sure you are using the supported version of the operating system to avoid compatibility issues. 54 | 55 | ## Installation 56 | 57 | Before starting, make sure you have installed the Mojo programming language and all necessary dependencies according to the system requirements. Next, you can install and configure this quantitative trading library by following the steps below: 58 | 59 | 1. Clone the Project 60 | 61 | Clone this project into your local environment: 62 | 63 | ```bash 64 | git clone https://github.com/f0cii/moxt.git 65 | cd moxt 66 | ``` 67 | 68 | 2. Use Docker (Optional) 69 | 70 | If you prefer to use Docker to run the Mojo environment, you can use our provided Dockerfile by following these steps: 71 | 72 | ```bash 73 | docker build -t moxt -f mojo.Dockerfile . 74 | docker run -it moxt 75 | ``` 76 | 77 | This step is optional and is provided for those who wish to simplify their environment setup using Docker. If you are not familiar with Docker, we recommend checking the Docker Official Documentation for more information. 78 | 79 | ## Download libmoxt.so 80 | 81 | Before running the application, you need to download the compiled libmoxt.so library file. You can use either curl or wget command to download it directly into your project directory: 82 | 83 | ```bash 84 | # Install jq 85 | sudo apt install jq 86 | # Make the download script executable 87 | chmod +x download_libmoxt.sh 88 | # Run the download script 89 | ./download_libmoxt.sh 90 | ``` 91 | 92 | Note: These commands download the latest version of libmoxt.so from the moxt-cpp GitHub releases. Ensure you have curl or wget installed on your system to use these commands. 93 | 94 | Alternatively, if you prefer to compile libmoxt.so yourself or need a specific version, please visit [moxt-cpp](https://github.com/f0cii/moxt-cpp) for compilation instructions. 95 | 96 | ## Why is it necessary to download libmoxt.so? 97 | 98 | Some of the core functionalities of this project are implemented in C++ and compiled into the libmoxt.so shared library. This means that in order for the MOXT library to function correctly and take full advantage of these high-performance features, you need to download this shared library to your project directory. The C++ code for the project is located in the [moxt-cpp](https://github.com/f0cii/moxt-cpp) repository. 99 | 100 | ## Quick Start 101 | 102 | ```mojo 103 | # Set script execution permissions 104 | chmod +x ./scripts/ld 105 | chmod +x ./scripts/mojoc 106 | chmod +x ./build.sh 107 | 108 | # Compile 109 | magic shell 110 | ./build.sh main.mojo -o moxt 111 | # Set environment variables 112 | source setup.sh 113 | # Install Python dependencies 114 | pip install tomli 115 | 116 | # Running ./moxt initiates the quantitative trading process. 117 | # Please note that the specific strategies to be run must be specified in the configuration file (config.toml). Ensure that the strategy settings in this file have been correctly configured according to your requirements before executing ./moxt. 118 | ./moxt 119 | ``` 120 | 121 | Note: The `trading_strategies` directory is used to store trading strategies. 122 | 123 | ## Community 124 | 125 | Join our community to get help, share ideas, and collaborate! 126 | 127 | * Discord: Join our [Discord server](https://discord.gg/XE8KJhq8) to chat with the MOXT community. 128 | 129 | ## About Me 130 | 131 | Feel free to add me on WeChat for further discussions and sharing! 132 | 133 | ![WeChat QR Code](https://raw.githubusercontent.com/f0cii/moxt/main/assets/wechat.jpg) 134 | 135 | ## License 136 | 137 | This project is licensed under the MIT License - see the [LICENSE] file for more details. 138 | 139 | --- 140 | 141 | **Disclaimer: ** This project is for learning and research purposes only and does not constitute any trading or investment advice. Please use this project cautiously for actual trading. 142 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | # 已废弃项目 2 | 3 | **注意:** 此项目已被废弃,并被新项目替代,新的项目可在 [furnace-examples](https://github.com/furnace-dev/furnace-examples) 找到,该项目基于 Mojo 和 Rust。 4 | 5 | # MOXT 6 | 7 | 一个高性能的交易库,用Mojo和C++编写,旨在简化量化交易。 8 | 9 | ## 特点 10 | 11 | 1. **简洁高效的语法** 12 | - **Mojo编程语言**:受Python易用性的启发,Mojo设计了简洁且高效的语法,旨在提供类似C/C++/Rust的高性能,同时保持代码的可读性和易用性。 13 | 14 | 2. **多交易所集成** 15 | - **广泛支持**:轻松集成主流交易所,如OKX、Bybit、Binance等。 16 | 17 | 3. **丰富的技术指标支持** 18 | - **[TulipIndicators](https://tulipindicators.org/)**:集成超过104种技术指标,为市场分析和策略开发提供强大工具。 19 | 20 | 4. **多策略支持** 21 | - **灵活运用**:同时运行和管理多个交易策略,提高资源利用率和投资效率。 22 | 23 | 5. **事件驱动模型** 24 | - **高效响应**:优化策略执行和信号处理,确保及时响应市场变化。 25 | 26 | 6. **集成[Photon](https://github.com/alibaba/PhotonLibOS)高性能协程库** 27 | - **并发处理**:通过高效的Photon协程库,提高程序的并发处理能力,优化执行效率和资源利用。 28 | 29 | 7. **低延迟HTTP客户端组件** 30 | - **快速通讯**:通过低延迟的HTTP客户端组件,确保数据传输和策略执行的实时性和准确性。 31 | 32 | 8. **高性能WebSocket模块** 33 | - **实时数据流**:利用高性能的WebSocket模块,实现与交易所的实时数据交流,保证信息的即时更新和策略的迅速执行。 34 | 35 | 9. **集成[simdjson](https://github.com/simdjson/simdjson)解析库** 36 | - **高效解析**:利用simdjson解析库,提供极速的JSON处理能力,确保数据解析的高效性和准确性。 37 | 38 | ## 系统要求 39 | 40 | 为了确保最佳性能和兼容性,请确保您的系统满足以下要求: 41 | 42 | - 操作系统:Ubuntu 20.04+ (amd64) 43 | - Python环境:Python 3.9+,推荐使用[Miniconda](https://docs.anaconda.com/free/miniconda/index.html)进行管理 44 | - Mojo编程语言版本:24.3.0 45 | 46 | 关于如何安装Mojo,请参考[Mojo官方安装指南](https://docs.modular.com/mojo/manual/get-started/)。确保您使用的是支持的操作系统版本,以避免兼容性问题。 47 | 48 | ## 安装 49 | 50 | 在开始之前,请确保您已按照系统要求安装了Mojo编程语言和所有必要的依赖项。接下来,您可以通过以下步骤安装和配置本量化交易库: 51 | 52 | 1. 克隆项目 53 | 54 | 克隆本项目到您的本地环境: 55 | 56 | ```bash 57 | git clone https://github.com/f0cii/moxt.git 58 | cd moxt 59 | ``` 60 | 61 | 2. 使用Docker(可选) 62 | 63 | 如果您偏好使用Docker来运行Mojo环境,可以通过以下步骤使用我们提供的Dockerfile: 64 | 65 | ```bash 66 | docker build -t moxt -f mojo.Dockerfile . 67 | docker run -it moxt 68 | ``` 69 | 70 | 这一步是可选的,为那些希望通过Docker简化环境配置的用户提供便利。如果您不熟悉Docker,建议查看Docker官方文档以获取更多信息。 71 | 72 | ## 下载 libmoxt.so 73 | 74 | 在运行应用程序之前,您需要下载编译好的 libmoxt.so 库文件。您可以使用 curl 或 wget 命令直接将其下载到您的项目目录中: 75 | 76 | ```bash 77 | # 安装 jq 78 | sudo apt install jq 79 | # 将下载脚本设置为可执行 80 | chmod +x download_libmoxt.sh 81 | # 运行下载脚本 82 | ./download_libmoxt.sh 83 | ``` 84 | 85 | 注意:这些命令从 moxt-cpp GitHub 发布中下载 libmoxt.so 的最新版本。确保您的系统中已安装 curl 或 wget 以使用这些命令。 86 | 87 | 或者,如果您更倾向于自己编译 libmoxt.so 或需要特定版本,请访问 [moxt-cpp](https://github.com/f0cii/moxt-cpp) 获取编译指南。 88 | 89 | ## 为什么需要下载libmoxt.so? 90 | 91 | 本项目的一些核心功能是基于C++实现的,并被编译为libmoxt.so共享库。这意味着,为了确保MOXT库能够正常运行并充分利用这些高性能特性,您需要将此共享库下载到您的项目目录中。项目的C++代码位于[moxt-cpp](https://github.com/f0cii/moxt-cpp)仓库中。 92 | 93 | ## 快速开始 94 | 95 | ```mojo 96 | # 设置脚本可执行权限 97 | chmod +x ./scripts/ld 98 | chmod +x ./scripts/mojoc 99 | chmod +x ./build.sh 100 | 101 | # 编译 102 | ./build.sh main.mojo -o moxt 103 | # 设置环境变量 104 | source setup.sh 105 | # 安装python依赖库 106 | pip install tomli 107 | 108 | # 运行 ./moxt 将启动量化交易过程。 109 | # 请注意,运行的具体策略需要在配置文件(config.toml)中进行指定。确保在执行 ./moxt 之前,已根据您的需求正确配置了该文件中的策略设置。 110 | ./moxt 111 | ``` 112 | 113 | 注意:`trading_strategies`目录用于存放交易策略。 114 | 115 | ## 授权 116 | 117 | 本项目采用 MIT 授权许可 - 请查看 [LICENSE] 文件了解更多细节。 118 | 119 | ## 社区 120 | 121 | 加入我们的社区,获取帮助,分享想法,进行协作! 122 | 123 | * Discord:加入我们的[Discord服务器](https://discord.gg/XE8KJhq8),与MOXT社区交流。 124 | 125 | ## 关于我 126 | 127 | 欢迎通过微信加我为好友,一起交流分享! 128 | 129 | ![WeChat QR Code](https://raw.githubusercontent.com/f0cii/moxt/main/assets/wechat.jpg) 130 | 131 | --- 132 | 133 | **免责声明:** 本项目仅供学习和研究使用,不构成任何交易建议或投资建议。请谨慎使用该项目进行实际交易。 134 | -------------------------------------------------------------------------------- /assets/wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f0cii/moxt/dee9018e4367dad3d9cfc600804f9b3d14493beb/assets/wechat.jpg -------------------------------------------------------------------------------- /base/__init__.mojo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f0cii/moxt/dee9018e4367dad3d9cfc600804f9b3d14493beb/base/__init__.mojo -------------------------------------------------------------------------------- /base/c.mojo: -------------------------------------------------------------------------------- 1 | from sys.info import sizeof 2 | from sys import external_call 3 | from memory import UnsafePointer 4 | 5 | # from sys.intrinsics import external_call, _mlirtype_is_eq 6 | from sys.intrinsics import _mlirtype_is_eq 7 | from memory import memcpy, memset_zero 8 | 9 | # Types aliases 10 | alias c_void = UInt8 11 | alias c_char = UInt8 12 | 13 | alias c_schar = Int8 14 | alias c_uchar = UInt8 15 | alias c_short = Int16 16 | alias c_ushort = UInt16 17 | alias c_int = Int32 18 | alias c_uint = UInt32 19 | alias c_long = Int64 20 | alias c_ulong = UInt64 21 | alias c_float = Float32 22 | alias c_double = Float64 23 | 24 | # Note: `Int` is known to be machine's width 25 | alias c_size_t = Int 26 | alias c_ssize_t = Int 27 | 28 | alias ptrdiff_t = Int64 29 | alias intptr_t = Int64 30 | alias uintptr_t = UInt64 31 | 32 | alias c_char_pointer = UnsafePointer[c_schar] 33 | 34 | alias c_void_pointer = UnsafePointer[c_void] 35 | 36 | 37 | fn strlen(s: UnsafePointer[c_char]) -> c_size_t: 38 | """ 39 | :strlen libc POSIX `strlen` function 40 | Reference: https://man7.org/linux/man-pages/man3/strlen.3p.html 41 | Fn signature: size_t strlen(const char *s). 42 | Args: 43 | s 44 | Returns: 45 | . 46 | """ 47 | return external_call["strlen", c_size_t, UnsafePointer[c_char]](s) 48 | 49 | 50 | fn strlen(s: UnsafePointer[c_schar]) -> c_size_t: 51 | """ 52 | :strlen libc POSIX `strlen` function 53 | Reference: https://man7.org/linux/man-pages/man3/strlen.3p.html 54 | Fn signature: size_t strlen(const char *s). 55 | Args: 56 | s 57 | Returns: 58 | . 59 | """ 60 | return external_call["strlen", c_size_t, UnsafePointer[c_schar]](s) 61 | 62 | 63 | fn to_char_ptr(s: String) -> UnsafePointer[c_char]: 64 | """Only ASCII-based strings.""" 65 | var ptr = UnsafePointer[c_char]().alloc(len(s) + 1) 66 | for i in range(len(s)): 67 | ptr[i] = ord(s[i]) 68 | ptr[len(s)] = ord("\0") 69 | return ptr 70 | 71 | 72 | fn to_schar_ptr(s: String) -> UnsafePointer[c_schar]: 73 | """Only ASCII-based strings.""" 74 | var ptr = UnsafePointer[c_schar]().alloc(len(s) + 1) 75 | for i in range(len(s)): 76 | ptr[i] = ord(s[i]) 77 | ptr[len(s)] = ord("\0") 78 | return ptr 79 | 80 | 81 | fn c_str_to_string_raw(s: UnsafePointer[c_char]) -> String: 82 | return String(s.bitcast[UInt8](), strlen(s)) 83 | 84 | 85 | fn c_str_to_string_raw(s: UnsafePointer[UInt8], n: Int) -> String: 86 | return String(s, n) 87 | 88 | 89 | fn c_str_to_string(s: UnsafePointer[c_schar], n: Int) -> String: 90 | var size = n + 1 91 | var ptr = UnsafePointer[Int8]().alloc(size) 92 | memset_zero(ptr.offset(n), 1) 93 | memcpy(ptr, s, n) 94 | return String(ptr.bitcast[UInt8](), size) 95 | 96 | 97 | fn c_str_to_string(s: UnsafePointer[c_char], n: Int) -> String: 98 | var size = n + 1 99 | var ptr = UnsafePointer[UInt8]().alloc(size) 100 | memset_zero(ptr.offset(n), 1) 101 | memcpy(ptr, s, n) 102 | return String(ptr, size) 103 | 104 | 105 | @always_inline 106 | fn str_as_scalar_pointer(s: StringLiteral) -> UnsafePointer[Scalar[DType.int8]]: 107 | return s.unsafe_cstr_ptr() 108 | 109 | 110 | @always_inline 111 | fn str_as_scalar_pointer( 112 | s: String, 113 | ) -> UnsafePointer[Scalar[DType.int8]]: 114 | return s.unsafe_cstr_ptr() 115 | -------------------------------------------------------------------------------- /base/far.mojo: -------------------------------------------------------------------------------- 1 | from .c import * 2 | from .mo import * 3 | 4 | 5 | @value 6 | struct Far: 7 | var ptr: c_void_pointer 8 | 9 | fn __init__(inout self): 10 | self.ptr = seq_far_new() 11 | 12 | fn __init__(inout self, ptr: c_void_pointer): 13 | self.ptr = ptr 14 | 15 | @always_inline 16 | fn get_int(self, key: String) -> Int: 17 | return int( 18 | seq_far_get_int( 19 | self.ptr, 20 | str_as_scalar_pointer(key), 21 | len(key), 22 | ) 23 | ) 24 | 25 | @always_inline 26 | fn get_float(self, key: String) -> Float64: 27 | return seq_far_get_float(self.ptr, str_as_scalar_pointer(key), len(key)) 28 | 29 | @always_inline 30 | fn get_bool(self, key: String) -> Bool: 31 | return seq_far_get_bool(self.ptr, str_as_scalar_pointer(key), len(key)) 32 | 33 | @always_inline 34 | fn get_str(self, key: String) -> String: 35 | var n: c_size_t = 0 36 | var c_str = seq_far_get_string( 37 | self.ptr, 38 | str_as_scalar_pointer(key), 39 | len(key), 40 | UnsafePointer[c_size_t].address_of(n), 41 | ) 42 | return c_str_to_string(c_str, n) 43 | 44 | @always_inline 45 | fn __len__(self) -> Int: 46 | return seq_far_size(self.ptr) 47 | 48 | @always_inline 49 | fn set_int(self, key: String, value: Int): 50 | _ = seq_far_set_int( 51 | self.ptr, 52 | str_as_scalar_pointer(key), 53 | len(key), 54 | value, 55 | ) 56 | 57 | @always_inline 58 | fn set_float(self, key: String, value: Float64): 59 | _ = seq_far_set_float( 60 | self.ptr, 61 | str_as_scalar_pointer(key), 62 | len(key), 63 | value, 64 | ) 65 | 66 | @always_inline 67 | fn set_bool(self, key: String, value: Bool): 68 | _ = seq_far_set_bool( 69 | self.ptr, 70 | str_as_scalar_pointer(key), 71 | len(key), 72 | value, 73 | ) 74 | 75 | @always_inline 76 | fn set_str(self, key: String, value: String): 77 | _ = seq_far_set_string( 78 | self.ptr, 79 | str_as_scalar_pointer(key), 80 | len(key), 81 | str_as_scalar_pointer(value), 82 | len(value), 83 | ) 84 | 85 | @always_inline 86 | fn free(self): 87 | seq_far_free(self.ptr) 88 | -------------------------------------------------------------------------------- /base/fixed.mojo: -------------------------------------------------------------------------------- 1 | from sys import external_call 2 | from memory import UnsafePointer 3 | from .c import * 4 | from .mo import * 5 | 6 | 7 | alias FIXED_SCALE_I = 1000000000000 8 | alias FIXED_SCALE_F = 1000000000000.0 9 | 10 | 11 | fn seq_fixed12_new_string(cstr: c_char_pointer, cstr_len: c_size_t) -> Int64: 12 | return external_call[ 13 | "seq_fixed12_new_string", Int64, c_char_pointer, c_size_t 14 | ](cstr, cstr_len) 15 | 16 | 17 | fn seq_fixed12_to_string(fixed: Int64, result: c_void_pointer) -> c_size_t: 18 | return external_call[ 19 | "seq_fixed12_to_string", c_size_t, Int64, c_void_pointer 20 | ](fixed, result) 21 | 22 | 23 | # Customize multiplication operation 24 | fn seq_fixed_mul(a: Int64, b: Int64) -> Int64: 25 | return external_call["seq_fixed_mul", Int64, Int64, Int64](a, b) 26 | 27 | 28 | # Customize division operation 29 | fn seq_fixed_truediv(a: Int64, b: Int64) -> Int64: 30 | return external_call["seq_fixed_truediv", Int64, Int64, Int64](a, b) 31 | 32 | 33 | fn seq_fixed_round_to_fractional(a: Int64, scale: Int64) -> Int64: 34 | return external_call["seq_fixed_round_to_fractional", Int64, Int64, Int64]( 35 | a, scale 36 | ) 37 | 38 | 39 | fn seq_fixed_round(a: Int64, decimalPlaces: Int) -> Int64: 40 | return external_call["seq_fixed_round", Int64, Int64, Int](a, decimalPlaces) 41 | 42 | 43 | @value 44 | @register_passable 45 | struct Fixed(Stringable): 46 | alias zero = Fixed(0) 47 | alias one = Fixed(1) 48 | alias two = Fixed(2) 49 | alias three = Fixed(3) 50 | alias four = Fixed(4) 51 | alias five = Fixed(5) 52 | alias six = Fixed(6) 53 | alias seven = Fixed(7) 54 | alias eight = Fixed(8) 55 | alias nine = Fixed(9) 56 | alias ten = Fixed(10) 57 | 58 | var _value: Int64 59 | 60 | fn __init__(inout self): 61 | self._value = 0 62 | 63 | fn __init__(inout self, v: Int): 64 | self._value = FIXED_SCALE_I * v 65 | 66 | fn __init__(inout self, v: Float64): 67 | self._value = Int64(int(v * FIXED_SCALE_F)) 68 | 69 | fn __init__(inout self, v: String): 70 | var v_ = seq_fixed12_new_string(str_as_scalar_pointer(v), len(v)) 71 | self._value = v_ 72 | 73 | fn copy_from(inout self, other: Self): 74 | self._value = other._value 75 | 76 | @staticmethod 77 | fn from_value(value: Int64) -> Self: 78 | return Self { 79 | _value: value, 80 | } 81 | 82 | @always_inline 83 | fn is_zero(self) -> Bool: 84 | return self._value == 0 85 | 86 | @always_inline 87 | fn value(self) -> Int64: 88 | return self._value 89 | 90 | @always_inline 91 | fn to_int(self) -> Int: 92 | return int(self._value / FIXED_SCALE_I) 93 | 94 | @always_inline 95 | fn to_float(self) -> Float64: 96 | return self._value.cast[DType.float64]() / FIXED_SCALE_F 97 | 98 | @always_inline 99 | fn to_string(self) -> String: 100 | var ptr = UnsafePointer[c_char].alloc(17) 101 | var n = seq_fixed12_to_string(self._value, ptr) 102 | var s = c_str_to_string(ptr, n) 103 | ptr.free() 104 | return s 105 | 106 | @always_inline 107 | fn round_to_fractional(self, scale: Int) -> Self: 108 | var v = seq_fixed_round_to_fractional(self._value, scale) 109 | return Self { 110 | _value: v, 111 | } 112 | 113 | @always_inline 114 | fn round(self, decimal_places: Int) -> Self: 115 | var v = seq_fixed_round(self._value, decimal_places) 116 | return Self { 117 | _value: v, 118 | } 119 | 120 | @always_inline 121 | fn abs(self) -> Self: 122 | var v = -self._value if self._value < 0 else self._value 123 | return Self {_value: v} 124 | 125 | fn __eq__(self, other: Self) -> Bool: 126 | return self._value == other._value 127 | 128 | fn __ne__(self, other: Self) -> Bool: 129 | return self._value != other._value 130 | 131 | fn __lt__(self, other: Self) -> Bool: 132 | return self._value < other._value 133 | 134 | fn __le__(self, other: Self) -> Bool: 135 | return self._value <= other._value 136 | 137 | fn __gt__(self, other: Self) -> Bool: 138 | return self._value > other._value 139 | 140 | fn __ge__(self, other: Self) -> Bool: 141 | return self._value >= other._value 142 | 143 | # Customizing negation 144 | fn __neg__(self) -> Self: 145 | return Self {_value: -self._value} 146 | 147 | # Customizing addition 148 | fn __add__(self, other: Self) -> Self: 149 | return Self {_value: self._value + other._value} 150 | 151 | # Customizing += 152 | fn __iadd__(inout self, other: Self): 153 | self._value += other._value 154 | 155 | # Customizing subtraction 156 | fn __sub__(self, other: Self) -> Self: 157 | return Self {_value: self._value - other._value} 158 | 159 | # Customizing -= 160 | fn __isub__(inout self, other: Self): 161 | self._value -= other._value 162 | 163 | # Customizing multiplication 164 | fn __mul__(self, other: Self) -> Self: 165 | var v = seq_fixed_mul(self._value, other._value) 166 | return Self {_value: v} 167 | 168 | # Customizing *= 169 | fn __imul__(inout self, other: Self): 170 | self._value = seq_fixed_mul(self._value, other._value) 171 | 172 | # Customizing division 173 | fn __truediv__(self, other: Self) -> Self: 174 | var v = seq_fixed_truediv(self._value, other._value) 175 | return Self {_value: v} 176 | 177 | # Customizing /= 178 | fn __itruediv__(inout self, other: Self): 179 | self._value = seq_fixed_truediv(self._value, other._value) 180 | 181 | fn __str__(self) -> String: 182 | return self.to_string() 183 | -------------------------------------------------------------------------------- /base/globals.mojo: -------------------------------------------------------------------------------- 1 | from sys.ffi import _get_global 2 | from sys import external_call 3 | from memory import UnsafePointer 4 | from collections.dict import Dict 5 | from .mo import seq_set_global_int, seq_get_global_int 6 | 7 | 8 | # Set global pointer 9 | @always_inline 10 | fn set_global_pointer(key: Int, pointer: Int): 11 | seq_set_global_int(key, pointer) 12 | 13 | 14 | # Get global pointer 15 | @always_inline 16 | fn get_global_pointer(key: Int) -> Int: 17 | return seq_get_global_int(key) 18 | 19 | 20 | alias SignalHandler = fn (Int) -> None 21 | 22 | 23 | # SEQ_FUNC void seq_register_signal_handler(int signum, SignalHandler handler) 24 | fn seq_register_signal_handler(signum: Int, handler: SignalHandler): 25 | external_call["seq_register_signal_handler", NoneType, Int, SignalHandler]( 26 | signum, handler 27 | ) 28 | 29 | 30 | @value 31 | struct __G: 32 | var stop_requested_flag: Bool 33 | var stopped_flag: Bool 34 | var current_strategy: String 35 | var executor_ptr: Int 36 | var algo_id: Int 37 | var vars: Dict[String, String] 38 | 39 | fn __init__(inout self): 40 | self.stop_requested_flag = False 41 | self.stopped_flag = False 42 | self.current_strategy = "" 43 | self.executor_ptr = 0 44 | self.algo_id = 0 45 | self.vars = Dict[String, String]() 46 | 47 | 48 | fn _GLOBAL() -> UnsafePointer[__G]: 49 | var p = _get_global["_GLOBAL", _init_global, _destroy_global]() 50 | return p.bitcast[__G]() 51 | 52 | 53 | fn _init_global(payload: UnsafePointer[NoneType]) -> UnsafePointer[NoneType]: 54 | var ptr = UnsafePointer[__G].alloc(1) 55 | ptr.init_pointee_move(__G()) 56 | return ptr.bitcast[NoneType]() 57 | 58 | 59 | fn _destroy_global(p: UnsafePointer[NoneType]): 60 | p.free() 61 | 62 | 63 | fn _GLOBAL_INT[name: StringLiteral]() -> UnsafePointer[Int]: 64 | var p = _get_global[name, _initialize_int, _destroy_int]() 65 | return p.bitcast[Int]() 66 | 67 | 68 | fn _initialize_int(payload: UnsafePointer[NoneType]) -> UnsafePointer[NoneType]: 69 | var data = UnsafePointer[Int].alloc(1) 70 | data[0] = 0 71 | return data.bitcast[NoneType]() 72 | 73 | 74 | fn _destroy_int(p: UnsafePointer[NoneType]): 75 | p.free() 76 | 77 | 78 | fn _GLOBAL_FLOAT[name: StringLiteral]() -> UnsafePointer[Float64]: 79 | var p = _get_global[name, _initialize_float64, _destroy_float64]() 80 | return p.bitcast[Float64]() 81 | 82 | 83 | fn _initialize_float64( 84 | payload: UnsafePointer[NoneType], 85 | ) -> UnsafePointer[NoneType]: 86 | var data = UnsafePointer[Float64].alloc(1) 87 | data[0] = 0 88 | return data.bitcast[NoneType]() 89 | 90 | 91 | fn _destroy_float64(p: UnsafePointer[NoneType]): 92 | p.free() 93 | 94 | 95 | fn _GLOBAL_BOOL[name: StringLiteral]() -> UnsafePointer[Bool]: 96 | var p = _get_global[name, _initialize_bool, _destroy_bool]() 97 | return p.bitcast[Bool]() 98 | 99 | 100 | fn _initialize_bool( 101 | payload: UnsafePointer[NoneType], 102 | ) -> UnsafePointer[NoneType]: 103 | var data = UnsafePointer[Bool].alloc(1) 104 | data[0] = False 105 | return data.bitcast[NoneType]() 106 | 107 | 108 | fn _destroy_bool(p: UnsafePointer[NoneType]): 109 | p.free() 110 | 111 | 112 | fn _GLOBAL_STRING[name: StringLiteral]() -> UnsafePointer[String]: 113 | var p = _get_global[name, _initialize_string, _destroy_string]() 114 | return p.bitcast[String]() 115 | 116 | 117 | fn _initialize_string( 118 | payload: UnsafePointer[NoneType], 119 | ) -> UnsafePointer[NoneType]: 120 | var ptr = UnsafePointer[String].alloc(1) 121 | ptr.init_pointee_move(String("")) 122 | return ptr.bitcast[NoneType]() 123 | 124 | 125 | fn _destroy_string(p: UnsafePointer[NoneType]): 126 | p.free() 127 | -------------------------------------------------------------------------------- /base/httpclient.mojo: -------------------------------------------------------------------------------- 1 | from collections import Dict 2 | from collections.optional import Optional 3 | from .c import * 4 | from .mo import * 5 | from .ssmap import SSMap 6 | 7 | 8 | alias VERB_UNKNOWN = 0 9 | alias VERB_DELETE = 1 10 | alias VERB_GET = 2 11 | alias VERB_HEAD = 3 12 | alias VERB_POST = 4 13 | alias VERB_PUT = 5 14 | 15 | 16 | # alias Headers = Dict[String, String] 17 | alias Headers = SSMap 18 | alias DEFAULT_BUFF_SIZE = 1024 * 100 19 | 20 | 21 | @value 22 | struct QueryParams: 23 | var data: Dict[String, String] 24 | 25 | fn __init__(inout self): 26 | self.data = Dict[String, String]() 27 | 28 | fn __setitem__(inout self, name: String, value: String): 29 | self.data[name] = value 30 | 31 | fn to_string(self) raises -> String: 32 | if len(self.data) == 0: 33 | return "" 34 | 35 | var url = String("?") 36 | for item in self.data.items(): 37 | # if item.value == "": 38 | # continue 39 | url += item[].key + "=" + item[].value + "&" 40 | return url[1:-1] 41 | 42 | fn debug(inout self) raises: 43 | for item in self.data.items(): 44 | logi( 45 | # str(i) 46 | # + ": " 47 | str(item[].key) 48 | + " = " 49 | + str(item[].value) 50 | ) 51 | 52 | 53 | @value 54 | struct HttpResponse: 55 | var status_code: Int 56 | var text: String 57 | 58 | fn __init__(inout self, status_code: Int, text: String): 59 | self.status_code = status_code 60 | self.text = text 61 | 62 | 63 | struct HttpClient: 64 | var _base_url: String 65 | var _method: Int 66 | var ptr: c_void_pointer 67 | var _verbose: Bool 68 | 69 | fn __init__(inout self, base_url: String, method: Int = tlsv12_client): 70 | logd("HttpClient.__init__") 71 | self._base_url = base_url 72 | self._method = method 73 | self.ptr = seq_client_new( 74 | str_as_scalar_pointer(base_url), 75 | len(base_url), 76 | method, 77 | ) 78 | self._verbose = False 79 | logd("HttpClient.__init__ done") 80 | 81 | fn __moveinit__(inout self, owned existing: Self): 82 | logd("HttpClient.__moveinit__") 83 | self._base_url = existing._base_url 84 | self._method = existing._method 85 | self.ptr = seq_client_new( 86 | str_as_scalar_pointer(self._base_url), 87 | len(self._base_url), 88 | self._method, 89 | ) 90 | self._verbose = existing._verbose 91 | existing.ptr = c_void_pointer() 92 | logd("HttpClient.__moveinit__ done") 93 | 94 | fn __del__(owned self): 95 | logd("HttpClient.__del__") 96 | var NULL = c_void_pointer() 97 | if self.ptr != NULL: 98 | seq_client_free(self.ptr) 99 | self.ptr = NULL 100 | logd("HttpClient.__del__ done") 101 | 102 | fn set_verbose(inout self, verbose: Bool): 103 | self._verbose = verbose 104 | 105 | fn delete( 106 | self, 107 | request_path: String, 108 | inout headers: Headers, 109 | buff_size: Int = DEFAULT_BUFF_SIZE, 110 | ) -> HttpResponse: 111 | var res = self.do_request( 112 | request_path, VERB_DELETE, headers, "", buff_size 113 | ) 114 | return res 115 | 116 | fn get( 117 | self, 118 | request_path: String, 119 | inout headers: Headers, 120 | buff_size: Int = DEFAULT_BUFF_SIZE, 121 | ) -> HttpResponse: 122 | var res = self.do_request( 123 | request_path, VERB_GET, headers, "", buff_size 124 | ) 125 | return res 126 | 127 | fn head( 128 | self, 129 | request_path: String, 130 | data: String, 131 | inout headers: Headers, 132 | buff_size: Int = DEFAULT_BUFF_SIZE, 133 | ) -> HttpResponse: 134 | var res = self.do_request( 135 | request_path, VERB_HEAD, headers, data, buff_size 136 | ) 137 | return res 138 | 139 | fn post( 140 | self, 141 | request_path: String, 142 | data: String, 143 | inout headers: Headers, 144 | buff_size: Int = DEFAULT_BUFF_SIZE, 145 | ) -> HttpResponse: 146 | var res = self.do_request( 147 | request_path, VERB_POST, headers, data, buff_size 148 | ) 149 | return res 150 | 151 | fn put( 152 | self, 153 | request_path: String, 154 | data: String, 155 | inout headers: Headers, 156 | buff_size: Int = DEFAULT_BUFF_SIZE, 157 | ) -> HttpResponse: 158 | var res = self.do_request( 159 | request_path, VERB_PUT, headers, data, buff_size 160 | ) 161 | return res 162 | 163 | fn do_request( 164 | self, 165 | path: String, 166 | verb: Int, 167 | inout headers: Headers, 168 | body: String, 169 | buff_size: Int, 170 | ) -> HttpResponse: 171 | var n: Int = 0 172 | headers["User-Agent"] = "MOXT/1.0.0" 173 | var buff = UnsafePointer[UInt8].alloc(buff_size) 174 | var status = seq_cclient_do_request( 175 | self.ptr, 176 | str_as_scalar_pointer(path), 177 | len(path), 178 | verb, 179 | headers.ptr, 180 | str_as_scalar_pointer(body), 181 | len(body), 182 | buff, 183 | buff_size, 184 | UnsafePointer[Int].address_of(n), 185 | self._verbose, 186 | ) 187 | 188 | var s = c_str_to_string(buff, n) 189 | buff.free() 190 | 191 | return HttpResponse(status, s) 192 | -------------------------------------------------------------------------------- /base/log.mojo: -------------------------------------------------------------------------------- 1 | from sys.ffi import _get_global 2 | from collections.optional import Optional 3 | from collections.dict import Dict 4 | from utils.variant import Variant 5 | from .mo import seq_snowflake_id 6 | from .sonic import SonicDocument, SonicNode 7 | from .redis import Redis 8 | from .thread import LockfreeQueue, lockfree_queue_itf 9 | from .c import * 10 | from .mo import * 11 | from .fixed import Fixed 12 | from ylstdlib.time import time_ns 13 | 14 | 15 | alias Args = Variant[String, Int, Float64, Bool] 16 | alias LOG_QUEUE = "_LOG_QUEUE" 17 | 18 | 19 | # 记录交易事件日志 20 | fn log_event( 21 | event_type: String, 22 | grid_level: Int, 23 | order_type: String, 24 | order_price: Fixed, 25 | order_quantity: Fixed, 26 | extra_info: Optional[Dict[String, Args]] = None, 27 | ): 28 | log_itf["MAIN"]()[]._write_event_log( 29 | event_type=event_type, 30 | grid_level=grid_level, 31 | order_type=order_type, 32 | order_price=order_price, 33 | order_quantity=order_quantity, 34 | extra_info=extra_info, 35 | ) 36 | 37 | 38 | fn debug(message: String, context: Optional[Dict[String, Args]] = None): 39 | log_itf["MAIN"]()[].debug(message, context) 40 | # logd(message) 41 | 42 | 43 | fn info(message: String, context: Optional[Dict[String, Args]] = None): 44 | log_itf["MAIN"]()[].info(message, context) 45 | # logi(message) 46 | 47 | 48 | fn warn(message: String, context: Optional[Dict[String, Args]] = None): 49 | log_itf["MAIN"]()[].warn(message, context) 50 | # logw(message) 51 | 52 | 53 | fn error(message: String, context: Optional[Dict[String, Args]] = None): 54 | log_itf["MAIN"]()[].error(message, context) 55 | # loge(message) 56 | 57 | 58 | @value 59 | struct LogInterface: 60 | var algo_id: Int 61 | var q: UnsafePointer[LockfreeQueue] 62 | 63 | fn __init__(inout self): 64 | self.algo_id = 0 65 | self.q = lockfree_queue_itf[LOG_QUEUE]() 66 | 67 | fn set_alog_id(inout self, algo_id: Int): 68 | self.algo_id = algo_id 69 | 70 | fn debug( 71 | self, message: String, context: Optional[Dict[String, Args]] = None 72 | ): 73 | self._write_log("DEBUG", message, context) 74 | 75 | fn info( 76 | self, message: String, context: Optional[Dict[String, Args]] = None 77 | ): 78 | self._write_log("INFO", message, context) 79 | 80 | fn warn( 81 | self, message: String, context: Optional[Dict[String, Args]] = None 82 | ): 83 | self._write_log("WARNING", message, context) 84 | 85 | fn error( 86 | self, message: String, context: Optional[Dict[String, Args]] = None 87 | ): 88 | self._write_log("ERROR", message, context) 89 | 90 | fn _write_log( 91 | self, 92 | level: String, 93 | message: String, 94 | context: Optional[Dict[String, Args]] = None, 95 | ): 96 | if self.algo_id == 0: 97 | return 98 | 99 | try: 100 | var doc = SonicDocument() 101 | doc.set_object() 102 | var seq_id = int(seq_snowflake_id()) 103 | 104 | # 2024-03-27 14:10:05.034 105 | # var formatted_time = "2024-04-01 12:00:00.100" 106 | 107 | # print(s) 108 | doc.add_string("type", "algo_log") 109 | doc.add_int("algo_id", self.algo_id) 110 | doc.add_int("seq_id", seq_id) 111 | doc.add_int("timestamp", time_ns()) 112 | doc.add_string("logger", "trading_engine") 113 | doc.add_string("level", level) 114 | doc.add_string("message", message) 115 | var node = SonicNode(doc) 116 | node.set_object() 117 | # node.add_string("type", "BUY") 118 | if context: 119 | for e in context.value().items(): 120 | var value_ref = Reference(e[].value) 121 | if value_ref[].isa[String](): 122 | var value = value_ref[][String] 123 | node.add_string(e[].key, value) 124 | elif value_ref[].isa[Int](): 125 | var value = value_ref[][Int] 126 | node.add_int(e[].key, value) 127 | elif value_ref[].isa[Float64](): 128 | var value = value_ref[][Float64] 129 | node.add_float(e[].key, value) 130 | elif value_ref[].isa[Bool](): 131 | var value = value_ref[][Bool] 132 | node.add_bool(e[].key, value) 133 | doc.add_node("context", node) 134 | 135 | var doc_str = doc.to_string() 136 | # logi(doc_str) 137 | _ = self.q[].push(doc_str) 138 | # _ = redis.rpush("q_moxtflow_log", doc_str) 139 | except e: 140 | print(str(e)) 141 | 142 | fn _write_event_log( 143 | self, 144 | event_type: String, 145 | grid_level: Int, 146 | order_type: String, 147 | order_price: Fixed, 148 | order_quantity: Fixed, 149 | extra_info: Optional[Dict[String, Args]] = None, 150 | ): 151 | try: 152 | var seq_id = int(seq_snowflake_id()) 153 | 154 | var doc = SonicDocument() 155 | doc.set_object() 156 | doc.add_string("type", "event_log") 157 | doc.add_int("algo_id", self.algo_id) 158 | doc.add_int("seq_id", seq_id) 159 | doc.add_int("timestamp", time_ns()) 160 | doc.add_string("event_type", event_type) 161 | doc.add_int("grid_level", grid_level) 162 | doc.add_string("order_type", order_type) 163 | doc.add_string("order_price", str(order_price)) 164 | doc.add_string("order_quantity", str(order_quantity)) 165 | var extra_info_node = SonicNode(doc) 166 | extra_info_node.set_object() 167 | 168 | if extra_info: 169 | for e in extra_info.value().items(): 170 | var value_ref = Reference(e[].value) 171 | if value_ref[].isa[String](): 172 | var value = value_ref[][String] 173 | extra_info_node.add_string(e[].key, value) 174 | elif value_ref[].isa[Int](): 175 | var value = value_ref[][Int] 176 | extra_info_node.add_int(e[].key, value) 177 | elif value_ref[].isa[Float64](): 178 | var value = value_ref[][Float64] 179 | extra_info_node.add_float(e[].key, value) 180 | elif value_ref[].isa[Bool](): 181 | var value = value_ref[][Bool] 182 | extra_info_node.add_bool(e[].key, value) 183 | doc.add_node("extra_info", extra_info_node) 184 | 185 | var doc_str = doc.to_string() 186 | # logi(doc_str) 187 | _ = self.q[].push(doc_str) 188 | except e: 189 | print(str(e)) 190 | 191 | 192 | fn log_itf[name: StringLiteral]() -> UnsafePointer[LogInterface]: 193 | var ptr = _get_global["_LOG:" + name, _init_log, _destroy_log]() 194 | return ptr.bitcast[LogInterface]() 195 | 196 | 197 | fn _init_log(payload: UnsafePointer[NoneType]) -> UnsafePointer[NoneType]: 198 | var ptr = UnsafePointer[LogInterface].alloc(1) 199 | ptr.init_pointee_move(LogInterface()) 200 | return ptr.bitcast[NoneType]() 201 | 202 | 203 | fn _destroy_log(p: UnsafePointer[NoneType]): 204 | p.free() 205 | 206 | 207 | @value 208 | struct LogService: 209 | var redis: UnsafePointer[Redis] 210 | var q: UnsafePointer[LockfreeQueue] 211 | 212 | fn __init__(inout self): 213 | self.redis = UnsafePointer[Redis].alloc(1) 214 | self.q = lockfree_queue_itf[LOG_QUEUE]() 215 | 216 | fn init(inout self, host: String, port: Int, password: String, db: Int): 217 | logi( 218 | "init log service host=" 219 | + host 220 | + " port=" 221 | + str(port) 222 | + " db=" 223 | + str(db) 224 | ) 225 | logi("init log service password=" + password) 226 | self.redis.init_pointee_move(Redis(host, port, password, db, 3000)) 227 | 228 | fn perform(self) -> Int: 229 | var e = self.q[].pop() 230 | if e: 231 | var s = e.value() 232 | # logi("log perform s=" + s) 233 | _ = self.redis[].rpush("q_moxtflow_log", s) 234 | return 1 235 | else: 236 | return 0 237 | 238 | fn perform_all(self): 239 | while True: 240 | var n = self.perform() 241 | if n == 0: 242 | return 243 | 244 | 245 | fn log_service_itf() -> UnsafePointer[LogService]: 246 | var ptr = _get_global[ 247 | "_LOG_SERVICE", _init_log_service, _destroy_log_service 248 | ]() 249 | return ptr.bitcast[LogService]() 250 | 251 | 252 | fn _init_log_service( 253 | payload: UnsafePointer[NoneType], 254 | ) -> UnsafePointer[NoneType]: 255 | var ptr = UnsafePointer[LogService].alloc(1) 256 | # ptr[] = LogService() 257 | ptr.init_pointee_move(LogService()) 258 | return ptr.bitcast[NoneType]() 259 | 260 | 261 | fn _destroy_log_service(p: UnsafePointer[NoneType]): 262 | p.free() 263 | -------------------------------------------------------------------------------- /base/moutil.mojo: -------------------------------------------------------------------------------- 1 | from memory import unsafe 2 | import math 3 | import .c 4 | from .mo import * 5 | from ylstdlib.time import time_ns 6 | 7 | 8 | fn time_ms() -> Int64: 9 | return Int64(int(time_ns() / 1e6)) 10 | 11 | 12 | fn time_us() -> Int64: 13 | return Int64(int(time_ns() / 1e3)) 14 | 15 | 16 | fn set_global_value_ptr[V: Intable](id: Int, v: UnsafePointer[V]) -> Int: 17 | var ptr = int(v) 18 | seq_set_global_int(id, ptr) 19 | return ptr 20 | 21 | 22 | @always_inline 23 | fn strtoi(s: String) -> Int: 24 | return seq_strtoi(str_as_scalar_pointer(s), len(s)) 25 | 26 | 27 | @always_inline 28 | fn strtod(s: String) -> Float64: 29 | return seq_strtod(str_as_scalar_pointer(s), len(s)) 30 | 31 | 32 | fn str_to_bool(s: String) -> Bool: 33 | if s == "true" or s == "True": 34 | return True 35 | elif s == "false" or s == "False": 36 | return False 37 | else: 38 | return False 39 | 40 | 41 | fn decimal_places(value: Float64) -> Int: 42 | """ 43 | Return decimal places: 0.0001 -> 4 44 | """ 45 | if value == 0.0: 46 | return 0 47 | 48 | return int(math.ceil(math.log10(1.0 / value))) 49 | -------------------------------------------------------------------------------- /base/rate_limiter.mojo: -------------------------------------------------------------------------------- 1 | from .c import * 2 | from .mo import * 3 | 4 | 5 | fn seq_new_crate_limiter( 6 | max_count: c_int, window_size: UInt64 7 | ) -> c_void_pointer: 8 | return external_call[ 9 | "seq_new_crate_limiter", c_void_pointer, c_int, UInt64 10 | ](max_count, window_size) 11 | 12 | 13 | fn seq_crate_limiter_allow_and_record_request(ptr: c_void_pointer) -> Bool: 14 | return external_call[ 15 | "seq_crate_limiter_allow_and_record_request", Bool, c_void_pointer 16 | ](ptr) 17 | 18 | 19 | fn seq_delete_crate_limiter(ptr: c_void_pointer) -> None: 20 | external_call["seq_delete_crate_limiter", NoneType, c_void_pointer](ptr) 21 | 22 | 23 | @value 24 | struct RateLimiter: 25 | var _ptr: c_void_pointer 26 | 27 | fn __init__(inout self, max_count: Int, window_size: UInt64): 28 | self._ptr = seq_new_crate_limiter(max_count, window_size) 29 | 30 | fn __del__(owned self: Self): 31 | if self._ptr == c_void_pointer(): 32 | return 33 | seq_delete_crate_limiter(self._ptr) 34 | self._ptr = c_void_pointer() 35 | 36 | fn allow_and_record_request(self) -> Bool: 37 | return seq_crate_limiter_allow_and_record_request(self._ptr) 38 | -------------------------------------------------------------------------------- /base/redis.mojo: -------------------------------------------------------------------------------- 1 | from sys import external_call 2 | from os import getenv 3 | from .c import * 4 | 5 | 6 | @value 7 | struct Redis: 8 | var p: c_void_pointer 9 | 10 | fn __init__( 11 | inout self, 12 | host: String, 13 | port: Int, 14 | password: String, 15 | db: Int, 16 | timeout_ms: Int = 3 * 1000, 17 | ): 18 | self.p = seq_redis_new(host, port, password, db, timeout_ms) 19 | 20 | fn __del__(owned self): 21 | seq_redis_free(self.p) 22 | 23 | fn set(self, key: String, value: String) -> Bool: 24 | return seq_redis_set(self.p, key, value) 25 | 26 | fn get(self, key: String) -> String: 27 | return seq_redis_get(self.p, key) 28 | 29 | fn rpush(self, key: String, value: String) -> Int64: 30 | return seq_redis_rpush(self.p, key, value) 31 | 32 | 33 | fn test_redis() -> None: 34 | var password = getenv("REDIS_PASSWORD", "") 35 | var redis = Redis("1.94.26.93", 6379, password, 0, 3000) 36 | var ok = redis.set("test_0", "1") 37 | print(ok) 38 | var s = redis.get("test_0") 39 | print(s) 40 | 41 | 42 | fn test_redis_raw() -> None: 43 | var password = getenv("REDIS_PASSWORD", "") 44 | var redis = seq_redis_new("1.94.26.93", 6379, password, 0, 1000) 45 | var ok = seq_redis_set(redis, "test_0", "1") 46 | print(ok) 47 | var s = seq_redis_get(redis, "test_0") 48 | print(s) 49 | seq_redis_free(redis) 50 | 51 | 52 | fn seq_redis_new( 53 | host: String, port: Int, password: String, db: Int, timeout_ms: Int 54 | ) -> c_void_pointer: 55 | return __mlir_op.`pop.external_call`[ 56 | func = "seq_redis_new".value, _type=c_void_pointer 57 | ]( 58 | host._buffer.data, 59 | len(host), 60 | port, 61 | password._buffer.data, 62 | len(password), 63 | db, 64 | timeout_ms, 65 | ) 66 | 67 | 68 | fn seq_redis_set(redis: c_void_pointer, key: String, value: String) -> Bool: 69 | return external_call[ 70 | "seq_redis_set", 71 | Bool, 72 | c_void_pointer, 73 | c_char_pointer, 74 | c_size_t, 75 | c_char_pointer, 76 | c_size_t, 77 | ]( 78 | redis, 79 | str_as_scalar_pointer(key), 80 | len(key), 81 | str_as_scalar_pointer(value), 82 | len(value), 83 | ) 84 | 85 | 86 | fn seq_redis_get(redis: c_void_pointer, key: String) -> String: 87 | var value_data = UnsafePointer[Int8].alloc(1024) 88 | var value_len = c_size_t(0) 89 | var ok = external_call[ 90 | "seq_redis_get", 91 | Bool, 92 | c_void_pointer, 93 | c_char_pointer, 94 | c_size_t, 95 | c_char_pointer, 96 | UnsafePointer[c_size_t], 97 | ]( 98 | redis, 99 | str_as_scalar_pointer(key), 100 | len(key), 101 | value_data, 102 | UnsafePointer[c_size_t].address_of(value_len), 103 | ) 104 | if ok: 105 | var s = c_str_to_string(value_data, value_len) 106 | value_data.free() 107 | return s 108 | else: 109 | value_data.free() 110 | return "" 111 | 112 | 113 | fn seq_redis_rpush(redis: c_void_pointer, key: String, value: String) -> Int64: 114 | var result = external_call[ 115 | "seq_redis_rpush", 116 | Bool, 117 | c_void_pointer, 118 | c_char_pointer, 119 | c_size_t, 120 | c_char_pointer, 121 | c_size_t, 122 | ]( 123 | redis, 124 | str_as_scalar_pointer(key), 125 | len(key), 126 | str_as_scalar_pointer(value), 127 | len(value), 128 | ) 129 | return result 130 | 131 | 132 | fn seq_redis_free(redis: c_void_pointer) -> None: 133 | external_call["seq_redis_free", NoneType](redis) 134 | -------------------------------------------------------------------------------- /base/requests.mojo: -------------------------------------------------------------------------------- 1 | from collections.optional import Optional 2 | from .httpclient import ( 3 | HttpClient, 4 | VERB_GET, 5 | Headers, 6 | HttpResponse, 7 | ) 8 | 9 | 10 | struct requests: 11 | @staticmethod 12 | fn delete( 13 | base_url: String, path: String, inout headers: Headers 14 | ) -> HttpResponse: 15 | var client = HttpClient(base_url) 16 | var res = client.delete(path, headers) 17 | return res 18 | 19 | @staticmethod 20 | fn get( 21 | base_url: String, path: String, inout headers: Headers 22 | ) -> HttpResponse: 23 | var client = HttpClient(base_url) 24 | var res = client.get(path, headers) 25 | return res 26 | 27 | @staticmethod 28 | fn head( 29 | base_url: String, path: String, data: String, inout headers: Headers 30 | ) -> HttpResponse: 31 | var client = HttpClient(base_url) 32 | var res = client.head(path, data, headers) 33 | return res 34 | 35 | @staticmethod 36 | fn post( 37 | base_url: String, path: String, data: String, inout headers: Headers 38 | ) -> HttpResponse: 39 | var client = HttpClient(base_url) 40 | var res = client.post(path, data, headers) 41 | return res 42 | 43 | @staticmethod 44 | fn put( 45 | base_url: String, path: String, data: String, inout headers: Headers 46 | ) -> HttpResponse: 47 | var client = HttpClient(base_url) 48 | var res = client.put(path, data, headers) 49 | return res 50 | -------------------------------------------------------------------------------- /base/ssmap.mojo: -------------------------------------------------------------------------------- 1 | import sys 2 | from memory import UnsafePointer 3 | from .c import ( 4 | c_str_to_string, 5 | c_size_t, 6 | c_void_pointer, 7 | str_as_scalar_pointer, 8 | ) 9 | from .mo import ( 10 | seq_ssmap_new, 11 | seq_ssmap_free, 12 | seq_ssmap_get, 13 | seq_ssmap_set, 14 | seq_ssmap_size, 15 | logd, 16 | ) 17 | 18 | 19 | struct SSMap: 20 | var ptr: c_void_pointer 21 | 22 | fn __init__(inout self): 23 | self.ptr = seq_ssmap_new() 24 | 25 | fn __del__(owned self): 26 | seq_ssmap_free(self.ptr) 27 | 28 | fn __moveinit__(inout self, owned existing: Self): 29 | self.ptr = existing.ptr 30 | 31 | fn __setitem__(inout self, key: String, value: String): 32 | seq_ssmap_set( 33 | self.ptr, 34 | str_as_scalar_pointer(key), 35 | str_as_scalar_pointer(value), 36 | ) 37 | 38 | fn __getitem__(self, key: String) -> String: 39 | var n: c_size_t = 0 40 | var s = seq_ssmap_get( 41 | self.ptr, 42 | str_as_scalar_pointer(key), 43 | UnsafePointer[c_size_t].address_of(n), 44 | ) 45 | return c_str_to_string(s, n) 46 | 47 | fn __len__(self) -> Int: 48 | return seq_ssmap_size(self.ptr) 49 | -------------------------------------------------------------------------------- /base/str.mojo: -------------------------------------------------------------------------------- 1 | from memory import memcpy 2 | 3 | 4 | # @value 5 | # @register_passable 6 | # struct Str(Stringable): 7 | # """ 8 | # A string that is dodgy because it is not null-terminated. 9 | # https://github.com/igorgue/firedis/blob/main/dodgy.mojo 10 | # """ 11 | 12 | # var data: Pointer[Int8] 13 | # var size: Int 14 | 15 | # fn __init__(value: StringLiteral) -> Str: 16 | # var l = len(value) 17 | # var s = String(value) 18 | # var p = Pointer[Int8].alloc(l) 19 | 20 | # for i in range(l): 21 | # p.store(i, s._buffer[i]) 22 | 23 | # return Str(p, l) 24 | 25 | # fn __init__(value: String) -> Str: 26 | # var l = len(value) 27 | # var p = Pointer[Int8].alloc(l) 28 | 29 | # for i in range(l): 30 | # p.store(i, value._buffer[i]) 31 | 32 | # return Str(p, l) 33 | 34 | # fn __init__(value: StringRef) -> Str: 35 | # var l = len(value) 36 | # var s = String(value) 37 | # var p = Pointer[Int8].alloc(l) 38 | 39 | # for i in range(l): 40 | # p.store(i, s._buffer[i]) 41 | 42 | # return Str(p, l) 43 | 44 | # @always_inline("nodebug") 45 | # fn __del__(owned self: Self): 46 | # self.data.free() 47 | 48 | # fn __eq__(self, other: Self) -> Bool: 49 | # if self.size != other.size: 50 | # return False 51 | 52 | # for i in range(self.size): 53 | # if self.data.load(i) != other.data.load(i): 54 | # return False 55 | 56 | # return True 57 | 58 | # fn __ne__(self, other: Self) -> Bool: 59 | # return not self.__eq__(other) 60 | 61 | # fn to_string(self) -> String: 62 | # var ptr = Pointer[Int8]().alloc(self.size) 63 | # memcpy(ptr, self.data, self.size) 64 | # return String(ptr, self.size + 1) 65 | 66 | # fn __str__(self) -> String: 67 | # return self.to_string() 68 | -------------------------------------------------------------------------------- /base/str_cache.mojo: -------------------------------------------------------------------------------- 1 | from collections.list import List 2 | from .c import * 3 | from .mo import * 4 | 5 | 6 | fn seq_get_next_cache_key() -> Int64: 7 | return external_call[ 8 | "seq_get_next_cache_key", 9 | Int64, 10 | ]() 11 | 12 | 13 | fn seq_set_string_in_cache( 14 | key: Int64, value: c_char_pointer, value_len: c_size_t 15 | ) -> c_char_pointer: 16 | return external_call[ 17 | "seq_set_string_in_cache", 18 | c_char_pointer, 19 | Int64, 20 | c_char_pointer, 21 | c_size_t, 22 | ](key, value, value_len) 23 | 24 | 25 | fn seq_get_string_in_cache( 26 | key: Int64, result_len: UnsafePointer[c_size_t] 27 | ) -> c_char_pointer: 28 | return external_call[ 29 | "seq_get_string_in_cache", 30 | c_char_pointer, 31 | Int64, 32 | UnsafePointer[c_size_t], 33 | ](key, result_len) 34 | 35 | 36 | fn seq_free_string_in_cache(key: Int64) -> Bool: 37 | return external_call[ 38 | "seq_free_string_in_cache", 39 | Bool, 40 | Int64, 41 | ](key) 42 | 43 | 44 | @value 45 | struct StringCache: 46 | fn __init__(inout self): 47 | pass 48 | 49 | @staticmethod 50 | fn set_string(s: String) -> Tuple[Int64, CString]: 51 | var key = seq_get_next_cache_key() 52 | var length = len(s) 53 | var result = seq_set_string_in_cache( 54 | key, str_as_scalar_pointer(s), length 55 | ) 56 | return Tuple[Int64, CString](key, CString(result, length)) 57 | 58 | @staticmethod 59 | fn get_string(key: Int64) -> Tuple[Int64, CString]: 60 | var length: c_size_t = 0 61 | var result = seq_get_string_in_cache( 62 | key, UnsafePointer[c_size_t].address_of(length) 63 | ) 64 | return Tuple[Int64, CString](key, CString(result, length)) 65 | 66 | @staticmethod 67 | fn free_string(key: Int64) -> Bool: 68 | return seq_free_string_in_cache(key) 69 | 70 | 71 | @value 72 | @register_passable 73 | struct CString: 74 | var data: c_char_pointer 75 | var len: Int 76 | 77 | fn data_u8(self) -> UnsafePointer[SIMD[DType.uint8, 1]]: 78 | return rebind[ 79 | UnsafePointer[SIMD[DType.uint8, 1]], UnsafePointer[SIMD[DType.int8, 1]] 80 | ](self.data) 81 | 82 | 83 | @value 84 | struct MyStringCache: 85 | var _keys: List[Int64] 86 | 87 | fn __init__(inout self): 88 | self._keys = List[Int64]() 89 | 90 | fn __del__(owned self): 91 | for key in self._keys: 92 | _ = StringCache.free_string(key[]) 93 | 94 | fn set_string(inout self, s: String) -> CString: 95 | var r = StringCache.set_string(s) 96 | var key = r.get[0, Int64]() 97 | var result = r.get[1, CString]() 98 | self._keys.append(key) 99 | return result 100 | -------------------------------------------------------------------------------- /base/websocket.mojo: -------------------------------------------------------------------------------- 1 | from sys.ffi import _get_global 2 | from memory import UnsafePointer 3 | from .c import * 4 | from .mo import * 5 | from .moutil import * 6 | from collections.dict import Dict 7 | 8 | 9 | alias TLS1_1_VERSION = 0x0302 10 | alias TLS1_2_VERSION = 0x0303 11 | alias TLS1_3_VERSION = 0x0304 12 | 13 | 14 | alias on_connect_callback = fn () escaping -> None 15 | alias on_heartbeat_callback = fn () escaping -> None 16 | alias on_message_callback = fn (String) escaping -> None 17 | 18 | 19 | alias OnConnectCallbackHolder = Dict[Int, on_connect_callback] 20 | alias OnHeartbeatCallbackHolder = Dict[Int, on_heartbeat_callback] 21 | alias OnMessageCallbackHolder = Dict[Int, on_message_callback] 22 | 23 | 24 | fn ws_on_connect_holder_ptr() -> UnsafePointer[OnConnectCallbackHolder]: 25 | var ptr = _get_global[ 26 | "__ws_on_connect_holder", 27 | _init_ws_on_connect_holder, 28 | _destroy_ws_on_connect_holder, 29 | ]() 30 | return ptr.bitcast[OnConnectCallbackHolder]() 31 | 32 | 33 | fn _init_ws_on_connect_holder( 34 | payload: UnsafePointer[NoneType], 35 | ) -> UnsafePointer[NoneType]: 36 | var ptr = UnsafePointer[OnConnectCallbackHolder].alloc(1) 37 | ptr.init_pointee_move(OnConnectCallbackHolder()) 38 | return ptr.bitcast[NoneType]() 39 | 40 | 41 | fn _destroy_ws_on_connect_holder(p: UnsafePointer[NoneType]): 42 | p.free() 43 | 44 | 45 | fn ws_on_heartbeat_holder_ptr() -> UnsafePointer[OnHeartbeatCallbackHolder]: 46 | var ptr = _get_global[ 47 | "__ws_on_heartbeat_holder", 48 | _init_ws_on_heartbeat_holder, 49 | _destroy_ws_on_heartbeat_holder, 50 | ]() 51 | return ptr.bitcast[OnHeartbeatCallbackHolder]() 52 | 53 | 54 | fn _init_ws_on_heartbeat_holder( 55 | payload: UnsafePointer[NoneType], 56 | ) -> UnsafePointer[NoneType]: 57 | var ptr = UnsafePointer[OnHeartbeatCallbackHolder].alloc(1) 58 | ptr.init_pointee_move(OnHeartbeatCallbackHolder()) 59 | return ptr.bitcast[NoneType]() 60 | 61 | 62 | fn _destroy_ws_on_heartbeat_holder(p: UnsafePointer[NoneType]): 63 | p.free() 64 | 65 | 66 | fn ws_on_message_holder_ptr() -> UnsafePointer[OnMessageCallbackHolder]: 67 | var ptr = _get_global[ 68 | "__moc", _init_ws_on_message_holder, _destroy_ws_on_message_holder 69 | ]() 70 | return ptr.bitcast[OnMessageCallbackHolder]() 71 | 72 | 73 | fn _init_ws_on_message_holder( 74 | payload: UnsafePointer[NoneType], 75 | ) -> UnsafePointer[NoneType]: 76 | var ptr = UnsafePointer[OnMessageCallbackHolder].alloc(1) 77 | ptr.init_pointee_move(OnMessageCallbackHolder()) 78 | return ptr.bitcast[NoneType]() 79 | 80 | 81 | fn _destroy_ws_on_message_holder(p: UnsafePointer[NoneType]): 82 | p.free() 83 | 84 | 85 | fn set_on_connect(id: Int, owned callback: on_connect_callback) -> None: 86 | if id == 0: 87 | return 88 | ws_on_connect_holder_ptr()[][id] = callback^ 89 | 90 | 91 | fn set_on_heartbeat(id: Int, owned callback: on_heartbeat_callback) -> None: 92 | if id == 0: 93 | return 94 | ws_on_heartbeat_holder_ptr()[][id] = callback^ 95 | 96 | 97 | fn set_on_message(id: Int, owned callback: on_message_callback) -> None: 98 | if id == 0: 99 | return 100 | ws_on_message_holder_ptr()[][id] = callback^ 101 | 102 | 103 | fn emit_on_connect(id: Int) -> None: 104 | try: 105 | ws_on_connect_holder_ptr()[][id]() 106 | except e: 107 | pass 108 | 109 | 110 | fn emit_on_heartbeat(id: Int) -> None: 111 | try: 112 | ws_on_heartbeat_holder_ptr()[][id]() 113 | except e: 114 | pass 115 | 116 | 117 | fn emit_on_message(id: Int, data: c_char_pointer, data_len: c_size_t) -> None: 118 | try: 119 | var msg = c_str_to_string(data, data_len) 120 | ws_on_message_holder_ptr()[][id](msg) 121 | msg._strref_keepalive() 122 | except e: 123 | pass 124 | 125 | 126 | struct WebSocket: 127 | var _ptr: c_void_pointer 128 | var _id: Int 129 | 130 | fn __init__( 131 | inout self, 132 | host: String, 133 | port: String, 134 | path: String, 135 | tls_version: Int = TLS1_3_VERSION, 136 | ) raises: 137 | var host_ = to_schar_ptr(host) 138 | var port_ = to_schar_ptr(port) 139 | var path_ = to_schar_ptr(path) 140 | var ptr = seq_websocket_new( 141 | host_, 142 | port_, 143 | path_, 144 | tls_version, 145 | ) 146 | host_.free() 147 | port_.free() 148 | path_.free() 149 | register_websocket(ptr) 150 | logd("ws._ptr=" + str(seq_voidptr_to_int(ptr))) 151 | self._ptr = ptr 152 | self._id = seq_voidptr_to_int(ptr) 153 | 154 | fn c_ptr(self) -> c_void_pointer: 155 | return self._ptr 156 | 157 | fn get_id(self) -> Int: 158 | return self._id 159 | 160 | fn get_on_connect(self) -> on_connect_callback: 161 | var self_ptr = UnsafePointer.address_of(self) 162 | 163 | fn wrapper(): 164 | self_ptr[].on_connect() 165 | 166 | return wrapper 167 | 168 | fn get_on_heartbeat(self) -> on_heartbeat_callback: 169 | var self_ptr = UnsafePointer.address_of(self) 170 | 171 | fn wrapper(): 172 | self_ptr[].on_heartbeat() 173 | 174 | return wrapper 175 | 176 | fn get_on_message(self) -> on_message_callback: 177 | var self_ptr = UnsafePointer.address_of(self) 178 | 179 | fn wrapper(msg: String): 180 | self_ptr[].on_message(msg) 181 | 182 | return wrapper 183 | 184 | fn on_connect(self) -> None: 185 | logd("WebSocket.on_connect") 186 | 187 | fn on_heartbeat(self) -> None: 188 | logd("WebSocket.on_heartbeat") 189 | 190 | fn on_message(self, msg: String) -> None: 191 | logd("WebSocket::on_message: " + msg) 192 | 193 | fn release(self) -> None: 194 | seq_websocket_delete(self._ptr) 195 | 196 | fn connect(self): 197 | seq_websocket_connect(self._ptr) 198 | 199 | fn close(self): 200 | # seq_websocket_close(self.ws) 201 | pass 202 | 203 | fn send(self, text: String) -> None: 204 | seq_websocket_send( 205 | self._ptr, 206 | str_as_scalar_pointer(text), 207 | len(text), 208 | ) 209 | 210 | fn __repr__(self) -> String: 211 | return "" 212 | 213 | 214 | fn websocket_connect_callback(ws: c_void_pointer) raises -> None: 215 | logi("websocket_connect_callback 100") 216 | var id = seq_voidptr_to_int(ws) 217 | emit_on_connect(id) 218 | logi("websocket_connect_callback done") 219 | 220 | 221 | fn websocket_heartbeat_callback(ws: c_void_pointer) raises -> None: 222 | # logi("websocket_heartbeat_callback") 223 | var id = seq_voidptr_to_int(ws) 224 | emit_on_heartbeat(id) 225 | # logi("websocket_heartbeat_callback done") 226 | 227 | 228 | fn websocket_message_callback( 229 | ws: c_void_pointer, data: c_char_pointer, data_len: c_size_t 230 | ) raises -> None: 231 | # logi("websocket_message_callback") 232 | var id = seq_voidptr_to_int(ws) 233 | emit_on_message(id, data, data_len) 234 | # logi("websocket_message_callback done") 235 | 236 | 237 | fn register_websocket(ws: c_void_pointer) -> None: 238 | seq_websocket_set_on_connected(ws, websocket_connect_callback) 239 | seq_websocket_set_on_heartbeat(ws, websocket_heartbeat_callback) 240 | seq_websocket_set_on_message(ws, websocket_message_callback) 241 | -------------------------------------------------------------------------------- /base/yyjson.mojo: -------------------------------------------------------------------------------- 1 | import time 2 | import sys 3 | import os 4 | from memory import unsafe 5 | from collections.list import List 6 | from .str_cache import * 7 | from .c import * 8 | from .mo import * 9 | from .yyjsonbase import * 10 | 11 | 12 | @value 13 | struct yyjson_mut_doc: 14 | var doc: c_void_pointer 15 | var root: c_void_pointer 16 | var _sc: MyStringCache 17 | 18 | @always_inline 19 | fn __init__(inout self): 20 | self.doc = seq_yyjson_mut_doc_new(UnsafePointer[UInt8]()) 21 | self.root = seq_yyjson_mut_obj(self.doc) 22 | seq_yyjson_mut_doc_set_root(self.doc, self.root) 23 | self._sc = MyStringCache() 24 | 25 | @always_inline 26 | fn __del__(owned self): 27 | seq_yyjson_mut_doc_free(self.doc) 28 | 29 | @always_inline 30 | fn add_str(inout self, key: StringLiteral, value: String): 31 | var v = self._sc.set_string(value) 32 | _ = seq_yyjson_mut_obj_add_strn( 33 | self.doc, 34 | self.root, 35 | str_as_scalar_pointer(key), 36 | v.data, 37 | v.len, 38 | ) 39 | 40 | @always_inline 41 | fn add_int(inout self, key: StringLiteral, value: Int): 42 | _ = seq_yyjson_mut_obj_add_int( 43 | self.doc, 44 | self.root, 45 | str_as_scalar_pointer(key), 46 | value, 47 | ) 48 | 49 | @always_inline 50 | fn add_float(inout self, key: StringLiteral, value: Float64): 51 | _ = seq_yyjson_mut_obj_add_real( 52 | self.doc, 53 | self.root, 54 | str_as_scalar_pointer(key), 55 | value, 56 | ) 57 | 58 | @always_inline 59 | fn add_bool(inout self, key: StringLiteral, value: Bool): 60 | _ = seq_yyjson_mut_obj_add_bool( 61 | self.doc, 62 | self.root, 63 | str_as_scalar_pointer(key), 64 | value, 65 | ) 66 | 67 | @always_inline 68 | fn arr_with_bool(inout self, key: StringLiteral, value: List[Bool]) raises: 69 | var n = len(value) 70 | var vp = UnsafePointer[Bool].alloc(n) 71 | for i in range(0, n): 72 | vp[i] = value[i] 73 | var harr = seq_yyjson_mut_arr_with_bool(self.doc, vp, n) 74 | _ = seq_yyjson_mut_obj_add_val( 75 | self.doc, 76 | self.root, 77 | str_as_scalar_pointer(key), 78 | harr, 79 | ) 80 | vp.free() 81 | 82 | @always_inline 83 | fn arr_with_float( 84 | inout self, key: StringLiteral, value: List[Float64] 85 | ) raises: 86 | var n = len(value) 87 | var vp = UnsafePointer[Float64].alloc(n) 88 | for i in range(0, n): 89 | vp[i] = value[i] 90 | var harr = seq_yyjson_mut_arr_with_real(self.doc, vp, n) 91 | _ = seq_yyjson_mut_obj_add_val( 92 | self.doc, 93 | self.root, 94 | str_as_scalar_pointer(key), 95 | harr, 96 | ) 97 | vp.free() 98 | 99 | @always_inline 100 | fn arr_with_int(inout self, key: StringLiteral, value: List[Int]) raises: 101 | var n = len(value) 102 | var vp = UnsafePointer[Int].alloc(n) 103 | for i in range(0, n): 104 | vp[i] = value[i] 105 | var harr = seq_yyjson_mut_arr_with_sint64(self.doc, vp, n) 106 | _ = seq_yyjson_mut_obj_add_val( 107 | self.doc, 108 | self.root, 109 | str_as_scalar_pointer(key), 110 | harr, 111 | ) 112 | vp.free() 113 | 114 | @always_inline 115 | fn arr_with_str(inout self, key: StringLiteral, value: List[String]) raises: 116 | var n = len(value) 117 | var vp = UnsafePointer[c_char_pointer].alloc(n) 118 | for i in range(0, n): 119 | var v = self._sc.set_string(value[i]) 120 | vp[i] = v.data 121 | var harr = seq_yyjson_mut_arr_with_str(self.doc, vp, n) 122 | _ = seq_yyjson_mut_obj_add_val( 123 | self.doc, 124 | self.root, 125 | str_as_scalar_pointer(key), 126 | harr, 127 | ) 128 | vp.free() 129 | 130 | @always_inline 131 | fn mut_write(self) -> String: 132 | var pLen: Int = 0 133 | var json_cstr = seq_yyjson_mut_write( 134 | self.doc, YYJSON_WRITE_NOFLAG, UnsafePointer[Int].address_of(pLen) 135 | ) 136 | # return c_str_to_string(json_cstr, pLen) 137 | return String(json_cstr.bitcast[UInt8](), pLen + 1) 138 | 139 | fn __repr__(self) -> String: 140 | return "" 141 | 142 | 143 | @value 144 | @register_passable 145 | struct yyjson_val(CollectionElement): 146 | var p: c_void_pointer 147 | 148 | fn __init__(inout self, p: c_void_pointer): 149 | self.p = p 150 | 151 | @always_inline 152 | fn __getitem__(self, key: String) -> yyjson_val: 153 | return yyjson_val( 154 | seq_yyjson_obj_getn(self.p, str_as_scalar_pointer(key), len(key)) 155 | ) 156 | 157 | fn __bool__(self) -> Bool: 158 | return self.p == c_void_pointer() 159 | 160 | @always_inline 161 | fn type(self) -> Int: 162 | return seq_yyjson_get_type(self.p) 163 | 164 | @always_inline 165 | fn type_desc(self) -> String: 166 | return c_str_to_string_raw(seq_yyjson_get_type_desc(self.p)) 167 | 168 | @always_inline 169 | fn str(self) -> String: 170 | var s = seq_yyjson_get_str(self.p) 171 | return c_str_to_string_raw(s) 172 | 173 | @always_inline 174 | fn safe_str(self) -> String: 175 | if not self: 176 | return "" 177 | return c_str_to_string_raw(seq_yyjson_get_str(self.p)) 178 | 179 | @always_inline 180 | fn uint(self) -> Int: 181 | return seq_yyjson_get_uint(self.p) 182 | 183 | @always_inline 184 | fn int(self) -> Int: 185 | return seq_yyjson_get_int(self.p) 186 | 187 | @always_inline 188 | fn float(self) -> Float64: 189 | return seq_yyjson_get_real(self.p) 190 | 191 | @always_inline 192 | fn bool(self) -> Bool: 193 | return seq_yyjson_get_bool(self.p) 194 | 195 | @always_inline 196 | fn raw(self) -> String: 197 | return c_str_to_string_raw(seq_yyjson_get_raw(self.p)) 198 | 199 | @always_inline 200 | fn object(self, key: StringLiteral) -> yyjson_val: 201 | return yyjson_val( 202 | seq_yyjson_obj_getn(self.p, str_as_scalar_pointer(key), len(key)) 203 | ) 204 | 205 | @always_inline 206 | fn arr_size(self) -> Int: 207 | return seq_yyjson_arr_size(self.p) 208 | 209 | @always_inline 210 | fn array_list(self) -> List[yyjson_val]: 211 | var res = List[yyjson_val]() 212 | var idx: Int = 0 213 | var max: Int = seq_yyjson_arr_size(self.p) 214 | var val = seq_yyjson_arr_get_first(self.p) 215 | while idx < max: 216 | res.append(yyjson_val(val)) 217 | idx += 1 218 | val = seq_unsafe_yyjson_get_next(val) 219 | return res 220 | 221 | fn __repr__(self) -> String: 222 | return "" 223 | 224 | 225 | @value 226 | struct yyjson_doc: 227 | var doc: c_void_pointer 228 | 229 | fn __init__(inout self, s: String, read_insitu: Bool = False): 230 | var flg = YYJSON_READ_INSITU if read_insitu else 0 231 | self.doc = seq_yyjson_read( 232 | s.unsafe_cstr_ptr().bitcast[UInt8](), len(s), flg 233 | ) 234 | 235 | @always_inline 236 | fn __del__(owned self): 237 | seq_yyjson_doc_free(self.doc) 238 | 239 | @always_inline 240 | fn root(self) -> yyjson_val: 241 | return yyjson_val(seq_yyjson_doc_get_root(self.doc)) 242 | 243 | @always_inline 244 | fn get_read_size(self) -> Int: 245 | return seq_yyjson_doc_get_read_size(self.doc) 246 | 247 | @always_inline 248 | fn get_val_count(self) -> Int: 249 | return seq_yyjson_doc_get_val_count(self.doc) 250 | 251 | fn __repr__(self) -> String: 252 | return "" 253 | -------------------------------------------------------------------------------- /build-all.sh: -------------------------------------------------------------------------------- 1 | ./build.sh test_skiplist.mojo -o test_skillist 2 | ./build.sh test_all.mojo -o test_all 3 | ./build.sh test_trade.mojo -o test_trade 4 | ./build.sh test_bybit.mojo -o test_bybit 5 | ./build.sh test_binance.mojo -o test_binance 6 | ./build.sh main.mojo -o moxt 7 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define the default output filename variable 4 | output="" 5 | 6 | # Parse input arguments 7 | while [[ $# -gt 0 ]]; do 8 | case $1 in 9 | -o) 10 | # Get the output filename argument 11 | output="$2" 12 | shift 2 13 | ;; 14 | *) 15 | # Get the input mojo filename 16 | input="$1" 17 | shift 18 | ;; 19 | esac 20 | done 21 | 22 | # If no output filename is specified, set it to the input filename by default 23 | if [ -z "$output" ]; then 24 | output="${input%.*}" 25 | fi 26 | 27 | # Construct mojoc command 28 | cmd="./scripts/mojoc $input -lmoxt -L . -o $output" 29 | 30 | # Print the command to be executed 31 | echo "Executing command: $cmd" 32 | 33 | # Execute the command 34 | $cmd 35 | 36 | # Check return status 37 | if [ $? -eq 0 ]; then 38 | echo "Command executed successfully" 39 | else 40 | echo "Command execution failed" 41 | fi -------------------------------------------------------------------------------- /config.example.toml: -------------------------------------------------------------------------------- 1 | testnet = true 2 | access_key = "" 3 | secret_key = "" 4 | category = "linear" 5 | symbols = "BTCUSDT" 6 | depth = 1 7 | is_local_based = true 8 | 9 | [strategy] 10 | name = "SmartGridStrategy" 11 | 12 | [params] 13 | grid_interval = 0.01 14 | order_qty = 0.001 15 | # Set overall stop-loss percentage 16 | total_sl_percent = -0.1 17 | # Set individual grid stop-loss percentage 18 | cell_sl_percent = -0.05 19 | -------------------------------------------------------------------------------- /copy-libmoxt.sh: -------------------------------------------------------------------------------- 1 | cp ~/cpp/moxt-cpp/build/linux/x86_64/release/libmoxt.so ./ -------------------------------------------------------------------------------- /core/__init__.mojo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f0cii/moxt/dee9018e4367dad3d9cfc600804f9b3d14493beb/core/__init__.mojo -------------------------------------------------------------------------------- /core/binancemodel.mojo: -------------------------------------------------------------------------------- 1 | from base.fixed import Fixed 2 | 3 | 4 | @value 5 | struct OrderInfo(Stringable, CollectionElement): 6 | """ 7 | { 8 | "orderId": 237740210409, 9 | "symbol": "BTCUSDT", 10 | "status": "NEW", 11 | "clientOrderId": "62ayQ4MjyVIaCkvDX00dhh", 12 | "price": "20000.00", 13 | "avgPrice": "0.00", 14 | "origQty": "0.010", 15 | "executedQty": "0.000", 16 | "cumQty": "0.000", 17 | "cumQuote": "0.00000", 18 | "timeInForce": "GTC", 19 | "type": "LIMIT", 20 | "reduceOnly": false, 21 | "closePosition": false, 22 | "side": "BUY", 23 | "positionSide": "LONG", 24 | "stopPrice": "0.00", 25 | "workingType": "CONTRACT_PRICE", 26 | "priceProtect": false, 27 | "origType": "LIMIT", 28 | "priceMatch": "NONE", 29 | "selfTradePreventionMode": "NONE", 30 | "goodTillDate": 0, 31 | "updateTime": 1704291033033 32 | } 33 | status: NEW, PARTIALLY_FILLED, FILLED, CANCELED, REJECTED, EXPIRED, EXPIRED_IN_MATCH 34 | """ 35 | 36 | var order_id: Int # orderId 37 | var symbol: String # BTCUSDT 38 | var status: String # orderStatus 39 | var order_client_id: String # clientOrderId 40 | var price: String # price 41 | var avg_price: String # avgPrice 42 | var orig_qty: String # origQty 43 | var executed_qty: String # executedQty 44 | var cum_qty: String # cumQty 45 | var cum_quote: String # cumQuote 46 | var time_in_force: String # timeInForce 47 | var type_: String # type LIMIT 48 | var reduce_only: Bool # reduceOnly 49 | var close_position: Bool # closePosition 50 | var side: String # BUY/SELL 51 | var position_side: String # positionSide LONG 52 | var stop_price: String # stopPrice 53 | var working_type: String # workingType CONTRACT_PRICE 54 | var price_protect: Bool # priceProtect 55 | var orig_type: String # origType LIMIT 56 | var price_match: String # priceMatch NONE 57 | var self_trade_prevention_mode: String # selfTradePreventionMode NONE 58 | var good_till_date: Int # goodTillDate 59 | var update_time: Int # updateTime 60 | 61 | fn __init__(inout self): 62 | self.order_id = 0 63 | self.symbol = "" 64 | self.status = "" 65 | self.order_client_id = "" 66 | self.price = "" 67 | self.avg_price = "" 68 | self.orig_qty = "" 69 | self.executed_qty = "" 70 | self.cum_qty = "" 71 | self.cum_quote = "" 72 | self.time_in_force = "" 73 | self.type_ = "" 74 | self.reduce_only = False 75 | self.close_position = False 76 | self.side = "" 77 | self.position_side = "" 78 | self.stop_price = "" 79 | self.working_type = "" 80 | self.price_protect = False 81 | self.orig_type = "" 82 | self.price_match = "" 83 | self.self_trade_prevention_mode = "" 84 | self.good_till_date = 0 85 | self.update_time = 0 86 | 87 | fn __str__(self) -> String: 88 | return ( 89 | "" 107 | ) 108 | -------------------------------------------------------------------------------- /core/binancews.mojo: -------------------------------------------------------------------------------- 1 | from ylstdlib.time import time_ns 2 | from base.globals import * 3 | from base.websocket import * 4 | from base.yyjson import yyjson_doc, yyjson_mut_doc 5 | from base.sj_ondemand import OndemandParser 6 | from base.websocket import ( 7 | on_connect_callback, 8 | on_heartbeat_callback, 9 | on_message_callback, 10 | ) 11 | from .sign import hmac_sha256_hex 12 | from .binanceclient import BinanceClient 13 | 14 | 15 | alias ParserBufferSize = 1000 * 100 16 | 17 | 18 | struct BinanceWS: 19 | """ 20 | Reference document: https://binance-docs.github.io/apidocs/futures/en/ 21 | """ 22 | 23 | var _ptr: c_void_pointer 24 | var _id: Int 25 | var _is_private: Bool 26 | var _access_key: String 27 | var _secret_key: String 28 | var _topics_str: String 29 | var _heartbeat_time: UnsafePointer[Int64] 30 | var _client: BinanceClient 31 | 32 | fn __init__( 33 | inout self, 34 | is_private: Bool, 35 | testnet: Bool, 36 | access_key: String, 37 | secret_key: String, 38 | topics: String = "", 39 | ) raises: 40 | self._is_private = is_private 41 | self._access_key = access_key 42 | self._secret_key = secret_key 43 | self._topics_str = topics 44 | self._client = BinanceClient(testnet, access_key, secret_key) 45 | 46 | var host: String = "stream.binancefuture.com" if testnet else "fstream.binance.com" 47 | var port: String = "443" 48 | var path: String = "" 49 | 50 | if is_private: 51 | var listen_key = self._client.generate_listen_key() 52 | logi("listen_key=" + listen_key) 53 | path = "/ws/" + listen_key 54 | else: 55 | path = "/stream?streams=" + topics 56 | logd( 57 | "websocket wss://" 58 | + host 59 | + ":" 60 | + port 61 | + path 62 | + " isPrivate=" 63 | + str(is_private) 64 | ) 65 | 66 | var host_ = to_schar_ptr(host) 67 | var port_ = to_schar_ptr(port) 68 | var path_ = to_schar_ptr(path) 69 | var ptr = seq_websocket_new( 70 | host_, 71 | port_, 72 | path_, 73 | TLS1_3_VERSION, 74 | ) 75 | host_.free() 76 | port_.free() 77 | path_.free() 78 | register_websocket(ptr) 79 | self._ptr = ptr 80 | self._id = seq_voidptr_to_int(ptr) 81 | self._heartbeat_time = UnsafePointer[Int64].alloc(1) 82 | self._heartbeat_time[0] = 0 83 | 84 | fn __del__(owned self): 85 | print("BinanceWS.__del__") 86 | 87 | fn get_id(self) -> Int: 88 | return self._id 89 | 90 | fn set_on_connect(self, owned callback: on_connect_callback): 91 | var id = self.get_id() 92 | set_on_connect(id, callback^) 93 | 94 | fn set_on_heartbeat(self, owned callback: on_heartbeat_callback): 95 | var id = self.get_id() 96 | set_on_heartbeat(id, callback^) 97 | 98 | fn set_on_message(self, owned callback: on_message_callback): 99 | var id = self.get_id() 100 | set_on_message(id, callback^) 101 | 102 | fn subscribe(self): 103 | logd("BinanceWS.subscribe") 104 | 105 | fn get_on_connect(self) -> on_connect_callback: 106 | fn wrapper(): 107 | pass 108 | 109 | return wrapper 110 | 111 | fn get_on_heartbeat(self) -> on_heartbeat_callback: 112 | fn wrapper(): 113 | pass 114 | 115 | return wrapper 116 | 117 | fn on_connect(self) -> None: 118 | logd("BinanceWS.on_connect") 119 | self._heartbeat_time[0] = time_ms() 120 | if self._is_private: 121 | pass 122 | else: 123 | self.subscribe() 124 | 125 | fn on_heartbeat(self) -> None: 126 | # logd("BinanceWS.on_heartbeat") 127 | var elapsed_time = time_ms() - self._heartbeat_time[0] 128 | if elapsed_time <= 1000 * 60 * 5: 129 | # logd("BinanceWS.on_heartbeat ignore [" + str(elapsed_time) + "]") 130 | return 131 | 132 | # For private subscriptions, listen_key renewal needs to be done within 60 minutes 133 | if self._is_private: 134 | try: 135 | var ret = self._client.extend_listen_key() 136 | logi("Renewal of listen_key returns: " + str(ret)) 137 | self._heartbeat_time[0] = time_ms() 138 | except err: 139 | loge("Renewal of listen_key encountered an error: " + str(err)) 140 | 141 | fn on_message(self, s: String) -> None: 142 | logd("BinanceWS::on_message: " + s) 143 | 144 | # {"e":"ORDER_TRADE_UPDATE","T":1704459987707,"E":1704459987709,"o":{"s":"BTCUSDT","c":"web_w4Sot5R1ym9ChzWfGdAm","S":"BUY","o":"LIMIT","f":"GTC","q":"0.010","p":"20000","ap":"0","sp":"0","x":"NEW","X":"NEW","i":238950797096,"l":"0","z":"0","L":"0","n":"0","N":"USDT","T":1704459987707,"t":0,"b":"200","a":"0","m":false,"R":false,"wt":"CONTRACT_PRICE","ot":"LIMIT","ps":"LONG","cp":false,"rp":"0","pP":false,"si":0,"ss":0,"V":"NONE","pm":"NONE","gtd":0}} 145 | # {"e":"ORDER_TRADE_UPDATE","T":1704460185744,"E":1704460185746,"o":{"s":"BTCUSDT","c":"web_w4Sot5R1ym9ChzWfGdAm","S":"BUY","o":"LIMIT","f":"GTC","q":"0.010","p":"20000","ap":"0","sp":"0","x":"CANCELED","X":"CANCELED","i":238950797096,"l":"0","z":"0","L":"0","n":"0","N":"USDT","T":1704460185744,"t":0,"b":"0","a":"0","m":false,"R":false,"wt":"CONTRACT_PRICE","ot":"LIMIT","ps":"LONG","cp":false,"rp":"0","pP":false,"si":0,"ss":0,"V":"NONE","pm":"NONE","gtd":0}} 146 | 147 | # var parser = OndemandParser(ParserBufferSize) 148 | # var doc = parser.parse(s) 149 | 150 | # _ = doc ^ 151 | # _ = parser ^ 152 | 153 | fn release(self) -> None: 154 | seq_websocket_delete(self._ptr) 155 | 156 | fn send(self, text: String) -> None: 157 | seq_websocket_send(self._ptr, str_as_scalar_pointer(text), len(text)) 158 | 159 | fn connect(self): 160 | seq_websocket_connect(self._ptr) 161 | -------------------------------------------------------------------------------- /core/bybitclientjson.mojo: -------------------------------------------------------------------------------- 1 | from base.sj_ondemand import OndemandParser 2 | from base.sj_dom import DomParser 3 | from base.c import * 4 | from base.mo import * 5 | from base.moutil import * 6 | from base.httpclient import * 7 | 8 | from base.yyjson import yyjson_doc, yyjson_mut_doc 9 | from .bybitmodel import ( 10 | ServerTime, 11 | ExchangeInfo, 12 | KlineItem, 13 | OrderBookItem, 14 | OrderBook, 15 | OrderResponse, 16 | BalanceInfo, 17 | OrderInfo, 18 | ) 19 | from .sign import hmac_sha256_b64 20 | from ylstdlib.time import time_ns 21 | 22 | 23 | fn test_json_parse() raises: 24 | var body = String( 25 | '{"retCode":0,"retMsg":"OK","result":{"category":"linear","list":[{"symbol":"BTCUSDT","contractType":"LinearPerpetual","status":"Trading","baseCoin":"BTC","quoteCoin":"USDT","launchTime":"1585526400000","deliveryTime":"0","deliveryFeeRate":"","priceScale":"2","leverageFilter":{"minLeverage":"1","maxLeverage":"100.00","leverageStep":"0.01"},"priceFilter":{"minPrice":"0.10","maxPrice":"199999.80","tickSize":"0.10"},"lotSizeFilter":{"maxOrderQty":"100.000","minOrderQty":"0.001","qtyStep":"0.001","postOnlyMaxOrderQty":"1000.000"},"unifiedMarginTrade":true,"fundingInterval":480,"settleCoin":"USDT","copyTrading":"normalOnly"}],"nextPageCursor":""},"retExtInfo":{},"time":1696236288675}' 26 | ) 27 | 28 | var tick_size: Float64 = 0 29 | var stepSize: Float64 = 0 30 | 31 | var od_parser = OndemandParser(1000 * 100) 32 | var doc = od_parser.parse(body) 33 | var ret_code = doc.get_int("retCode") 34 | var ret_msg = doc.get_str("retMsg") 35 | if ret_code != 0: 36 | raise "error retCode=" + String(ret_code) + ", retMsg=" + String( 37 | ret_msg 38 | ) 39 | 40 | var result = doc.get_object("result") 41 | var result_list = result.get_array("list") 42 | 43 | if result_list.__len__() == 0: 44 | raise "error list length is 0" 45 | 46 | var list_iter = result_list.iter() 47 | while list_iter.has_value(): 48 | var obj = list_iter.get() 49 | var symbol_ = obj.get_str("symbol") 50 | 51 | var priceFilter = obj.get_object("priceFilter") 52 | var tick_size = strtod(priceFilter.get_str("tickSize")) 53 | var lotSizeFilter = obj.get_object("lotSizeFilter") 54 | var stepSize = strtod(lotSizeFilter.get_str("qtyStep")) 55 | 56 | logi("stepSize: " + String(stepSize)) 57 | _ = obj 58 | 59 | list_iter.step() 60 | 61 | _ = list_iter 62 | _ = result_list 63 | _ = result 64 | _ = doc 65 | _ = od_parser 66 | 67 | 68 | fn test_parse_fetch_kline_body() raises: 69 | var body = String( 70 | '{"retCode":0,"retMsg":"OK","result":{"symbol":"BTCUSDT","category":"linear","list":[["1687589640000","30709.9","30710.4","30709.9","30710.3","3.655","112245.7381"],["1687589580000","30707.9","30710","30704.7","30709.9","21.984","675041.8648"],["1687589520000","30708","30714.7","30705","30707.9","33.378","1025097.6459"],["1687589460000","30689.9","30710.3","30689.9","30708","51.984","1595858.2778"],["1687589400000","30678.6","30690.9","30678.5","30689.9","38.747","1188886.4093"]]},"retExtInfo":{},"time":1687589659062}' 71 | ) 72 | 73 | var res = List[KlineItem]() 74 | var dom_parser = DomParser(1000 * 100) 75 | var doc = dom_parser.parse(body) 76 | var ret_code = doc.get_int("retCode") 77 | var ret_msg = doc.get_str("retMsg") 78 | if ret_code != 0: 79 | raise "error retCode=" + str(ret_code) + ", retMsg=" + ret_msg 80 | 81 | var result = doc.get_object("result") 82 | var result_list = result.get_array("list") 83 | 84 | var list_iter = result_list.iter() 85 | 86 | while list_iter.has_element(): 87 | var obj = list_iter.get() 88 | var i_arr_list = obj.array() 89 | 90 | var timestamp = strtoi(i_arr_list.at_str(0)) 91 | var open_ = strtod(i_arr_list.at_str(1)) 92 | var high = strtod(i_arr_list.at_str(2)) 93 | var low = strtod(i_arr_list.at_str(3)) 94 | var close = strtod(i_arr_list.at_str(4)) 95 | var volume = strtod(i_arr_list.at_str(5)) 96 | var turnover = strtod(i_arr_list.at_str(6)) 97 | 98 | res.append( 99 | KlineItem( 100 | timestamp=timestamp, 101 | open=open_, 102 | high=high, 103 | low=low, 104 | close=close, 105 | volume=volume, 106 | turnover=turnover, 107 | confirm=True, 108 | ) 109 | ) 110 | 111 | list_iter.step() 112 | 113 | _ = doc 114 | _ = dom_parser 115 | 116 | for index in range(len(res)): 117 | var item = res[index] 118 | logi(str(item)) 119 | 120 | 121 | fn test_orderbook_parse_body() raises: 122 | var body = String( 123 | '{"result":{"a":[["30604.8","174.267"],["30648.6","0.002"],["30649.1","0.001"],["30650","1.119"],["30650.3","0.01"],["30650.8","0.001"],["30651.6","0.001"],["30652","0.001"],["30652.4","0.062"],["30652.5","0.001"]],"b":[["30598.7","142.31"],["30578.2","0.004"],["30575.3","0.001"],["30571.8","0.001"],["30571.1","0.002"],["30568.5","0.002"],["30566.6","0.005"],["30565.6","0.01"],["30565.5","0.061"],["30563","0.001"]],"s":"BTCUSDT","ts":1689132447413,"u":5223166},"retCode":0,"retExtInfo":{},"retMsg":"OK","time":1689132448224}' 124 | ) 125 | 126 | var asks = List[OrderBookItem]() 127 | var bids = List[OrderBookItem]() 128 | 129 | var dom_parser = DomParser(1000 * 100) 130 | var doc = dom_parser.parse(body) 131 | var ret_code = doc.get_int("retCode") 132 | var ret_msg = doc.get_str("retMsg") 133 | if ret_code != 0: 134 | raise "error retCode=" + str(ret_code) + ", retMsg=" + ret_msg 135 | 136 | var result = doc.get_object("result") 137 | var a_list = result.get_array("a") 138 | 139 | var list_iter_a = a_list.iter() 140 | 141 | while list_iter_a.has_element(): 142 | var obj = list_iter_a.get() 143 | var i_arr_list = obj.array() 144 | 145 | var price = strtod(i_arr_list.at_str(0)) 146 | var qty = strtod(i_arr_list.at_str(1)) 147 | 148 | asks.append(OrderBookItem(price, qty)) 149 | 150 | list_iter_a.step() 151 | 152 | var b_list = result.get_array("b") 153 | 154 | var list_iter_b = b_list.iter() 155 | 156 | while list_iter_b.has_element(): 157 | var obj = list_iter_b.get() 158 | var i_arr_list = obj.array() 159 | 160 | var price = strtod(i_arr_list.at_str(0)) 161 | var qty = strtod(i_arr_list.at_str(1)) 162 | 163 | bids.append(OrderBookItem(price, qty)) 164 | 165 | list_iter_b.step() 166 | 167 | var ob = OrderBook(asks, bids) 168 | 169 | _ = doc 170 | _ = dom_parser 171 | 172 | logi("-----asks-----") 173 | for index in range(len(ob.asks)): 174 | var item = ob.asks[index] 175 | logi(str(item)) 176 | 177 | logi("-----bids-----") 178 | for index in range(len(ob.bids)): 179 | var item = ob.bids[index] 180 | logi(str(item)) 181 | 182 | 183 | fn test_fetch_balance_parse_body() raises: 184 | var body = String( 185 | '{"retCode":0,"retMsg":"OK","result":{"list":[{"accountType":"CONTRACT","accountIMRate":"","accountMMRate":"","totalEquity":"","totalWalletBalance":"","totalMarginBalance":"","totalAvailableBalance":"","totalPerpUPL":"","totalInitialMargin":"","totalMaintenanceMargin":"","accountLTV":"","coin":[{"coin":"USDT","equity":"100.0954887","usdValue":"","walletBalance":"100.0954887","borrowAmount":"","availableToBorrow":"","availableToWithdraw":"100.0954887","accruedInterest":"","totalOrderIM":"0","totalPositionIM":"0","totalPositionMM":"","unrealisedPnl":"0","cumRealisedPnl":"1.0954887"}]}]},"retExtInfo":{},"time":1701876547097}' 186 | ) 187 | 188 | var coin = "USDT" 189 | 190 | var parser = OndemandParser(1000 * 100) 191 | var doc = parser.parse(body) 192 | 193 | var ret_code = doc.get_int("retCode") 194 | var ret_msg = doc.get_str("retMsg") 195 | if ret_code != 0: 196 | raise Error("error retCode=" + str(ret_code) + ", retMsg=" + ret_msg) 197 | 198 | var result_list = doc.get_object("result").get_array("list") 199 | var list_iter = result_list.iter() 200 | 201 | while list_iter.has_value(): 202 | var obj = list_iter.get() 203 | var account_type = obj.get_str("accountType") 204 | logi("account_type=" + account_type) 205 | if account_type == "CONTRACT": 206 | var coin_list = obj.get_array("coin") 207 | var coin_iter = coin_list.iter() 208 | while coin_iter.has_value(): 209 | var coin_obj = coin_iter.get() 210 | var coin_name = coin_obj.get_str("coin") 211 | logi("coin_name: " + coin_name) 212 | if coin_name != coin: 213 | continue 214 | var equity = strtod(coin_obj.get_str("equity")) 215 | var available_to_withdraw = strtod( 216 | coin_obj.get_str("availableToWithdraw") 217 | ) 218 | var wallet_balance = strtod(coin_obj.get_str("walletBalance")) 219 | var total_order_im = strtod(coin_obj.get_str("totalOrderIM")) 220 | var total_position_im = strtod( 221 | coin_obj.get_str("totalPositionIM") 222 | ) 223 | coin_iter.step() 224 | elif account_type == "SPOT": 225 | pass 226 | 227 | list_iter.step() 228 | 229 | _ = doc 230 | _ = parser 231 | 232 | 233 | fn test_fetch_orders_body_parse() raises: 234 | var body = String( 235 | '{"retCode":0,"retMsg":"OK","result":{"list":[],"nextPageCursor":"","category":"linear"},"retExtInfo":{},"time":1702103872882}' 236 | ) 237 | var res = List[OrderInfo]() 238 | logd("300000") 239 | 240 | var parser = DomParser(1024 * 64) 241 | var doc = parser.parse(body) 242 | 243 | var ret_code = doc.get_int("retCode") 244 | var ret_msg = doc.get_str("retMsg") 245 | if ret_code != 0: 246 | raise Error("error retCode=" + str(ret_code) + ", retMsg=" + ret_msg) 247 | 248 | var result = doc.get_object("result") 249 | var result_list = result.get_array("list") 250 | 251 | var list_iter = result_list.iter() 252 | 253 | while list_iter.has_element(): 254 | var i = list_iter.get() 255 | var position_idx = i.get_int("positionIdx") 256 | var order_id = i.get_str("orderId") 257 | var _symbol = i.get_str("symbol") 258 | var side = i.get_str("side") 259 | var order_type = i.get_str("orderType") 260 | var price = strtod(i.get_str("price")) 261 | var qty = strtod(i.get_str("qty")) 262 | var cum_exec_qty = strtod(i.get_str("cumExecQty")) 263 | var order_status = i.get_str("orderStatus") 264 | var created_time = i.get_str("createdTime") 265 | var updated_time = i.get_str("updatedTime") 266 | var avg_price = strtod(i.get_str("avgPrice")) 267 | var cum_exec_fee = strtod(i.get_str("cumExecFee")) 268 | var time_in_force = i.get_str("timeInForce") 269 | var reduce_only = i.get_bool("reduceOnly") 270 | var order_link_id = i.get_str("orderLinkId") 271 | 272 | res.append( 273 | OrderInfo( 274 | position_idx=position_idx, 275 | order_id=order_id, 276 | symbol=_symbol, 277 | side=side, 278 | type_=order_type, 279 | price=price, 280 | qty=qty, 281 | cum_exec_qty=cum_exec_qty, 282 | status=order_status, 283 | created_time=created_time, 284 | updated_time=updated_time, 285 | avg_price=avg_price, 286 | cum_exec_fee=cum_exec_fee, 287 | time_in_force=time_in_force, 288 | reduce_only=reduce_only, 289 | order_link_id=order_link_id, 290 | ) 291 | ) 292 | 293 | list_iter.step() 294 | 295 | _ = result 296 | _ = result_list 297 | _ = doc 298 | _ = parser 299 | 300 | for index in range(len(res)): 301 | var item = res[index] 302 | logi(str(item)) 303 | 304 | logi("OK") 305 | -------------------------------------------------------------------------------- /core/env.mojo: -------------------------------------------------------------------------------- 1 | from collections.dict import Dict 2 | from pathlib.path import Path 3 | 4 | 5 | fn env_loads(s: String) raises -> Dict[String, String]: 6 | var lines = s.split("\n") 7 | 8 | var env_dict = Dict[String, String]() 9 | for i in range(len(lines)): 10 | var line = lines[i] 11 | if "=" not in line: 12 | continue 13 | var l_list = line.split("=") 14 | if len(l_list) != 2: 15 | continue 16 | var key = l_list[0] 17 | var value = l_list[1] 18 | env_dict[key] = value 19 | 20 | return env_dict 21 | 22 | 23 | fn env_load(filename: String = ".env") raises -> Dict[String, String]: 24 | var env_file = Path(filename) 25 | var text = env_file.read_text() 26 | return env_loads(text) 27 | -------------------------------------------------------------------------------- /core/models.mojo: -------------------------------------------------------------------------------- 1 | from base.fixed import Fixed 2 | 3 | 4 | @value 5 | struct PositionInfo(Stringable, CollectionElement): 6 | var position_idx: Int 7 | # riskId: Int 8 | var symbol: String 9 | var side: String # "None" 10 | var size: String 11 | var avg_price: String 12 | var position_value: String 13 | # tradeMode: Int # 0 14 | # positionStatus: String # Normal 15 | # autoAddMargin: Int # 0 16 | # adlRankIndicator: Int # 0 17 | var leverage: Float64 # 1 18 | # positionBalance: String # 0 19 | var mark_price: String # 26515.73 20 | # liq_price: String # "" 21 | # bust_price: String # "0.00" 22 | var position_mm: String # "0" 23 | var position_im: String # "0" 24 | # tpslMode: String # "Full" 25 | var take_profit: String # "0.00" 26 | var stop_loss: String # "0.00" 27 | # trailingStop: String # "0.00" 28 | var unrealised_pnl: String # "0" 29 | var cum_realised_pnl: String # "-19.59637027" 30 | # seq: Int # 8172241025 31 | var created_time: String # "1682125794703" 32 | var updated_time: String # "updatedTime" 33 | 34 | fn __init__(inout self): 35 | self.position_idx = 0 36 | self.symbol = "" 37 | self.side = "" 38 | self.size = "" 39 | self.avg_price = "" 40 | self.position_value = "" 41 | self.leverage = 1 42 | self.mark_price = "" 43 | self.position_mm = "" 44 | self.position_im = "" 45 | self.take_profit = "" 46 | self.stop_loss = "" 47 | self.unrealised_pnl = "" 48 | self.cum_realised_pnl = "" 49 | self.created_time = "" 50 | self.updated_time = "" 51 | 52 | fn __init__( 53 | inout self, 54 | position_idx: Int, 55 | symbol: String, 56 | side: String, 57 | size: String, 58 | avg_price: String, 59 | position_value: String, 60 | leverage: Float64, 61 | mark_price: String, 62 | position_mm: String, 63 | position_im: String, 64 | take_profit: String, 65 | stop_loss: String, 66 | unrealised_pnl: String, 67 | cum_realised_pnl: String, 68 | created_time: String, 69 | updated_time: String, 70 | ): 71 | self.position_idx = position_idx 72 | self.symbol = symbol 73 | self.side = side 74 | self.size = size 75 | self.avg_price = avg_price 76 | self.position_value = position_value 77 | self.leverage = leverage 78 | self.mark_price = mark_price 79 | self.position_mm = position_mm 80 | self.position_im = position_im 81 | self.take_profit = take_profit 82 | self.stop_loss = stop_loss 83 | self.unrealised_pnl = unrealised_pnl 84 | self.cum_realised_pnl = cum_realised_pnl 85 | self.created_time = created_time 86 | self.updated_time = updated_time 87 | 88 | fn __str__(self) -> String: 89 | return ( 90 | "" 123 | ) 124 | 125 | 126 | @value 127 | @register_passable 128 | struct OrderBookLevel(CollectionElement): 129 | var price: Fixed 130 | var qty: Fixed 131 | 132 | 133 | @value 134 | struct OrderBookLite: 135 | var symbol: String 136 | var asks: List[OrderBookLevel] 137 | var bids: List[OrderBookLevel] 138 | 139 | fn __init__(inout self, symbol: String = ""): 140 | self.symbol = symbol 141 | self.asks = List[OrderBookLevel]() 142 | self.bids = List[OrderBookLevel]() -------------------------------------------------------------------------------- /core/sign.mojo: -------------------------------------------------------------------------------- 1 | from base.c import * 2 | from base.mo import * 3 | 4 | 5 | fn hmac_sha256_b64(message: String, secret_key: String) -> String: 6 | var b_ptr = UnsafePointer[UInt8].alloc(32) 7 | var secret_key_cstr = to_schar_ptr(secret_key) 8 | var message_cstr = to_schar_ptr(message) 9 | var n = seq_hmac_sha256( 10 | secret_key_cstr, 11 | len(secret_key), 12 | message_cstr, 13 | len(message), 14 | b_ptr, 15 | ) 16 | var s_ptr = UnsafePointer[UInt8].alloc(50) 17 | var s_len = seq_base64_encode(b_ptr, n, s_ptr) 18 | var s = c_str_to_string(s_ptr, s_len) 19 | 20 | b_ptr.free() 21 | s_ptr.free() 22 | secret_key_cstr.free() 23 | message_cstr.free() 24 | 25 | return s 26 | 27 | 28 | fn hmac_sha256_hex(message: String, secret_key: String) -> String: 29 | var b_ptr = UnsafePointer[UInt8].alloc(32) 30 | var secret_key_cstr = to_schar_ptr(secret_key) 31 | var message_cstr = to_schar_ptr(message) 32 | var n = seq_hmac_sha256( 33 | secret_key_cstr, 34 | len(secret_key), 35 | message_cstr, 36 | len(message), 37 | b_ptr, 38 | ) 39 | var s_ptr = UnsafePointer[UInt8].alloc(80) 40 | var s_len = seq_hex_encode(b_ptr, n, s_ptr) 41 | var s = c_str_to_string(s_ptr, s_len) 42 | 43 | b_ptr.free() 44 | s_ptr.free() 45 | secret_key_cstr.free() 46 | message_cstr.free() 47 | 48 | return s 49 | -------------------------------------------------------------------------------- /download_libmoxt.sh: -------------------------------------------------------------------------------- 1 | # Use GitHub API to get the latest release information 2 | LATEST_RELEASE_INFO=$(curl -s https://api.github.com/repos/f0cii/moxt-cpp/releases/latest) 3 | 4 | # Parse the assets array and extract the file name that matches the pattern (for example, files ending with .so) 5 | FILE_NAME=$(echo "$LATEST_RELEASE_INFO" | jq -r '.assets[] | .name | select(endswith(".so"))') 6 | 7 | echo "File Name: $FILE_NAME" 8 | 9 | # Directly parse the assets array and extract the download URL corresponding to the file name 10 | DOWNLOAD_URL=$(echo "$LATEST_RELEASE_INFO" | jq -r ".assets[] | select(.name == \"$FILE_NAME\") | .browser_download_url") 11 | 12 | echo "Download URL: $DOWNLOAD_URL" 13 | 14 | # Use curl to download the file, save it as libmoxt.so 15 | curl -L -o libmoxt.so $DOWNLOAD_URL 16 | 17 | # Or use wget to download the file, save it as libmoxt.so 18 | # wget -O libmoxt.so $DOWNLOAD_URL -------------------------------------------------------------------------------- /experiments/test_0001.mojo: -------------------------------------------------------------------------------- 1 | alias a_fun = fn () -> None 2 | 3 | 4 | fn e_do() -> None: 5 | print("e_do") 6 | 7 | 8 | fn e_do1() -> None: 9 | print("e_do1") 10 | 11 | 12 | struct Wrap: 13 | var f: a_fun 14 | 15 | fn __init__(inout self): 16 | self.f = e_do 17 | 18 | fn set_f(inout self, f: a_fun): 19 | self.f = f 20 | print("set_f") 21 | 22 | fn do(self) -> None: 23 | print("do") 24 | self.f() 25 | print("done") 26 | 27 | 28 | var w = Wrap() 29 | 30 | 31 | fn do_it() -> None: 32 | w.do() 33 | 34 | 35 | # var do_func_ptr: a_fun = e_do() 36 | 37 | 38 | fn test_h(): 39 | w.set_f(e_do1) 40 | do_it() 41 | # print("11000") 42 | # do_func_ptr() 43 | print("11001") 44 | 45 | 46 | fn main(): 47 | # test_h() 48 | -------------------------------------------------------------------------------- /experiments/test_global_values.mojo: -------------------------------------------------------------------------------- 1 | # https://github.com/modularml/mojo/issues/1393 2 | 3 | var glob = True 4 | var gstr = String("hello") 5 | 6 | 7 | fn get_glob() -> Bool: 8 | return glob 9 | 10 | 11 | fn set_glob(v: Bool) -> None: 12 | glob = v 13 | 14 | 15 | fn get_gstr() -> String: 16 | let a = gstr 17 | print(a) 18 | return a 19 | 20 | 21 | fn set_gstr(v: String) -> None: 22 | gstr = v 23 | 24 | 25 | fn main(): 26 | set_glob(True) 27 | set_glob(False) 28 | print(get_glob()) 29 | # set_gstr("hello f0cci") 30 | let s = get_gstr() 31 | print("10000") 32 | print(s) 33 | -------------------------------------------------------------------------------- /experiments/test_rebind.mojo: -------------------------------------------------------------------------------- 1 | fn main(): 2 | var a: Int = 100 3 | var b = rebind[Int64, Int](a) 4 | print(b) 5 | -------------------------------------------------------------------------------- /experiments/test_tuple.mojo: -------------------------------------------------------------------------------- 1 | fn main(): 2 | # test Tuple 3 | var t = Tuple[Int, StringLiteral](199, "b") 4 | var a_i = t.get[0, Int]() 5 | print("a_i: " + str(a_i)) 6 | 7 | var a = Tuple[Int, Int](3, 5) 8 | var a_value = a.get[0, Int]() 9 | print(a_value) 10 | -------------------------------------------------------------------------------- /experiments/test_v0_6_1.mojo: -------------------------------------------------------------------------------- 1 | from memory import memcpy, memset_zero 2 | 3 | 4 | alias any_pointer_simd_int8_to_pointer_simd_int8 = rebind[ 5 | Pointer[SIMD[DType.int8, 1]], AnyPointer[SIMD[DType.int8, 1]] 6 | ] 7 | 8 | 9 | fn to_string_ref(s: String) -> StringRef: 10 | # var data = rebind[Pointer[SIMD[DType.int8, 1]], AnyPointer[SIMD[DType.int8, 1]]](s._buffer.data) 11 | # var data = any_pointer_simd_int8_to_pointer_simd_int8(s._buffer.data) 12 | var data_len = len(s) 13 | var ptr = Pointer[Int8]().alloc(data_len + 1) 14 | 15 | # memcpy(ptr, data.bitcast[Int8](), data_len) 16 | memcpy(ptr, s._buffer.data.value, data_len) 17 | memset_zero(ptr.offset(data_len), 1) 18 | 19 | return StringRef(ptr.bitcast[__mlir_type.`!pop.scalar`]().address, data_len) 20 | 21 | 22 | # var a = String("hello") 23 | 24 | 25 | @value 26 | struct BoxedInt(Stringable): 27 | var value: Int 28 | 29 | fn __str__(self) -> String: 30 | return self.value 31 | 32 | 33 | fn main(): 34 | # print(a[0]) 35 | 36 | print(BoxedInt(46)) 37 | 38 | var s = String("hello") 39 | print(s) 40 | 41 | var s_ref = to_string_ref(s) 42 | print(s_ref) 43 | 44 | print(s + 100) 45 | 46 | var f: Float64 = 100.1 47 | var i = int(f) 48 | var i0 = int(Float32(100.1)) 49 | var i1 = int(Int8(10)) 50 | var i2 = int(Int16(10)) 51 | var i3 = int(Int32(10)) 52 | var i4 = int(Int64(10)) 53 | var i5 = int(UInt8(10)) 54 | var i6 = int(UInt16(10)) 55 | var i7 = int(UInt32(10)) 56 | var i8 = int(UInt64(10)) 57 | 58 | var vec = List[Bool]() 59 | var vec0 = List[StringLiteral]() 60 | var vec1 = List[Int]() 61 | var vec2 = List[StringRef]() 62 | var vec3 = List[String]() 63 | var vec4 = List[Float32]() 64 | var vec5 = List[Float64]() 65 | var vec6 = List[Int16]() 66 | var vec7 = List[Int32]() 67 | var vec8 = List[Int8]() 68 | var vec9 = List[Int16]() 69 | -------------------------------------------------------------------------------- /main.mojo: -------------------------------------------------------------------------------- 1 | from sys import argv 2 | import sys 3 | from sys.param_env import is_defined, env_get_int, env_get_string 4 | import time 5 | from base.globals import _GLOBAL 6 | from trade.config import * 7 | from strategies.runner import * 8 | 9 | 10 | fn print_usage(): 11 | print("Usage: ./moxt [options]") 12 | print("Example: ./moxt -v") 13 | print("Options:") 14 | print(" -log-level Set the logging level (default: INFO)") 15 | print(" -log-file Set the log file name (default: app.log)") 16 | print( 17 | " -c Set the configuration file name (default: config.toml)" 18 | ) 19 | 20 | 21 | fn main() raises: 22 | var host = String("") 23 | var token = String("") 24 | var id = String("") 25 | var sid = String("") 26 | var algo = String("") 27 | var log_level = String("DBG") # Default log level set to "INF" for "Info" 28 | var log_file = String( 29 | "" 30 | ) # Initialize log file path as empty, meaning logging to stdout by default 31 | var config_file = String("config.toml") # Default configuration file name 32 | var test = False 33 | 34 | @parameter 35 | fn argparse() raises -> Int: 36 | var args = argv() 37 | for i in range(1, len(args), 2): 38 | if args[i] == "-host": 39 | host = args[i + 1] 40 | if args[i] == "-token": 41 | token = args[i + 1] 42 | if args[i] == "-id": 43 | id = args[i + 1] 44 | if args[i] == "-sid": 45 | sid = args[i + 1] 46 | if args[i] == "-algo": 47 | algo = args[i + 1] 48 | if args[i] == "-log-level": 49 | log_level = args[i + 1] 50 | if args[i] == "-log-file": 51 | log_file = args[i + 1] 52 | if args[i] == "-c": 53 | config_file = args[i + 1] 54 | if args[i] == "-test": 55 | test = True 56 | if args[i] == "-v": 57 | return 2 58 | if args[i] == "-h": 59 | print_usage() 60 | return 0 61 | return 1 62 | 63 | var res = argparse() 64 | if res == 0: 65 | return 66 | if test: 67 | return 68 | if res == 2: 69 | return 70 | 71 | # print("log_level=" + log_level) 72 | # print(config_file) 73 | 74 | _ = seq_ct_init() 75 | var ret = seq_photon_init_default() 76 | # seq_init_photon_work_pool(1) 77 | 78 | init_log(log_level, log_file) 79 | 80 | # seq_init_net(0) 81 | # seq_init_net(1) 82 | 83 | logi("Initialization return result: " + str(ret)) 84 | 85 | alias SIGINT = 2 86 | seq_register_signal_handler(SIGINT, signal_handler) 87 | 88 | if host != "" and token != "": 89 | # "http://1.94.26.93" 90 | var backend_url = "http://" + host if "http://" not in host else host 91 | logi(backend_url) 92 | var bi = BackendInteractor(backend_url) 93 | # var username = "" 94 | # var password = "" 95 | # var login_res = bi.login(username, password) 96 | # var bot_id = id # "2m3snT6YMHo" 97 | # var token = "" 98 | 99 | logi("token=" + token) 100 | logi("id=" + id) 101 | logi("sid=" + sid) 102 | logi("algo=" + algo) 103 | 104 | var res = bi.get_bot_params(sid, token) 105 | print(res) 106 | var strategy_param = parse_strategy_param(res) 107 | 108 | var app_config = parse_strategy_param_to_app_config(strategy_param) 109 | app_config.strategy = algo 110 | logi("Load configuration information: " + str(app_config)) 111 | 112 | var g = _GLOBAL() 113 | g[].current_strategy = app_config.strategy 114 | g[].algo_id = atol(sid) 115 | 116 | # 初始化日志 117 | var key = "0c6d6db61905400fee1f39e7fa26be87" 118 | var redis_pass = twofish_decrypt(strategy_param.redis.password, key) 119 | logi("redis_pass=" + redis_pass) 120 | log.log_service_itf()[].init( 121 | strategy_param.redis.host, 122 | strategy_param.redis.port, 123 | redis_pass, 124 | strategy_param.redis.db, 125 | ) 126 | 127 | run_strategy(app_config) 128 | else: 129 | var app_config: AppConfig = AppConfig() 130 | app_config = load_config(config_file) 131 | 132 | logi("Load configuration information: " + str(app_config)) 133 | 134 | var g = _GLOBAL() 135 | g[].current_strategy = app_config.strategy 136 | g[].algo_id = 0 137 | 138 | run_strategy(app_config) 139 | -------------------------------------------------------------------------------- /mojo.Dockerfile: -------------------------------------------------------------------------------- 1 | # Reference: 2 | # https://github.com/modularml/mojo/blob/main/examples/docker/Dockerfile.mojosdk 3 | 4 | # Use the base image based on Ubuntu 22.04 5 | FROM ubuntu:22.04 6 | 7 | # Set the default timezone argument 8 | ARG DEFAULT_TZ=Asia/Shanghai 9 | ENV DEFAULT_TZ=$DEFAULT_TZ 10 | 11 | # Update package list and install basic utilities 12 | RUN apt-get update \ 13 | && DEBIAN_FRONTEND=noninteractive TZ=$DEFAULT_TZ apt-get install -y \ 14 | tzdata \ 15 | vim \ 16 | nano \ 17 | sudo \ 18 | curl \ 19 | wget \ 20 | git && \ 21 | rm -rf /var/lib/apt/lists/* 22 | 23 | # Download the latest version of minicoda py3.11 for linux x86/x64. 24 | RUN curl -fsSL https://repo.anaconda.com/miniconda/$( wget -O - https://repo.anaconda.com/miniconda/ 2>/dev/null | grep -o 'Miniconda3-py311_[^"]*-Linux-x86_64.sh' | head -n 1) > /tmp/miniconda.sh \ 25 | && chmod +x /tmp/miniconda.sh \ 26 | && /tmp/miniconda.sh -b -p /opt/conda 27 | 28 | ENV PATH=/opt/conda/bin:$PATH 29 | RUN conda init 30 | 31 | RUN pip install \ 32 | jupyterlab \ 33 | ipykernel \ 34 | matplotlib \ 35 | ipywidgets 36 | 37 | # A random default token 38 | ARG AUTH_KEY=5ca1ab1e 39 | ENV AUTH_KEY=$AUTH_KEY 40 | 41 | RUN curl https://get.modular.com | sh - && \ 42 | modular auth $AUTH_KEY 43 | RUN modular install mojo 44 | 45 | ARG MODULAR_HOME="/root/.modular" 46 | ENV MODULAR_HOME=$MODULAR_HOME 47 | ENV PATH="$PATH:$MODULAR_HOME/pkg/packages.modular.com_mojo/bin" 48 | 49 | # Change permissions to allow for Apptainer/Singularity containers 50 | RUN chmod -R a+rwX /root 51 | 52 | RUN jupyter labextension disable "@jupyterlab/apputils-extension:announcements" 53 | # CMD ["jupyter", "lab", "--ip='*'", "--NotebookApp.token=''", "--NotebookApp.password=''","--allow-root"] 54 | 55 | # Set the container startup command 56 | CMD ["/bin/bash"] 57 | -------------------------------------------------------------------------------- /mojoproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | authors = ["yl <2201441955@qq.com>"] 3 | channels = ["conda-forge", "https://conda.modular.com/max-nightly"] 4 | description = "Add a short description here" 5 | name = "moxt" 6 | platforms = ["linux-64"] 7 | version = "0.1.0" 8 | 9 | [tasks] 10 | 11 | [dependencies] 12 | max = ">=24.6.0.dev2024101817,<25" 13 | -------------------------------------------------------------------------------- /morrow/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .morrow import Morrow 2 | from .timezone import TimeZone 3 | from .timedelta import TimeDelta 4 | 5 | alias __version__ = "0.3.1" 6 | -------------------------------------------------------------------------------- /morrow/_libc.mojo: -------------------------------------------------------------------------------- 1 | alias c_void = UInt8 2 | alias c_char = UInt8 3 | alias c_schar = Int8 4 | alias c_uchar = UInt8 5 | alias c_short = Int16 6 | alias c_ushort = UInt16 7 | alias c_int = Int32 8 | alias c_uint = UInt32 9 | alias c_long = Int64 10 | alias c_ulong = UInt64 11 | alias c_float = Float32 12 | alias c_double = Float64 13 | 14 | 15 | @value 16 | @register_passable("trivial") 17 | struct CTimeval: 18 | var tv_sec: Int # Seconds 19 | var tv_usec: Int # Microseconds 20 | 21 | fn __init__(inout self, tv_sec: Int = 0, tv_usec: Int = 0): 22 | self.tv_sec = tv_sec 23 | self.tv_usec = tv_usec 24 | 25 | 26 | @value 27 | @register_passable("trivial") 28 | struct CTm: 29 | var tm_sec: Int32 # Seconds 30 | var tm_min: Int32 # Minutes 31 | var tm_hour: Int32 # Hour 32 | var tm_mday: Int32 # Day of the month 33 | var tm_mon: Int32 # Month 34 | var tm_year: Int32 # Year minus 1900 35 | var tm_wday: Int32 # Day of the week 36 | var tm_yday: Int32 # Day of the year 37 | var tm_isdst: Int32 # Daylight savings flag 38 | var tm_gmtoff: Int64 # localtime zone offset seconds 39 | 40 | fn __init__(inout self): 41 | self.tm_sec = 0 42 | self.tm_min = 0 43 | self.tm_hour = 0 44 | self.tm_mday = 0 45 | self.tm_mon = 0 46 | self.tm_year = 0 47 | self.tm_wday = 0 48 | self.tm_yday = 0 49 | self.tm_isdst = 0 50 | self.tm_gmtoff = 0 51 | 52 | 53 | @always_inline 54 | fn c_gettimeofday() -> CTimeval: 55 | var tv = CTimeval() 56 | var p_tv = UnsafePointer[CTimeval].address_of(tv) 57 | external_call["gettimeofday", NoneType, UnsafePointer[CTimeval], Int32]( 58 | p_tv, 0 59 | ) 60 | return tv 61 | 62 | 63 | @always_inline 64 | fn c_localtime(owned tv_sec: Int) -> CTm: 65 | var p_tv_sec = UnsafePointer[Int].address_of(tv_sec) 66 | var tm = external_call["localtime", UnsafePointer[CTm], UnsafePointer[Int]]( 67 | p_tv_sec 68 | )[0] 69 | return tm 70 | 71 | 72 | @always_inline 73 | fn c_strptime(time_str: String, time_format: String) -> CTm: 74 | var tm = CTm() 75 | var p_tm = UnsafePointer[CTm].address_of(tm) 76 | external_call[ 77 | "strptime", 78 | NoneType, 79 | UnsafePointer[c_char], 80 | UnsafePointer[c_char], 81 | UnsafePointer[CTm], 82 | ](to_char_ptr(time_str), to_char_ptr(time_format), p_tm) 83 | return tm 84 | 85 | 86 | @always_inline 87 | fn c_gmtime(owned tv_sec: Int) -> CTm: 88 | var p_tv_sec = UnsafePointer[Int].address_of(tv_sec) 89 | var tm = external_call["gmtime", UnsafePointer[CTm], UnsafePointer[Int]]( 90 | p_tv_sec 91 | )[0] 92 | return tm 93 | 94 | 95 | fn to_char_ptr(s: String) -> UnsafePointer[c_char]: 96 | """Only ASCII-based strings.""" 97 | var ptr = UnsafePointer[c_char]().alloc(len(s)) 98 | for i in range(len(s)): 99 | ptr[i] = ord(s[i]) 100 | return ptr 101 | -------------------------------------------------------------------------------- /morrow/_py.mojo: -------------------------------------------------------------------------------- 1 | from python import Python 2 | 3 | 4 | fn py_dt_datetime() raises -> PythonObject: 5 | var _datetime = Python.import_module("datetime") 6 | return _datetime.datetime 7 | 8 | 9 | fn py_time() raises -> PythonObject: 10 | var _time = Python.import_module("time") 11 | return _time 12 | -------------------------------------------------------------------------------- /morrow/constants.mojo: -------------------------------------------------------------------------------- 1 | from utils import StaticTuple 2 | 3 | # todo: hardcode for tmp 4 | alias _MAX_TIMESTAMP: Int = 32503737600 5 | alias MAX_TIMESTAMP = _MAX_TIMESTAMP 6 | alias MAX_TIMESTAMP_MS = MAX_TIMESTAMP * 1000 7 | alias MAX_TIMESTAMP_US = MAX_TIMESTAMP * 1_000_000 8 | 9 | alias _DAYS_IN_MONTH = VariadicList[Int]( 10 | -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 11 | ) 12 | alias _DAYS_BEFORE_MONTH = VariadicList[Int]( 13 | -1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 14 | ) # -1 is a placeholder for indexing purposes. 15 | 16 | alias MONTH_NAMES = StaticTuple[StringLiteral, 13]( 17 | "", 18 | "January", 19 | "February", 20 | "March", 21 | "April", 22 | "May", 23 | "June", 24 | "July", 25 | "August", 26 | "September", 27 | "October", 28 | "November", 29 | "December", 30 | ) 31 | 32 | alias MONTH_ABBREVIATIONS = StaticTuple[StringLiteral, 13]( 33 | "", 34 | "Jan", 35 | "Feb", 36 | "Mar", 37 | "Apr", 38 | "May", 39 | "Jun", 40 | "Jul", 41 | "Aug", 42 | "Sep", 43 | "Oct", 44 | "Nov", 45 | "Dec", 46 | ) 47 | 48 | alias DAY_NAMES = StaticTuple[StringLiteral, 8]( 49 | "", 50 | "Monday", 51 | "Tuesday", 52 | "Wednesday", 53 | "Thursday", 54 | "Friday", 55 | "Saturday", 56 | "Sunday", 57 | ) 58 | alias DAY_ABBREVIATIONS = StaticTuple[StringLiteral, 8]( 59 | "", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" 60 | ) 61 | -------------------------------------------------------------------------------- /morrow/formatter.mojo: -------------------------------------------------------------------------------- 1 | from collections.vector import InlinedFixedVector 2 | from utils.static_tuple import StaticTuple 3 | from .util import rjust 4 | from .constants import MONTH_NAMES, MONTH_ABBREVIATIONS, DAY_NAMES, DAY_ABBREVIATIONS 5 | from .timezone import UTC_TZ 6 | 7 | alias formatter = _Formatter() 8 | 9 | 10 | struct _Formatter: 11 | var _sub_chrs: InlinedFixedVector[Int, 128] 12 | 13 | fn __init__(inout self): 14 | self._sub_chrs = InlinedFixedVector[Int, 128](0) 15 | for i in range(128): 16 | self._sub_chrs[i] = 0 17 | self._sub_chrs[_Y] = 4 18 | self._sub_chrs[_M] = 4 19 | self._sub_chrs[_D] = 2 20 | self._sub_chrs[_d] = 4 21 | self._sub_chrs[_H] = 2 22 | self._sub_chrs[_h] = 2 23 | self._sub_chrs[_m] = 2 24 | self._sub_chrs[_s] = 2 25 | self._sub_chrs[_S] = 6 26 | self._sub_chrs[_Z] = 3 27 | self._sub_chrs[_A] = 1 28 | self._sub_chrs[_a] = 1 29 | 30 | fn format(self, m: Morrow, fmt: String) raises -> String: 31 | """ 32 | "YYYY[abc]MM" -> repalce("YYYY") + "abc" + replace("MM") 33 | """ 34 | if len(fmt) == 0: 35 | return "" 36 | var ret: String = "" 37 | var in_bracket = False 38 | var start_idx = 0 39 | for i in range(len(fmt)): 40 | if fmt[i] == "[": 41 | if in_bracket: 42 | ret += "[" 43 | else: 44 | in_bracket = True 45 | ret += self.replace(m, fmt[start_idx:i]) 46 | start_idx = i + 1 47 | elif fmt[i] == "]": 48 | if in_bracket: 49 | ret += fmt[start_idx:i] 50 | in_bracket = False 51 | else: 52 | ret += self.replace(m, fmt[start_idx:i]) 53 | ret += "]" 54 | start_idx = i + 1 55 | if in_bracket: 56 | ret += "[" 57 | if start_idx < len(fmt): 58 | ret += self.replace(m, fmt[start_idx:]) 59 | return ret 60 | 61 | fn replace(self, m: Morrow, s: String) raises -> String: 62 | """ 63 | split token and replace 64 | """ 65 | if len(s) == 0: 66 | return "" 67 | var ret: String = "" 68 | var match_chr_ord = 0 69 | var match_count = 0 70 | for i in range(len(s)): 71 | var c = ord(s[i]) 72 | if 0 < c < 128 and self._sub_chrs[c] > 0: 73 | if c == match_chr_ord: 74 | match_count += 1 75 | else: 76 | ret += self.replace_token(m, str(match_chr_ord), match_count) 77 | match_chr_ord = c 78 | match_count = 1 79 | if match_count == self._sub_chrs[c]: 80 | ret += self.replace_token(m, str(match_chr_ord), match_count) 81 | match_chr_ord = 0 82 | else: 83 | if match_chr_ord > 0: 84 | ret += self.replace_token(m, str(match_chr_ord), match_count) 85 | match_chr_ord = 0 86 | ret += s[i] 87 | if match_chr_ord > 0: 88 | ret += self.replace_token(m, str(match_chr_ord), match_count) 89 | return ret 90 | 91 | fn replace_token(self, m: Morrow, token: String, token_count: Int) raises -> String: 92 | if token == str(_Y): 93 | if token_count == 1: 94 | return "Y" 95 | if token_count == 2: 96 | return rjust(m.year, 4, "0")[2:4] 97 | if token_count == 4: 98 | return rjust(m.year, 4, "0") 99 | elif token == str(_M): 100 | if token_count == 1: 101 | return String(m.month) 102 | if token_count == 2: 103 | return rjust(m.month, 2, "0") 104 | if token_count == 3: 105 | return String(MONTH_ABBREVIATIONS[m.month]) 106 | if token_count == 4: 107 | return String(MONTH_NAMES[m.month]) 108 | elif token == str(_D): 109 | if token_count == 1: 110 | return String(m.day) 111 | if token_count == 2: 112 | return rjust(m.day, 2, "0") 113 | elif token == str(_H): 114 | if token_count == 1: 115 | return String(m.hour) 116 | if token_count == 2: 117 | return rjust(m.hour, 2, "0") 118 | elif token == str(_h): 119 | var h_12 = m.hour 120 | if m.hour > 12: 121 | h_12 -= 12 122 | if token_count == 1: 123 | return String(h_12) 124 | if token_count == 2: 125 | return rjust(h_12, 2, "0") 126 | elif token == str(_m): 127 | if token_count == 1: 128 | return String(m.minute) 129 | if token_count == 2: 130 | return rjust(m.minute, 2, "0") 131 | elif token == str(_s): 132 | if token_count == 1: 133 | return String(m.second) 134 | if token_count == 2: 135 | return rjust(m.second, 2, "0") 136 | elif token == str(_S): 137 | if token_count == 1: 138 | return String(m.microsecond // 100000) 139 | if token_count == 2: 140 | return rjust(m.microsecond // 10000, 2, "0") 141 | if token_count == 3: 142 | return rjust(m.microsecond // 1000, 3, "0") 143 | if token_count == 4: 144 | return rjust(m.microsecond // 100, 4, "0") 145 | if token_count == 5: 146 | return rjust(m.microsecond // 10, 5, "0") 147 | if token_count == 6: 148 | return rjust(m.microsecond, 6, "0") 149 | elif token == str(_d): 150 | if token_count == 1: 151 | return String(m.isoweekday()) 152 | if token_count == 3: 153 | return String(DAY_ABBREVIATIONS[m.isoweekday()]) 154 | if token_count == 4: 155 | return String(DAY_NAMES[m.isoweekday()]) 156 | elif token == str(_Z): 157 | if token_count == 3: 158 | return UTC_TZ.name if m.tz.is_none() else m.tz.name 159 | var separator = "" if token_count == 1 else ":" 160 | if m.tz.is_none(): 161 | return UTC_TZ.format(separator) 162 | else: 163 | return m.tz.format(separator) 164 | 165 | elif token == str(_a): 166 | return "am" if m.hour < 12 else "pm" 167 | elif token == str(_A): 168 | return "AM" if m.hour < 12 else "PM" 169 | return "" 170 | 171 | 172 | alias _Y = ord("Y") 173 | alias _M = ord("M") 174 | alias _D = ord("D") 175 | alias _d = ord("d") 176 | alias _H = ord("H") 177 | alias _h = ord("h") 178 | alias _m = ord("m") 179 | alias _s = ord("s") 180 | alias _S = ord("S") 181 | alias _X = ord("X") 182 | alias _x = ord("x") 183 | alias _Z = ord("Z") 184 | alias _A = ord("A") 185 | alias _a = ord("a") 186 | -------------------------------------------------------------------------------- /morrow/timedelta.mojo: -------------------------------------------------------------------------------- 1 | from .util import rjust 2 | 3 | alias SECONDS_OF_DAY = 24 * 3600 4 | 5 | 6 | struct TimeDelta(Stringable): 7 | var days: Int 8 | var seconds: Int 9 | var microseconds: Int 10 | 11 | fn __init__( 12 | inout self, 13 | days: Int = 0, 14 | seconds: Int = 0, 15 | microseconds: Int = 0, 16 | milliseconds: Int = 0, 17 | minutes: Int = 0, 18 | hours: Int = 0, 19 | weeks: Int = 0, 20 | ): 21 | self.days = 0 22 | self.seconds = 0 23 | self.microseconds = 0 24 | 25 | var days_ = days 26 | var seconds_ = seconds 27 | var microseconds_ = microseconds 28 | 29 | # Normalize everything to days, seconds, microseconds. 30 | days_ += weeks * 7 31 | seconds_ += minutes * 60 + hours * 3600 32 | microseconds_ += milliseconds * 1000 33 | 34 | self.days = days_ 35 | days_ = seconds_ // SECONDS_OF_DAY 36 | seconds_ = seconds_ % SECONDS_OF_DAY 37 | self.days += days_ 38 | self.seconds += seconds_ 39 | 40 | seconds_ = microseconds_ // 1000000 41 | microseconds_ = microseconds_ % 1000000 42 | days_ = seconds_ // SECONDS_OF_DAY 43 | seconds_ = seconds_ % SECONDS_OF_DAY 44 | self.days += days_ 45 | self.seconds += seconds_ 46 | 47 | seconds_ = microseconds_ // 1000000 48 | self.microseconds = microseconds_ % 1000000 49 | self.seconds += seconds_ 50 | days_ = self.seconds // SECONDS_OF_DAY 51 | self.seconds = self.seconds % SECONDS_OF_DAY 52 | self.days += days_ 53 | 54 | fn __copyinit__(inout self, other: Self): 55 | self.days = other.days 56 | self.seconds = other.seconds 57 | self.microseconds = other.microseconds 58 | 59 | fn __str__(self) -> String: 60 | var mm = self.seconds // 60 61 | var ss = self.seconds % 60 62 | var hh = mm // 60 63 | mm = mm % 60 64 | var s = String(hh) + ":" + rjust(mm, 2, "0") + ":" + rjust(ss, 2, "0") 65 | if self.days: 66 | if abs(self.days) != 1: 67 | s = String(self.days) + " days, " + s 68 | else: 69 | s = String(self.days) + " day, " + s 70 | if self.microseconds: 71 | s = s + rjust(self.microseconds, 6, "0") 72 | return s 73 | 74 | fn total_seconds(self) -> Float64: 75 | """Total seconds in the duration.""" 76 | return ( 77 | (self.days * 86400 + self.seconds) * 10**6 + self.microseconds 78 | ) / 10**6 79 | 80 | @always_inline 81 | fn __add__(self, other: Self) -> Self: 82 | return Self( 83 | self.days + other.days, 84 | self.seconds + other.seconds, 85 | self.microseconds + other.microseconds, 86 | ) 87 | 88 | fn __radd__(self, other: Self) -> Self: 89 | return self.__add__(other) 90 | 91 | fn __sub__(self, other: Self) -> Self: 92 | return Self( 93 | self.days - other.days, 94 | self.seconds - other.seconds, 95 | self.microseconds - other.microseconds, 96 | ) 97 | 98 | fn __rsub__(self, other: Self) -> Self: 99 | return Self( 100 | other.days - self.days, 101 | other.seconds - self.seconds, 102 | other.microseconds - self.microseconds, 103 | ) 104 | 105 | fn __neg__(self) -> Self: 106 | return Self(-self.days, -self.seconds, -self.microseconds) 107 | 108 | fn __pos__(self) -> Self: 109 | return self 110 | 111 | fn __abs__(self) -> Self: 112 | if self.days < 0: 113 | return -self 114 | else: 115 | return self 116 | 117 | @always_inline 118 | fn __mul__(self, other: Int) -> Self: 119 | return Self( 120 | self.days * other, 121 | self.seconds * other, 122 | self.microseconds * other, 123 | ) 124 | 125 | fn __rmul__(self, other: Int) -> Self: 126 | return self.__mul__(other) 127 | 128 | fn _to_microseconds(self) -> Int: 129 | return (self.days * SECONDS_OF_DAY + self.seconds) * 1000000 + self.microseconds 130 | 131 | fn __mod__(self, other: Self) -> Self: 132 | var r = self._to_microseconds() % other._to_microseconds() 133 | return Self(0, 0, r) 134 | 135 | fn __eq__(self, other: Self) -> Bool: 136 | return ( 137 | self.days == other.days 138 | and self.seconds == other.seconds 139 | and self.microseconds == other.microseconds 140 | ) 141 | 142 | @always_inline 143 | fn __le__(self, other: Self) -> Bool: 144 | if self.days < other.days: 145 | return True 146 | elif self.days == other.days: 147 | if self.seconds < other.seconds: 148 | return True 149 | elif ( 150 | self.seconds == other.seconds 151 | and self.microseconds <= other.microseconds 152 | ): 153 | return True 154 | return False 155 | 156 | @always_inline 157 | fn __lt__(self, other: Self) -> Bool: 158 | if self.days < other.days: 159 | return True 160 | elif self.days == other.days: 161 | if self.seconds < other.seconds: 162 | return True 163 | elif ( 164 | self.seconds == other.seconds and self.microseconds < other.microseconds 165 | ): 166 | return True 167 | return False 168 | 169 | fn __ge__(self, other: Self) -> Bool: 170 | return not self.__lt__(other) 171 | 172 | fn __gt__(self, other: Self) -> Bool: 173 | return not self.__le__(other) 174 | 175 | fn __bool__(self) -> Bool: 176 | return self.days != 0 or self.seconds != 0 or self.microseconds != 0 177 | 178 | 179 | alias Min = TimeDelta(-99999999) 180 | alias Max = TimeDelta(days=99999999) 181 | alias Resolution = TimeDelta(microseconds=1) 182 | -------------------------------------------------------------------------------- /morrow/timezone.mojo: -------------------------------------------------------------------------------- 1 | from .util import rjust 2 | from ._libc import c_localtime 3 | 4 | alias UTC_TZ = TimeZone(0, "UTC") 5 | 6 | 7 | @value 8 | struct TimeZone(Stringable): 9 | var offset: Int 10 | var name: String 11 | 12 | fn __init__(inout self, offset: Int, name: String = ""): 13 | self.offset = offset 14 | self.name = name 15 | 16 | fn __str__(self) -> String: 17 | return self.name 18 | 19 | fn is_none(self) -> Bool: 20 | return self.name == "None" 21 | 22 | @staticmethod 23 | fn none() -> TimeZone: 24 | return TimeZone(0, "None") 25 | 26 | @staticmethod 27 | fn local() -> TimeZone: 28 | var local_t = c_localtime(0) 29 | return TimeZone(int(local_t.tm_gmtoff), "local") 30 | 31 | @staticmethod 32 | fn from_utc(utc_str: String) raises -> TimeZone: 33 | if len(utc_str) == 0: 34 | raise Error("utc_str is empty") 35 | if utc_str == "utc" or utc_str == "UTC" or utc_str == "Z": 36 | return TimeZone(0, "utc") 37 | var p = 3 if len(utc_str) > 3 and utc_str[0:3] == "UTC" else 0 38 | 39 | var sign = -1 if utc_str[p] == "-" else 1 40 | if utc_str[p] == "+" or utc_str[p] == "-": 41 | p += 1 42 | 43 | if ( 44 | len(utc_str) < p + 2 45 | or not isdigit(ord(utc_str[p])) 46 | or not isdigit(ord(utc_str[p + 1])) 47 | ): 48 | raise Error("utc_str format is invalid") 49 | var hours: Int = atol(utc_str[p : p + 2]) 50 | p += 2 51 | 52 | var minutes: Int 53 | if len(utc_str) <= p: 54 | minutes = 0 55 | elif len(utc_str) == p + 3 and utc_str[p] == ":": 56 | minutes = atol(utc_str[p + 1 : p + 3]) 57 | elif len(utc_str) == p + 2 and isdigit(ord(utc_str[p])): 58 | minutes = atol(utc_str[p : p + 2]) 59 | else: 60 | minutes = 0 61 | raise Error("utc_str format is invalid") 62 | var offset: Int = sign * (hours * 3600 + minutes * 60) 63 | return TimeZone(offset) 64 | 65 | fn format(self, sep: String = ":") -> String: 66 | var sign: String 67 | var offset_abs: Int 68 | if self.offset < 0: 69 | sign = "-" 70 | offset_abs = -self.offset 71 | else: 72 | sign = "+" 73 | offset_abs = self.offset 74 | var hh = offset_abs // 3600 75 | var mm = offset_abs % 3600 76 | return sign + rjust(hh, 2, "0") + sep + rjust(mm, 2, "0") 77 | -------------------------------------------------------------------------------- /morrow/util.mojo: -------------------------------------------------------------------------------- 1 | from collections.vector import DynamicVector 2 | 3 | from .constants import MAX_TIMESTAMP, MAX_TIMESTAMP_MS, MAX_TIMESTAMP_US 4 | from .constants import _DAYS_IN_MONTH, _DAYS_BEFORE_MONTH 5 | 6 | 7 | fn _is_leap(year: Int) -> Bool: 8 | "year -> 1 if leap year, else 0." 9 | return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) 10 | 11 | 12 | fn _days_before_year(year: Int) -> Int: 13 | "year -> number of days before January 1st of year." 14 | var y = year - 1 15 | return y * 365 + y // 4 - y // 100 + y // 400 16 | 17 | 18 | fn _days_in_month(year: Int, month: Int) -> Int: 19 | "year, month -> number of days in that month in that year." 20 | if month == 2 and _is_leap(year): 21 | return 29 22 | return _DAYS_IN_MONTH[month] 23 | 24 | 25 | fn _days_before_month(year: Int, month: Int) -> Int: 26 | "year, month -> number of days in year preceding first day of month." 27 | if month > 2 and _is_leap(year): 28 | return _DAYS_BEFORE_MONTH[month] + 1 29 | return _DAYS_BEFORE_MONTH[month] 30 | 31 | 32 | @always_inline 33 | fn _ymd2ord(year: Int, month: Int, day: Int) -> Int: 34 | "year, month, day -> ordinal, considering 01-Jan-0001 as day 1." 35 | var dim = _days_in_month(year, month) 36 | return _days_before_year(year) + _days_before_month(year, month) + day 37 | 38 | 39 | fn normalize_timestamp(timestamp: Float64) raises -> Float64: 40 | """Normalize millisecond and microsecond timestamps into normal timestamps. 41 | """ 42 | var timestamp_ = timestamp 43 | if timestamp_ > MAX_TIMESTAMP: 44 | if timestamp_ < MAX_TIMESTAMP_MS: 45 | timestamp_ /= 1000 46 | elif timestamp_ < MAX_TIMESTAMP_US: 47 | timestamp_ /= 1_000_000 48 | else: 49 | raise Error( 50 | "The specified timestamp " 51 | + String(timestamp_) 52 | + "is too large." 53 | ) 54 | return timestamp 55 | 56 | 57 | fn _repeat_string(string: String, n: Int) -> String: 58 | var result: String = "" 59 | for _ in range(n): 60 | result += string 61 | return result 62 | 63 | 64 | fn rjust(string: String, width: Int, fillchar: String = " ") -> String: 65 | var extra = width - len(string) 66 | return _repeat_string(fillchar, extra) + string 67 | 68 | 69 | fn rjust(string: Int, width: Int, fillchar: String = " ") -> String: 70 | return rjust(String(string), width, fillchar) 71 | -------------------------------------------------------------------------------- /scripts/ld: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Simple ld wrapper to do fun things 4 | """ 5 | 6 | # https://github.com/ihnorton/mojo-ffi/blob/ffi-demo/scripts/ld 7 | 8 | import json 9 | import subprocess 10 | import sys, os, shutil 11 | 12 | def lprefix(the_list, prefix): 13 | for idx,item in enumerate(reversed(the_list)): 14 | if item.startswith(prefix): 15 | return -1 - idx 16 | 17 | def main(): 18 | fwd_json = os.environ["MOJOC_LD_FWD"] 19 | fwd_args = json.loads(fwd_json) 20 | 21 | #print("fwd_args", fwd_args) 22 | 23 | argv = sys.argv 24 | 25 | argv[0] = "/usr/bin/ld" # put back original ld 26 | 27 | shared = fwd_args["shared"] 28 | if shared: 29 | argv.insert(1, "-shared") 30 | def remove_arg(the_arg): 31 | if the_arg in argv: 32 | del argv[argv.index(the_arg)] 33 | remove_arg("-pie") 34 | remove_arg("--gc-sections") 35 | 36 | # can't use with shared linkage 37 | remove_arg("/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o") 38 | 39 | # force the linker to keep all symbols. this is ugly but simple for now. 40 | # TODO: extract @export annotations and make linker script 41 | for idx,arg in enumerate(argv): 42 | if "/tmp" in arg and arg.endswith(".a"): 43 | argv[idx:idx+1] = ["--whole-archive", argv[idx], "--no-whole-archive"] 44 | break 45 | 46 | # insert forwarded -L arguments 47 | lib_args = fwd_args["lib_args"] 48 | #print("lib_args: ", lib_args) 49 | if lib_args is not None: 50 | L_index = lprefix(argv, "-L") 51 | argv[L_index:L_index] = lib_args 52 | 53 | link_args = fwd_args["link_args"] 54 | #print("link_args: ", link_args) 55 | if link_args: 56 | l_index = lprefix(argv, "-l") 57 | argv[l_index:l_index] = link_args 58 | 59 | if fwd_args["output_file"] is not None: 60 | ofile_idx = argv.index("-o") 61 | del argv[ofile_idx:ofile_idx+2] 62 | argv[ofile_idx:ofile_idx] = ["-o", fwd_args["output_file"]] 63 | 64 | # copy the tmp archive 65 | if False: 66 | for arg in argv: 67 | if "/tmp" in arg and arg.endswith(".a"): 68 | shutil.copyfile(arg, arg+".1") 69 | elif "/tmp" in arg and arg.endswith(".res"): 70 | p = arg.split("=")[-1] 71 | shutil.copyfile(p, p+".1") 72 | 73 | 74 | subprocess.run(argv) 75 | 76 | if __name__ == "__main__": 77 | main() -------------------------------------------------------------------------------- /scripts/mojoc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- mode: py -*- 3 | 4 | # https://github.com/ihnorton/mojo-ffi/blob/ffi-demo/scripts/mojoc 5 | 6 | import argparse 7 | import base64 8 | import os 9 | import json 10 | import sys 11 | import subprocess 12 | 13 | from typing import List 14 | 15 | def make_parser(): 16 | parser = argparse.ArgumentParser() 17 | parser.add_argument( 18 | "--debug-level", default='none', action="store", dest="debug_level" 19 | ) 20 | parser.add_argument( 21 | "-shared", default=False, action='store_true' 22 | ) 23 | parser.add_argument( 24 | "-l", action="append", dest="link_libs" 25 | ) 26 | parser.add_argument( 27 | "-S", action="append", dest="link_static" 28 | ) 29 | parser.add_argument( 30 | "-L", action="append", dest="link_paths" 31 | ) 32 | parser.add_argument( 33 | "-o", dest="output_file", type=str 34 | ) 35 | parser.add_argument( 36 | "input_file", type=str 37 | ) 38 | 39 | return parser 40 | 41 | def test_parser(): 42 | parser = make_parser() 43 | 44 | a0, _ = parser.parse_known_args(["test.mojo"]) 45 | assert a0.shared is False 46 | assert a0.input_file == "test.mojo" 47 | assert a0.link_libs == None 48 | assert a0.link_paths == None 49 | 50 | a1, _ = parser.parse_known_args("-shared -lCallable -lfoo test.mojo -o test".split()) 51 | assert a1.link_libs == ["Callable", "foo"] 52 | assert a1.link_paths == None 53 | assert a1.input_file == "test.mojo" 54 | print(a1.output_file) 55 | assert a1.output_file == "test" 56 | 57 | #test_parser() 58 | 59 | def run_mojo(args, mojo_args): 60 | link_args = [f"-l{arg}" for arg in args.link_libs] if args.link_libs else [] 61 | lib_args = [f"-L{arg}" for arg in args.link_paths] if args.link_paths else [] 62 | # Use `-S` arguments to pass through individual objects to link 63 | if args.link_static: 64 | lib_args += args.link_static 65 | 66 | ld_fwd = { 67 | "shared": args.shared, 68 | "link_args": link_args, 69 | "lib_args": lib_args, 70 | "output_file": args.output_file 71 | } 72 | # Should be safe enough for now, use base64 later if needed 73 | ld_fwd_json = json.dumps(ld_fwd) 74 | 75 | env = os.environ.copy() 76 | env["MOJOC_LD_FWD"] = ld_fwd_json 77 | # We want mojo to use the `ld` sitting next to this file 78 | #env["PATH"] = os.path.join(os.path.dirname(__file__)) + ":" + env["PATH"] 79 | mojoc_real_dir = os.path.dirname(os.path.realpath(__file__)) 80 | env["PATH"] = mojoc_real_dir + ":" + env["PATH"] 81 | 82 | #print("MOJOC_LD_FWD", ld_fwd_json) 83 | 84 | # print(*mojo_args) 85 | 86 | exec_args = ( 87 | "mojo", "build", "--debug-level", args.debug_level, *mojo_args 88 | ) 89 | exec_kwargs = { 90 | "env": env, 91 | } 92 | 93 | return subprocess.run(exec_args, **exec_kwargs, check=False) 94 | 95 | def cli(input_args): 96 | parser = make_parser() 97 | args, mojo_args = parser.parse_known_args(input_args) 98 | 99 | run_mojo(args, mojo_args) 100 | 101 | if __name__ == '__main__': 102 | cli(sys.argv) -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | cwd=$(pwd) 2 | export LD_LIBRARY_PATH=$cwd:$LD_LIBRARY_PATH 3 | -------------------------------------------------------------------------------- /strategies/__init__.mojo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f0cii/moxt/dee9018e4367dad3d9cfc600804f9b3d14493beb/strategies/__init__.mojo -------------------------------------------------------------------------------- /strategies/grid_utils.mojo: -------------------------------------------------------------------------------- 1 | import math 2 | from ylstdlib import * 3 | from collections.list import List 4 | from base.fixed import Fixed 5 | from base.mo import * 6 | from trade.types import * 7 | 8 | 9 | fn append_string_to_list_if_not_empty(inout l: List[String], s: String): 10 | if s == "": 11 | return 12 | l.append(s) 13 | 14 | 15 | @value 16 | struct GridCellInfo(CollectionElement): 17 | var level: Int 18 | var price: Fixed 19 | 20 | var long_open_cid: String # Order client ID 21 | var long_open_status: OrderStatus # Order status 22 | var long_open_quantity: Fixed # Quantity of open orders 23 | var long_entry_price: Fixed # Entry price 24 | var long_filled_qty: Fixed # Filled quantity 25 | var long_tp_cid: String # Take-profit order Client ID 26 | var long_tp_status: OrderStatus # Take-profit order status 27 | var long_sl_cid: String # Stop-loss order Client ID 28 | var long_sl_status: OrderStatus # Stop-loss order status 29 | var long_open_time_ms: Int64 30 | 31 | var short_open_cid: String 32 | var short_open_status: OrderStatus 33 | var short_open_quantity: Fixed 34 | var short_entry_price: Fixed 35 | var short_filled_qty: Fixed 36 | var short_tp_cid: String 37 | var short_tp_status: OrderStatus 38 | var short_sl_cid: String 39 | var short_sl_status: OrderStatus 40 | var short_open_time_ms: Int64 41 | 42 | fn __init__(inout self, level: Int, price: Fixed): 43 | self.level = level 44 | self.price = price 45 | 46 | self.long_open_cid = "" 47 | self.long_open_status = OrderStatus.empty 48 | self.long_open_quantity = Fixed.zero 49 | self.long_entry_price = Fixed.zero 50 | self.long_filled_qty = Fixed.zero 51 | self.long_tp_cid = "" 52 | self.long_tp_status = OrderStatus.empty 53 | self.long_sl_cid = "" 54 | self.long_sl_status = OrderStatus.empty 55 | self.long_open_time_ms = 0 56 | 57 | self.short_open_cid = "" 58 | self.short_open_status = OrderStatus.empty 59 | self.short_open_quantity = Fixed.zero 60 | self.short_entry_price = Fixed.zero 61 | self.short_filled_qty = Fixed.zero 62 | self.short_tp_cid = "" 63 | self.short_tp_status = OrderStatus.empty 64 | self.short_sl_cid = "" 65 | self.short_sl_status = OrderStatus.empty 66 | self.short_open_time_ms = 0 67 | 68 | fn reset_long_side(inout self) -> List[String]: 69 | var cid_list = List[String]() 70 | 71 | append_string_to_list_if_not_empty(cid_list, self.long_open_cid) 72 | append_string_to_list_if_not_empty(cid_list, self.long_tp_cid) 73 | append_string_to_list_if_not_empty(cid_list, self.long_sl_cid) 74 | 75 | self.long_open_cid = "" 76 | self.long_open_status = OrderStatus.empty 77 | self.long_open_quantity = Fixed.zero 78 | self.long_entry_price = Fixed.zero 79 | self.long_filled_qty = Fixed.zero 80 | self.long_tp_cid = "" 81 | self.long_tp_status = OrderStatus.empty 82 | self.long_sl_cid = "" 83 | self.long_sl_status = OrderStatus.empty 84 | self.long_open_time_ms = 0 85 | 86 | return cid_list 87 | 88 | fn reset_short_side(inout self) -> List[String]: 89 | var cid_list = List[String]() 90 | 91 | append_string_to_list_if_not_empty(cid_list, self.short_open_cid) 92 | append_string_to_list_if_not_empty(cid_list, self.short_tp_cid) 93 | append_string_to_list_if_not_empty(cid_list, self.short_sl_cid) 94 | 95 | self.short_open_cid = "" 96 | self.short_open_status = OrderStatus.empty 97 | self.short_open_quantity = Fixed.zero 98 | self.short_entry_price = Fixed.zero 99 | self.short_filled_qty = Fixed.zero 100 | self.short_tp_cid = "" 101 | self.short_tp_status = OrderStatus.empty 102 | self.short_sl_cid = "" 103 | self.short_sl_status = OrderStatus.empty 104 | self.short_open_time_ms = 0 105 | 106 | return cid_list 107 | 108 | fn calculate_profit_percentage( 109 | self, ask: Fixed, bid: Fixed, position_idx: PositionIdx 110 | ) -> Fixed: 111 | """ 112 | Calculate profit margin 113 | """ 114 | # Calculate floating profit using entry price 115 | var entry_price = Fixed.zero 116 | var entry_quantity = Fixed.zero 117 | var profit = Fixed(0.0) 118 | if position_idx == PositionIdx.both_side_buy: 119 | entry_price = self.long_entry_price 120 | entry_quantity = self.long_open_quantity 121 | var current_price = ask 122 | profit = (current_price - entry_price) * entry_quantity 123 | else: 124 | entry_price = self.short_entry_price 125 | entry_quantity = self.short_open_quantity 126 | var current_price = bid 127 | profit = (entry_price - current_price) * entry_quantity 128 | 129 | var entry_value = entry_price * entry_quantity 130 | if entry_price.is_zero(): 131 | return 0.0 132 | 133 | var profit_percentage = profit / entry_value 134 | return profit_percentage 135 | 136 | fn calculate_profit_amount( 137 | self, ask: Fixed, bid: Fixed, position_idx: PositionIdx 138 | ) -> Float64: 139 | """ 140 | Calculate profit amount 141 | """ 142 | # Calculate floating profit using entry price 143 | var entry_price = Fixed.zero 144 | var entry_quantity = Fixed.zero 145 | var profit = Float64(0.0) 146 | if position_idx == PositionIdx.both_side_buy: 147 | entry_price = self.long_entry_price 148 | entry_quantity = self.long_open_quantity 149 | var current_price = ask 150 | profit = ( 151 | current_price.to_float() - entry_price.to_float() 152 | ) * entry_quantity.to_float() 153 | else: 154 | entry_price = self.short_entry_price 155 | entry_quantity = self.short_open_quantity 156 | var current_price = bid 157 | profit = ( 158 | entry_price.to_float() - current_price.to_float() 159 | ) * entry_quantity.to_float() 160 | 161 | return profit 162 | 163 | fn __str__(self: Self) -> String: 164 | return ( 165 | "" 198 | ) 199 | 200 | 201 | @value 202 | struct GridInfo(Stringable): 203 | var grid_interval: Fixed 204 | var price_range: Fixed 205 | var tick_size: Fixed 206 | var base_price: Fixed 207 | var precision: Int 208 | var cells: List[GridCellInfo] 209 | 210 | fn __init__(inout self): 211 | self.grid_interval = Fixed.zero 212 | self.price_range = Fixed.zero 213 | self.tick_size = Fixed.zero 214 | self.base_price = Fixed.zero 215 | self.precision = 0 216 | self.cells = List[GridCellInfo](capacity=8) 217 | 218 | fn setup( 219 | inout self, 220 | grid_interval: Fixed, 221 | price_range: Fixed, 222 | tick_size: Fixed, 223 | base_price: Fixed, 224 | precision: Int, 225 | ) raises: 226 | self.grid_interval = grid_interval 227 | self.price_range = price_range 228 | self.tick_size = tick_size 229 | self.base_price = base_price 230 | self.precision = precision 231 | self.cells.append(GridCellInfo(0, base_price)) 232 | self.update(base_price) 233 | 234 | fn update(inout self, current_price: Fixed) raises: 235 | var x = self.price_range + Fixed(1) 236 | var range_high = current_price * x 237 | var range_low = current_price / x 238 | 239 | if ( 240 | self.cells[0].price <= range_low 241 | and self.cells[-1].price >= range_high 242 | ): 243 | return 244 | 245 | while self.cells[-1].price <= range_high: 246 | var level = self.cells[-1].level + 1 247 | var price = self.get_price_by_level(level) 248 | var cell = self.new_grid_cell(level, price) 249 | self.cells.append(cell) 250 | 251 | while self.cells[0].price >= range_low: 252 | var level = self.cells[0].level - 1 253 | var price = self.get_price_by_level(level) 254 | var cell = self.new_grid_cell(level, price) 255 | self.cells.insert(0, cell) 256 | 257 | fn get_price_by_level(self, level: Int) -> Fixed: 258 | var offset = level 259 | var price = pow(1.0 + self.grid_interval.to_float(), offset).cast[ 260 | DType.float64 261 | ]() * self.base_price.to_float() 262 | # var a = math.pow(Float64(10.0), self.precision) 263 | # var rounded = math.round(price * a) / a 264 | return Fixed(price).round(self.precision) 265 | 266 | fn get_cell_level_by_price(self, price: Fixed) -> Int: 267 | var offset = math.log( 268 | price.to_float() / self.base_price.to_float() 269 | ) / math.log(1 + self.grid_interval.to_float()) 270 | return int(round(offset)) 271 | 272 | fn new_grid_cell(self, level: Int, price: Fixed) -> GridCellInfo: 273 | return GridCellInfo(level, price) 274 | 275 | fn reset(inout self) raises: 276 | self.cells.clear() 277 | self.cells.append(self.new_grid_cell(0, self.base_price)) 278 | self.update(self.base_price) 279 | 280 | fn __str__(self) -> String: 281 | var result: String = "[" 282 | var n = len(self.cells) 283 | for i in range(n): 284 | var cell = self.cells[i] 285 | var repr = str(cell.level) + "," + str(cell.price) 286 | if i != n - 1: 287 | result += repr + ", " 288 | else: 289 | result += repr 290 | return result + "]" 291 | -------------------------------------------------------------------------------- /strategies/runner.mojo: -------------------------------------------------------------------------------- 1 | from sys import external_call 2 | from sys import argv 3 | import sys 4 | from sys.param_env import is_defined, env_get_int, env_get_string 5 | import time 6 | from testing import assert_equal, assert_true, assert_false 7 | from algorithm.functional import parallelize, sync_parallelize 8 | from base.c import * 9 | from base.mo import * 10 | from base.globals import _GLOBAL 11 | from base.thread import * 12 | from base.websocket import ( 13 | on_connect_callback, 14 | on_heartbeat_callback, 15 | on_message_callback, 16 | ) 17 | from trade.config import * 18 | from trade.base_strategy import * 19 | from trade.executor import * 20 | from .grid_strategy import GridStrategy 21 | from .grid_strategy_pm import GridStrategyPM 22 | from trade.backend_interactor import BackendInteractor 23 | from ylstdlib.twofish import twofish_decrypt 24 | import base.log 25 | from sys import num_physical_cores, num_logical_cores, num_performance_cores 26 | from memory.unsafe import bitcast 27 | 28 | 29 | @value 30 | struct ApiKey(CollectionElement, Stringable): 31 | var access_key: String 32 | var secret_key: String 33 | var testnet: Bool 34 | 35 | fn __str__(self) -> String: 36 | return self.access_key + ":" + self.secret_key + ":" + str(self.testnet) 37 | 38 | 39 | @value 40 | struct RedisSettings: 41 | var host: String 42 | var port: Int 43 | var password: String 44 | var db: Int 45 | 46 | fn __init__(inout self): 47 | self.host = "" 48 | self.port = 6379 49 | self.password = "" 50 | self.db = 0 51 | 52 | 53 | @value 54 | struct Param(CollectionElement, Stringable): 55 | var name: String 56 | var description: String 57 | var type: String 58 | var value: String 59 | 60 | fn __init__( 61 | inout self, 62 | name: String, 63 | description: String, 64 | type: String, 65 | value: String, 66 | ): 67 | self.name = name 68 | self.description = "" 69 | self.type = type 70 | self.value = value 71 | 72 | fn __str__(self) -> String: 73 | return self.name + "=" + self.value 74 | 75 | 76 | @value 77 | struct StrategyParams(CollectionElement, Stringable): 78 | var apikeys: List[ApiKey] 79 | var params: Dict[String, Param] 80 | var redis: RedisSettings 81 | 82 | fn __init__( 83 | inout self, 84 | apikeys: List[ApiKey], 85 | params: Dict[String, Param], 86 | redis: RedisSettings, 87 | ): 88 | self.apikeys = apikeys 89 | self.params = params 90 | self.redis = redis 91 | 92 | fn __init__(inout self): 93 | self.apikeys = List[ApiKey]() 94 | self.params = Dict[String, Param]() 95 | self.redis = RedisSettings() 96 | 97 | fn __str__(self) -> String: 98 | var result = String("") 99 | for apikey in self.apikeys: 100 | result += apikey[].access_key + ":" + apikey[].secret_key + "\n" 101 | for e in self.params.items(): 102 | result += e[].key + "=" + e[].value.value + "\n" 103 | result += "\n" 104 | return result 105 | 106 | 107 | fn parse_strategy_param(param_str: String) raises -> StrategyParams: 108 | var dom_parser = DomParser(1000 * 100) 109 | var doc = dom_parser.parse(param_str) 110 | var code = doc.get_int("code") 111 | var message = doc.get_str("message") 112 | if code != 0: 113 | raise "error code=" + str(code) + ", message=" + message 114 | 115 | var result = doc.get_object("result") 116 | var apikeys_array = result.get_array("apikeys") 117 | var params_array = result.get_array("params") 118 | var redis = result.get_object("redis") 119 | 120 | var apikeys = List[ApiKey]() 121 | var params = Dict[String, Param]() 122 | var apikeys_array_iter = apikeys_array.iter() 123 | 124 | while apikeys_array_iter.has_element(): 125 | var obj = apikeys_array_iter.get() 126 | # "id": "2m2f7Nb2iZN", 127 | # "uid": "2kvBETEGfXv", 128 | # "label": "testnet-001", 129 | # "name": "bybit", 130 | # "access_key": "ab", 131 | # "secret_key": "bc", 132 | # "testnet": 1, 133 | # "created_at": "2024-03-05T08:18:26.367051+08:00", 134 | # "updated_at": "2024-03-16T10:30:19.599758+08:00", 135 | # "status": 1 136 | 137 | var id = obj.get_str("id") 138 | var uid = obj.get_str("uid") 139 | var label = obj.get_str("label") 140 | var name = obj.get_str("name") 141 | var access_key = obj.get_str("access_key") 142 | var secret_key = obj.get_str("secret_key") 143 | var testnet = obj.get_int("testnet") == 1 144 | 145 | logi("id=" + id) 146 | logi("uid=" + uid) 147 | logi("label=" + label) 148 | logi("name=" + name) 149 | logi("access_key=" + access_key) 150 | logi("secret_key=" + secret_key) 151 | 152 | apikeys.append(ApiKey(access_key, secret_key, testnet)) 153 | 154 | apikeys_array_iter.step() 155 | 156 | var params_array_iter = params_array.iter() 157 | 158 | while params_array_iter.has_element(): 159 | var obj = params_array_iter.get() 160 | # { 161 | # "name": "s", 162 | # "description": "s", 163 | # "type": "string", 164 | # "value": "s100", 165 | # "def_val": "s100" 166 | # } 167 | 168 | var name = obj.get_str("name") 169 | var description = obj.get_str("description") 170 | var type = obj.get_str("type") 171 | var value = String("") 172 | if type == "string": 173 | value = obj.get_str("value") 174 | elif type == "int": 175 | value = str(obj.get_int("value")) 176 | elif type == "float": 177 | value = str(Fixed(obj.get_float("value"))) 178 | elif type == "bool": 179 | value = str(obj.get_bool("value")) 180 | var def_val = obj.get_str("def_val") 181 | 182 | # logi("name=" + name) 183 | # logi("description=" + description) 184 | # logi("type=" + type) 185 | # logi("value=" + value) 186 | # logi("def_val=" + def_val) 187 | 188 | var param = Param(name, description, type, value) 189 | # logi("param " + name + "=" + value) 190 | params[name] = param 191 | 192 | params_array_iter.step() 193 | 194 | var redisSettings = RedisSettings() 195 | redisSettings.host = redis.get_str("host") 196 | redisSettings.port = redis.get_int("port") 197 | redisSettings.password = redis.get_str("pass") 198 | redisSettings.db = redis.get_int("db") 199 | 200 | _ = doc^ 201 | _ = dom_parser^ 202 | 203 | return StrategyParams(apikeys, params, redisSettings) 204 | 205 | 206 | fn parse_strategy_param_to_app_config( 207 | strategy_param: StrategyParams, 208 | ) raises -> AppConfig: 209 | if len(strategy_param.apikeys) == 0: 210 | raise "apikeys is empty" 211 | 212 | var apikey = strategy_param.apikeys[0] 213 | var params = strategy_param.params 214 | 215 | if "symbols" not in params: 216 | raise "symbols is empty" 217 | 218 | var app_config = AppConfig() 219 | var app_config_ref = UnsafePointer[AppConfig].address_of(app_config) 220 | 221 | app_config_ref[].access_key = apikey.access_key 222 | app_config_ref[].secret_key = apikey.secret_key 223 | app_config_ref[].testnet = apikey.testnet 224 | app_config_ref[].depth = 1 225 | app_config_ref[].category = params["category"].value # "linear" 226 | 227 | var symbols = params["symbols"].value 228 | 229 | app_config_ref[].symbols = symbols 230 | for e in params.items(): 231 | # logi(e[].key + "=" + e[].value.value) 232 | app_config_ref[].params[e[].key] = e[].value.value 233 | 234 | return app_config 235 | 236 | 237 | fn run_strategy(app_config: AppConfig) raises: 238 | var i_num_physical_cores = num_physical_cores() 239 | var i_num_logical_cores = num_logical_cores() 240 | var i_num_performance_cores = num_performance_cores() 241 | logi("i_num_physical_cores=" + str(i_num_physical_cores)) 242 | logi("i_num_logical_cores=" + str(i_num_logical_cores)) 243 | logi("i_num_performance_cores=" + str(i_num_performance_cores)) 244 | if ( 245 | i_num_physical_cores < 2 246 | or i_num_logical_cores < 2 247 | or i_num_performance_cores < 2 248 | ): 249 | raise "i_num_physical_cores=" + str( 250 | i_num_physical_cores 251 | ) + " i_num_logical_cores=" + str( 252 | i_num_logical_cores 253 | ) + " i_num_performance_cores=" + str( 254 | i_num_performance_cores 255 | ) + " is not enough" 256 | 257 | var strategy = app_config.strategy 258 | if strategy == "GridStrategy": 259 | var strategy = GridStrategy(app_config) 260 | var executor = Executor[GridStrategy](app_config, strategy^) 261 | __run_strategy[GridStrategy](executor) 262 | _ = executor^ 263 | elif strategy == "GridStrategyPM": 264 | var strategy = GridStrategyPM(app_config) 265 | var executor = Executor[GridStrategyPM](app_config, strategy^) 266 | __run_strategy[GridStrategyPM](executor) 267 | _ = executor^ 268 | else: 269 | raise "strategy=" + strategy + " is not supported" 270 | 271 | 272 | fn __run_strategy[T: BaseStrategy](inout executor: Executor[T]) raises: 273 | var executor_ptr = int(UnsafePointer.address_of(executor)) 274 | _GLOBAL()[].executor_ptr = executor_ptr 275 | 276 | logi("starting...") 277 | executor.start() 278 | logi("started") 279 | 280 | executor.run_once() 281 | 282 | var log_servie = log.log_service_itf() 283 | 284 | # parallelize 285 | # https://stackoverflow.com/questions/76562547/vectorize-vs-parallelize-in-mojo 286 | var g = _GLOBAL() 287 | var last_run_time = time.now() 288 | var interval = 10_000_000 # 10ms 289 | var current_time = time.now() 290 | var ioc = seq_asio_ioc() 291 | var err_count = 0 292 | while not g[].stop_requested_flag: 293 | seq_asio_ioc_poll(ioc) 294 | _ = log_servie[].perform() 295 | current_time = time.now() 296 | if current_time - last_run_time >= interval: 297 | try: 298 | executor.run_once() 299 | err_count = 0 300 | except e: 301 | if str(e) == str(TooManyPendingRequestsError): 302 | err_count = 0 303 | else: 304 | loge("run_once error: " + str(e)) 305 | err_count += 1 306 | 307 | if err_count >= 5: 308 | loge("run_once error too many times, stop") 309 | break 310 | last_run_time = current_time 311 | # _ = sleep_us(1000 * 10) 312 | time.sleep(0.01) 313 | 314 | logi("Executor stopping...") 315 | 316 | var stop_flag = False 317 | 318 | @parameter 319 | fn run_loop(n: Int) -> None: 320 | logi("task " + str(n) + " thread id: " + str(seq_thread_id())) 321 | _ = seq_photon_init_default() 322 | if n == 0: 323 | var ioc = seq_asio_ioc() 324 | while not stop_flag: 325 | # logi("task 0 loop") 326 | seq_asio_ioc_poll(ioc) 327 | # logi("task 0 loop end") 328 | time.sleep(0.001) 329 | elif n == 1: 330 | executor.stop_now() 331 | # var ptr = get_executor_ptr[T]() 332 | # ptr[].stop_now() 333 | stop_flag = True 334 | 335 | parallelize[run_loop](2) 336 | 337 | logi("Strategy execution finished") 338 | 339 | _ = log_servie[].perform_all() 340 | 341 | logi("run_strategy finished") 342 | 343 | 344 | fn signal_handler(signum: Int) -> None: 345 | logi("handle_exit: " + str(signum)) 346 | _GLOBAL()[].stop_requested_flag = True 347 | -------------------------------------------------------------------------------- /test_dict.mojo: -------------------------------------------------------------------------------- 1 | from testing import assert_true, assert_equal 2 | import os 3 | 4 | 5 | fn test_dict() raises: 6 | var a_dict = Dict[Int, Int]() 7 | a_dict[1] = 100 8 | 9 | assert_equal(a_dict[1], 100) 10 | 11 | var b_dict = Dict[Int, String]() 12 | b_dict[100] = "hello" 13 | 14 | assert_equal(b_dict[100], "hello") 15 | 16 | var c_dict = Dict[String, String]() 17 | c_dict["a1"] = "hello100" 18 | 19 | assert_equal(c_dict["a1"], "hello100") 20 | 21 | 22 | fn main() raises: 23 | test_dict() 24 | -------------------------------------------------------------------------------- /test_skiplist.mojo: -------------------------------------------------------------------------------- 1 | from testing import assert_true, assert_false, assert_equal 2 | from base.mo import * 3 | from base.fixed import Fixed 4 | 5 | 6 | fn test_skiplist() raises: 7 | var asks = seq_skiplist_new(True) 8 | _ = seq_skiplist_insert(asks, Fixed(3000).value(), Fixed(100).value(), True) 9 | _ = seq_skiplist_insert(asks, Fixed(3005).value(), Fixed(200).value(), True) 10 | _ = seq_skiplist_insert( 11 | asks, Fixed(2700.1).value(), Fixed(200).value(), True 12 | ) 13 | seq_skiplist_dump(asks) 14 | 15 | var node = seq_skiplist_begin(asks) 16 | var end = seq_skiplist_end(asks) 17 | while node != end: 18 | var key: Int64 = 0 19 | var value: Int64 = 0 20 | seq_skiplist_node_value( 21 | node, 22 | UnsafePointer[Int64].address_of(key), 23 | UnsafePointer[Int64].address_of(value), 24 | ) 25 | var key_ = Fixed.from_value(key) 26 | var value_ = Fixed.from_value(value) 27 | print("key: " + str(key_) + " value: " + str(value_)) 28 | node = seq_skiplist_next(asks, node) 29 | 30 | var v = seq_skiplist_remove(asks, Fixed(3000).value()) 31 | var vf = Fixed.from_value(v) 32 | 33 | assert_equal(str(vf), "100") 34 | 35 | seq_skiplist_dump(asks) 36 | seq_skiplist_free(asks) 37 | 38 | 39 | fn main() raises: 40 | test_skiplist() 41 | -------------------------------------------------------------------------------- /trade/__init__.mojo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f0cii/moxt/dee9018e4367dad3d9cfc600804f9b3d14493beb/trade/__init__.mojo -------------------------------------------------------------------------------- /trade/backend_interactor.mojo: -------------------------------------------------------------------------------- 1 | from base.httpclient import HttpClient, VERB_GET, Headers, QueryParams 2 | 3 | 4 | struct BackendInteractor: 5 | var backend_url: String 6 | var client: HttpClient 7 | 8 | fn __init__(inout self, backend_url: String): 9 | self.backend_url = backend_url 10 | self.client = HttpClient(self.backend_url) 11 | 12 | fn login(self, username: String, password: String) -> String: 13 | var headers = Headers() 14 | self.set_default_headers(headers) 15 | headers["Content-Type"] = "application/json;charset=UTF-8" 16 | headers["X-Requested-With"] = "XMLHttpRequest" 17 | var data = '{"username": "' + username + '", "password": "' + password + '"}' 18 | var res = self.client.post("/api/auth/login", data, headers) 19 | # print(res.status_code) 20 | # print(res.text) 21 | return res.text 22 | 23 | fn get_bot_params(self, sid: String, token: String) -> String: 24 | var authorization = "Bearer " + token 25 | # var headers = { 26 | # 'Accept': 'application/json, text/plain, */*', 27 | # 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,ja;q=0.5,ms;q=0.4,zh-TW;q=0.3', 28 | # 'Authorization': authorization, 29 | # 'Cache-Control': 'no-cache', 30 | # 'Connection': 'keep-alive', 31 | # 'Pragma': 'no-cache', 32 | # 'Referer': 'http://1.94.26.93/', 33 | # 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0', 34 | # 'X-Requested-With': 'XMLHttpRequest', 35 | # } 36 | 37 | var path = "/api/algos/" + sid + "/options" 38 | var headers = Headers() 39 | self.set_default_headers(headers) 40 | headers["Authorization"] = authorization 41 | # headers["X-Requested-With"] = "XMLHttpRequest" 42 | var res = self.client.get(path, headers) 43 | # print(res.status_code) 44 | # print(res.text) 45 | return res.text 46 | 47 | fn set_default_headers(self, inout headers: Headers): 48 | headers["Accept"] = "application/json, text/plain, */*" 49 | headers["Accept-Language"] = "zh-CN,zh;q=0.9" 50 | -------------------------------------------------------------------------------- /trade/base_strategy.mojo: -------------------------------------------------------------------------------- 1 | from base.c import * 2 | from base.mo import * 3 | from base.fixed import Fixed 4 | from core.models import PositionInfo, OrderBookLevel, OrderBookLite 5 | from .config import AppConfig 6 | from .types import Order 7 | from .platform import Platform 8 | 9 | 10 | trait BaseStrategy(Movable): 11 | fn __init__(inout self, config: AppConfig) raises: 12 | ... 13 | 14 | fn setup(inout self, platform: UnsafePointer[Platform]) raises: 15 | ... 16 | 17 | fn on_init(inout self) raises: 18 | ... 19 | 20 | fn on_exit(inout self) raises: 21 | ... 22 | 23 | fn on_tick(inout self) raises: 24 | ... 25 | 26 | fn on_orderbook(inout self, ob: OrderBookLite) raises: 27 | ... 28 | 29 | fn on_order(inout self, order: Order) raises: 30 | ... 31 | 32 | fn on_position(inout self, position: PositionInfo) raises: 33 | ... 34 | -------------------------------------------------------------------------------- /trade/config.mojo: -------------------------------------------------------------------------------- 1 | from python import Python 2 | from collections import Dict 3 | from pathlib.path import Path 4 | from base.fixed import Fixed 5 | from base.moutil import * 6 | from core.env import * 7 | from base.mo import * 8 | 9 | 10 | @value 11 | struct AppConfig(Stringable): 12 | var testnet: Bool 13 | var access_key: String 14 | var secret_key: String 15 | var category: String 16 | var symbols: String 17 | var depth: Int 18 | var is_local_based: Bool 19 | var strategy: String 20 | var params: Dict[String, String] 21 | 22 | fn __init__(inout self): 23 | self.testnet = False 24 | self.access_key = "" 25 | self.secret_key = "" 26 | self.category = "" 27 | self.symbols = "" 28 | self.depth = 1 29 | self.is_local_based = True 30 | self.strategy = "" 31 | self.params = Dict[String, String]() 32 | 33 | fn __str__(self) -> String: 34 | var params_str = String("") 35 | for e in self.params.items(): 36 | if params_str != "": 37 | params_str += ", " 38 | params_str += e[].key + "=" + e[].value 39 | return ( 40 | "" 59 | ) 60 | 61 | 62 | fn load_config(filename: String) raises -> AppConfig: 63 | var py = Python.import_module("builtins") 64 | var tomli = Python.import_module("tomli") 65 | var tmp_file = Path(filename) 66 | var s = tmp_file.read_text() 67 | var dict = tomli.loads(s) 68 | var config = AppConfig() 69 | var config_ref = UnsafePointer[AppConfig].address_of(config) 70 | config_ref[].testnet = str_to_bool(str(dict["testnet"])) 71 | config_ref[].access_key = str(dict["access_key"]) 72 | config_ref[].secret_key = str(dict["secret_key"]) 73 | config_ref[].category = str(dict["category"]) 74 | config_ref[].symbols = str(dict["symbols"]) 75 | # logi("load_config symbols: " + config.symbols) 76 | config_ref[].depth = strtoi(str(dict["depth"])) 77 | config_ref[].is_local_based = str_to_bool(str(dict["is_local_based"])) 78 | config_ref[].strategy = str(dict["strategy"]["name"]) 79 | var params = dict["params"] 80 | var iterator = py.iter(params) 81 | var index = 0 82 | var count = int(params.__len__()) 83 | while index < count: 84 | var name = py.next(iterator) 85 | var value = params[name] 86 | # print(name, value) 87 | config_ref[].params[str(name)] = str(value) 88 | index += 1 89 | return config 90 | 91 | 92 | fn load_env_config(filename: String) raises -> AppConfig: 93 | var dict = env_load(filename) 94 | var config = AppConfig() 95 | config.testnet = str_to_bool(dict["testnet"]) 96 | config.access_key = dict["access_key"] 97 | config.secret_key = dict["secret_key"] 98 | config.category = dict["category"] 99 | config.symbols = dict["symbols"] 100 | config.depth = strtoi(dict["depth"]) 101 | return config 102 | -------------------------------------------------------------------------------- /trade/helpers.mojo: -------------------------------------------------------------------------------- 1 | from .types import * 2 | from core.bybitmodel import OrderInfo 3 | 4 | 5 | fn convert_bybit_order_status(status: String) -> OrderStatus: 6 | if status == "Created" or status == "New": 7 | return OrderStatus.new 8 | elif status == "Rejected": 9 | return OrderStatus.rejected 10 | elif status == "PartiallyFilled": 11 | return OrderStatus.partially_filled 12 | elif status == "PartiallyFilledCanceled" or status == "Cancelled": 13 | return OrderStatus.canceled 14 | elif status == "Filled": 15 | return OrderStatus.filled 16 | else: 17 | return OrderStatus.empty 18 | 19 | 20 | fn convert_bybit_order(order: OrderInfo) -> Order: 21 | var order_ = Order( 22 | symbol=order.symbol, 23 | order_type=order.type_, 24 | order_client_id=order.order_link_id, 25 | order_id=order.order_id, 26 | price=Fixed(order.price), 27 | quantity=Fixed(order.qty), 28 | filled_qty=Fixed(order.cum_exec_qty), 29 | status=convert_bybit_order_status(order.status), 30 | ) 31 | return order_ 32 | -------------------------------------------------------------------------------- /trade/pos.mojo: -------------------------------------------------------------------------------- 1 | from base.fixed import Fixed 2 | from base.moutil import time_ms 3 | from base.mo import * 4 | 5 | 6 | struct LocalPosition: 7 | """ 8 | Virtual position management 9 | """ 10 | 11 | var open_time_ms: Int 12 | var qty: Fixed 13 | var avg_price: Fixed 14 | 15 | fn __init__(inout self): 16 | self.open_time_ms = 0 17 | self.qty = Fixed.zero 18 | self.avg_price = Fixed.zero 19 | 20 | fn reset(inout self): 21 | self.open_time_ms = 0 22 | self.qty = Fixed.zero 23 | self.avg_price = Fixed.zero 24 | 25 | fn add(inout self, qty: Fixed, price: Fixed) raises: 26 | if qty < Fixed.zero or price.is_zero(): 27 | raise Error("[Position.add] parameter error") 28 | 29 | if self.open_time_ms == 0: 30 | self.open_time_ms = int(time_ms()) 31 | 32 | if self.qty.is_zero(): 33 | self.qty = qty 34 | self.avg_price = price 35 | self.log_position_change() 36 | return 37 | 38 | var origin_qty = self.qty 39 | var origin_avg_price = self.avg_price 40 | 41 | var total_new = qty * price 42 | var total_original = origin_qty * origin_avg_price 43 | var total = total_new + total_original 44 | var total_qty = self.qty + qty 45 | 46 | self.qty = total_qty 47 | self.avg_price = total / total_qty 48 | self.log_position_change() 49 | 50 | fn reduce(inout self, qty: Fixed, price: Fixed) raises: 51 | if qty < Fixed.zero or price.is_zero(): 52 | raise Error("[Position.reduce] parameter error") 53 | 54 | if self.qty.is_zero() or qty > self.qty: 55 | raise Error("[Position.reduce] insufficient quantity") 56 | 57 | var origin_qty = self.qty 58 | var origin_avg_price = self.avg_price 59 | 60 | var total_original = origin_qty * origin_avg_price 61 | var total_reduce = qty * price 62 | var total_qty = self.qty - qty 63 | 64 | if total_qty.is_zero(): 65 | self.reset() 66 | else: 67 | var total_remaining = total_original - total_reduce 68 | self.qty = total_qty 69 | self.avg_price = total_remaining / total_qty 70 | 71 | self.log_position_change() 72 | 73 | fn log_position_change(self): 74 | logi( 75 | "Change in position " + str(self.qty) + " @ " + str(self.avg_price) 76 | ) 77 | -------------------------------------------------------------------------------- /trade/types.mojo: -------------------------------------------------------------------------------- 1 | from collections.optional import Optional 2 | from collections.dict import Dict 3 | from base.fixed import Fixed 4 | 5 | 6 | trait StringableCollectionElement(CollectionElement, Stringable): 7 | ... 8 | 9 | 10 | struct OrderType: 11 | alias BUY = "BUY" 12 | alias SELL = "SELL" 13 | 14 | 15 | struct PositionMode: 16 | # 单项持仓模式 17 | alias MergedSingle = 0 18 | # 双向持仓模式 19 | alias BothSides = 3 20 | 21 | 22 | @value 23 | @register_passable 24 | struct OrderStatus(Stringable): 25 | var value: UInt8 26 | alias empty = OrderStatus(0) # Empty order 27 | alias new = OrderStatus(1) # Create new order 28 | alias partially_filled = OrderStatus(2) # Partial filled 29 | alias filled = OrderStatus(3) # Complete filled 30 | alias canceled = OrderStatus(4) # Cancelled 31 | alias rejected = OrderStatus(5) # Order rejected 32 | alias expired = OrderStatus(6) # Order expired 33 | 34 | # Method to determine if an order is active 35 | # Active orders include statuses of "new" and "partially filled" 36 | fn is_active(self: Self) -> Bool: 37 | return self.value == 1 or self.value == 2 38 | 39 | # Method to determine if an order is closed 40 | # Closed orders include statuses of "fully filled", "cancelled", "rejected", and "expired" 41 | fn is_closed(self: Self) -> Bool: 42 | return ( 43 | self.value == 3 44 | or self.value == 4 45 | or self.value == 5 46 | or self.value == 6 47 | ) 48 | 49 | fn __eq__(self: Self, rhs: Self) -> Bool: 50 | return self.value == rhs.value 51 | 52 | fn __ne__(self: Self, rhs: Self) -> Bool: 53 | return self.value != rhs.value 54 | 55 | fn __str__(self: Self) -> String: 56 | if self.value == 0: 57 | return "Empty" 58 | elif self.value == 1: 59 | return "New" 60 | elif self.value == 2: 61 | return "PartiallyFilled" 62 | elif self.value == 3: 63 | return "Filled" 64 | elif self.value == 4: 65 | return "Canceled" 66 | elif self.value == 5: 67 | return "Rejected" 68 | elif self.value == 6: 69 | return "Expired" 70 | else: 71 | return "--" 72 | 73 | 74 | @value 75 | @register_passable 76 | struct PositionIdx(Stringable, Intable): 77 | var value: UInt8 78 | alias single_side = PositionIdx(0) # Unidirectional position 79 | alias both_side_buy = PositionIdx( 80 | 1 81 | ) # Bidirectional position on the buy side 82 | alias both_side_sell = PositionIdx( 83 | 2 84 | ) # Bidirectional position on the sell side 85 | 86 | fn __eq__(self: Self, rhs: Self) -> Bool: 87 | return self.value == rhs.value 88 | 89 | fn __ne__(self: Self, rhs: Self) -> Bool: 90 | return self.value != rhs.value 91 | 92 | fn __int__(self: Self) -> Int: 93 | return int(self.value) 94 | 95 | fn __str__(self: Self) -> String: 96 | if self.value == 0: 97 | return "SingleSide" 98 | elif self.value == 1: 99 | return "BothSideBuy" 100 | elif self.value == 2: 101 | return "BothSideSell" 102 | else: 103 | return "--" 104 | 105 | 106 | @value 107 | struct Account(Stringable): 108 | var coin: String # 账户币种 "USDT" 109 | var equity: Fixed # 账户权益 110 | var wallet_balance: Fixed # 钱包余额 111 | var available_to_withdraw: Fixed # 可提取余额,即可用保证金 112 | var total_order_margin: Fixed # 订单初始保证金 113 | var total_position_margin: Fixed # 持仓维持保证金 114 | var unrealised_pnl: Fixed # 未实现盈亏 115 | var cum_realised_pnl: Fixed # 累计已实现盈亏 116 | var borrow_amount: Fixed # 借币数量 117 | var extra: Dict[String, String] 118 | 119 | fn __init__(inout self): 120 | self.coin = "" 121 | self.equity = Fixed.zero 122 | self.wallet_balance = Fixed.zero 123 | self.available_to_withdraw = Fixed.zero 124 | self.total_order_margin = Fixed.zero 125 | self.total_position_margin = Fixed.zero 126 | self.unrealised_pnl = Fixed.zero 127 | self.cum_realised_pnl = Fixed.zero 128 | self.borrow_amount = Fixed.zero 129 | self.extra = Dict[String, String]() 130 | 131 | fn __init__( 132 | inout self, 133 | coin: String, 134 | equity: Fixed, 135 | wallet_balance: Fixed, 136 | available_to_withdraw: Fixed, 137 | total_order_margin: Fixed, 138 | total_position_margin: Fixed, 139 | unrealised_pnl: Fixed, 140 | cum_realised_pnl: Fixed, 141 | borrow_amount: Fixed = Fixed.zero, 142 | ): 143 | self.coin = coin 144 | self.equity = equity 145 | self.wallet_balance = wallet_balance 146 | self.available_to_withdraw = available_to_withdraw 147 | self.total_order_margin = total_order_margin 148 | self.total_position_margin = total_position_margin 149 | self.unrealised_pnl = unrealised_pnl 150 | self.cum_realised_pnl = cum_realised_pnl 151 | self.borrow_amount = borrow_amount 152 | self.extra = Dict[String, String]() 153 | 154 | fn available_margin(self) -> Fixed: 155 | """ 156 | 可用保证金 157 | """ 158 | return self.available_to_withdraw 159 | 160 | fn __str__(self) -> String: 161 | return ( 162 | "" 181 | ) 182 | 183 | 184 | @value 185 | struct Order(CollectionElement, Stringable): 186 | var symbol: String 187 | var order_type: String 188 | var order_client_id: String 189 | var order_id: String 190 | var price: Fixed 191 | var quantity: Fixed 192 | var filled_qty: Fixed 193 | var status: OrderStatus 194 | 195 | fn __init__(inout self): 196 | self.symbol = "" 197 | self.order_type = "" 198 | self.order_client_id = "" 199 | self.order_id = "" 200 | self.price = Fixed.zero 201 | self.quantity = Fixed.zero 202 | self.filled_qty = Fixed.zero 203 | self.status = OrderStatus.empty 204 | 205 | fn __init__( 206 | inout self, 207 | symbol: String, 208 | order_type: String, 209 | order_client_id: String, 210 | order_id: String, 211 | price: Fixed, 212 | quantity: Fixed, 213 | filled_qty: Fixed, 214 | status: OrderStatus, 215 | ): 216 | self.symbol = symbol 217 | self.order_type = order_type 218 | self.order_client_id = order_client_id 219 | self.order_id = order_id 220 | self.price = price 221 | self.quantity = quantity 222 | self.filled_qty = filled_qty 223 | self.status = status 224 | 225 | fn __str__(self: Self) -> String: 226 | return ( 227 | "" 244 | ) 245 | 246 | 247 | @value 248 | struct PlaceOrderResult(StringableCollectionElement): 249 | var order_id: String 250 | var order_client_id: String 251 | var order_detail: Optional[Order] 252 | 253 | fn __init__(inout self, order_id: String, order_client_id: String): 254 | self.order_id = order_id 255 | self.order_client_id = order_client_id 256 | self.order_detail = None 257 | 258 | fn __str__(self) -> String: 259 | return ( 260 | "" 265 | ) 266 | 267 | 268 | @value 269 | struct PlaceOrdersResult(Stringable): 270 | var orders: List[PlaceOrderResult] 271 | 272 | fn __init__(inout self, owned orders: List[PlaceOrderResult]): 273 | self.orders = orders 274 | 275 | fn __str__(self) -> String: 276 | return ( 277 | "" 282 | ) 283 | 284 | 285 | @value 286 | struct CancelOrderResult(StringableCollectionElement): 287 | var order_id: String 288 | var order_client_id: String 289 | var order_detail: Optional[Order] 290 | 291 | fn __init__(inout self, order_id: String, order_client_id: String): 292 | self.order_id = order_id 293 | self.order_client_id = order_client_id 294 | self.order_detail = None 295 | 296 | fn __str__(self) -> String: 297 | return ( 298 | "" 303 | ) 304 | 305 | 306 | @value 307 | struct BatchCancelResult(Stringable): 308 | var cancelled_orders: List[CancelOrderResult] 309 | 310 | fn __init__(inout self, owned cancelled_orders: List[CancelOrderResult]): 311 | self.cancelled_orders = cancelled_orders 312 | 313 | fn __str__(self) -> String: 314 | return ( 315 | "" 320 | ) 321 | 322 | 323 | fn get_list_string[T: StringableCollectionElement](l: List[T]) -> String: 324 | var s: String = "[" 325 | for i in range(len(l)): 326 | var repr = "'" + str(l[i]) + "'" 327 | if i != len(l) - 1: 328 | s += repr + ", " 329 | else: 330 | s += repr 331 | s += "]" 332 | return s 333 | -------------------------------------------------------------------------------- /ylstdlib/__init__.mojo: -------------------------------------------------------------------------------- 1 | from .floatutils import format_float 2 | from .queue import Queue 3 | from .list import i1 4 | -------------------------------------------------------------------------------- /ylstdlib/coroutine.mojo: -------------------------------------------------------------------------------- 1 | # https://github.com/modularml/mojo/blob/main/stdlib/src/builtin/coroutine.mojo 2 | # ===----------------------------------------------------------------------=== # 3 | # Copyright (c) 2024, Modular Inc. All rights reserved. 4 | # 5 | # Licensed under the Apache License v2.0 with LLVM Exceptions: 6 | # https://llvm.org/LICENSE.txt 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # ===----------------------------------------------------------------------=== # 14 | """Implements classes and methods for coroutines. 15 | 16 | These are Mojo built-ins, so you don't need to import them. 17 | """ 18 | 19 | from sys.info import sizeof 20 | 21 | from memory.unsafe import Pointer 22 | 23 | # ===----------------------------------------------------------------------=== # 24 | # _CoroutineContext 25 | # ===----------------------------------------------------------------------=== # 26 | 27 | 28 | @register_passable("trivial") 29 | struct _CoroutineContext: 30 | """The default context for a Coroutine, capturing the resume function 31 | callback and parent Coroutine. The resume function will typically just 32 | resume the parent. May be overwritten by other context types with different 33 | interpretations of the payload, but which nevertheless be the same size 34 | and contain the resume function and a payload pointer.""" 35 | 36 | alias _opaque_handle = Pointer[__mlir_type.i8] 37 | # Passed the coroutine being completed and its context's payload. 38 | alias _resume_fn_type = fn (Self._opaque_handle, Self._opaque_handle) -> None 39 | 40 | var _resume_fn: Self._resume_fn_type 41 | var _parent_hdl: Self._opaque_handle 42 | 43 | 44 | fn _coro_resume_callback( 45 | handle: _CoroutineContext._opaque_handle, 46 | parent: _CoroutineContext._opaque_handle, 47 | ): 48 | """Resume the parent Coroutine.""" 49 | _coro_resume_fn(parent) 50 | 51 | 52 | @always_inline 53 | fn _coro_resume_fn(handle: _CoroutineContext._opaque_handle): 54 | """This function is a generic coroutine resume function.""" 55 | __mlir_op.`pop.coroutine.resume`(handle.address) 56 | 57 | 58 | fn _coro_resume_noop_callback( 59 | handle: _CoroutineContext._opaque_handle, 60 | null: _CoroutineContext._opaque_handle, 61 | ): 62 | """Return immediately since nothing to resume.""" 63 | return 64 | 65 | 66 | # ===----------------------------------------------------------------------=== # 67 | # Coroutine 68 | # ===----------------------------------------------------------------------=== # 69 | 70 | 71 | @register_passable 72 | struct Coroutine[type: AnyRegType]: 73 | """Represents a coroutine. 74 | 75 | Coroutines can pause execution saving the state of the program (including 76 | values of local variables and the location of the next instruction to be 77 | executed). When the coroutine is resumed, execution continues from where it 78 | left off, with the saved state restored. 79 | 80 | Parameters: 81 | type: Type of value returned upon completion of the coroutine. 82 | """ 83 | 84 | alias _handle_type = __mlir_type[`!pop.coroutine<() -> `, type, `>`] 85 | alias _promise_type = __mlir_type[`!kgen.struct<(`, type, `)>`] 86 | var _handle: Self._handle_type 87 | 88 | @always_inline 89 | fn _get_promise(self) -> Pointer[type]: 90 | """Return the pointer to the beginning of the memory where the async 91 | function results are stored. 92 | 93 | Returns: 94 | The coroutine promise. 95 | """ 96 | var promise: Pointer[Self._promise_type] = __mlir_op.`pop.coroutine.promise`( 97 | self._handle 98 | ) 99 | return promise.bitcast[type]() 100 | 101 | @always_inline 102 | fn get(self) -> type: 103 | """Get the value of the fulfilled coroutine promise. 104 | 105 | Returns: 106 | The value of the fulfilled promise. 107 | """ 108 | return self._get_promise().load() 109 | 110 | @always_inline 111 | fn _get_ctx[ctx_type: AnyRegType](self) -> Pointer[ctx_type]: 112 | """Returns the pointer to the coroutine context. 113 | 114 | Parameters: 115 | ctx_type: The type of the coroutine context. 116 | 117 | Returns: 118 | The coroutine context. 119 | """ 120 | constrained[ 121 | sizeof[_CoroutineContext]() == sizeof[ctx_type](), 122 | "context size must be 16 bytes", 123 | ]() 124 | return self._get_promise().bitcast[ctx_type]() - 1 125 | 126 | @always_inline 127 | fn __init__(handle: Self._handle_type) -> Coroutine[type]: 128 | """Construct a coroutine object from a handle. 129 | 130 | Args: 131 | handle: The init handle. 132 | 133 | Returns: 134 | The constructed coroutine object. 135 | """ 136 | var self = Coroutine[type] {_handle: handle} 137 | var parent_hdl = __mlir_op.`pop.coroutine.opaque_handle`() 138 | self._get_ctx[_CoroutineContext]().store( 139 | _CoroutineContext { 140 | _resume_fn: _coro_resume_callback, _parent_hdl: parent_hdl 141 | } 142 | ) 143 | return self ^ 144 | 145 | @always_inline 146 | fn __del__(owned self): 147 | """Destroy the coroutine object.""" 148 | __mlir_op.`pop.coroutine.destroy`(self._handle) 149 | 150 | @always_inline 151 | fn __call__(self) -> type: 152 | """Execute the coroutine synchronously. 153 | 154 | Returns: 155 | The coroutine promise. 156 | """ 157 | 158 | self._get_ctx[_CoroutineContext]().store( 159 | _CoroutineContext { 160 | _resume_fn: _coro_resume_noop_callback, 161 | _parent_hdl: _CoroutineContext._opaque_handle(), 162 | } 163 | ) 164 | __mlir_op.`pop.coroutine.resume`(self._handle) 165 | return self.get() 166 | 167 | @always_inline 168 | fn resume(self): 169 | """Execute the coroutine synchronously. 170 | 171 | Returns: 172 | The coroutine promise. 173 | """ 174 | 175 | self._get_ctx[_CoroutineContext]().store( 176 | _CoroutineContext { 177 | _resume_fn: _coro_resume_noop_callback, 178 | _parent_hdl: _CoroutineContext._opaque_handle(), 179 | } 180 | ) 181 | __mlir_op.`pop.coroutine.resume`(self._handle) 182 | 183 | @always_inline 184 | fn __await__(self) -> type: 185 | """Suspends the current coroutine until the coroutine is complete. 186 | 187 | Returns: 188 | The coroutine promise. 189 | """ 190 | 191 | __mlir_region await_body(): 192 | __mlir_op.`pop.coroutine.resume`(self._handle) 193 | __mlir_op.`pop.coroutine.await.end`() 194 | 195 | __mlir_op.`pop.coroutine.await`[_region = "await_body".value]() 196 | return self.get() 197 | 198 | 199 | # ===----------------------------------------------------------------------=== # 200 | # RaisingCoroutine 201 | # ===----------------------------------------------------------------------=== # 202 | 203 | 204 | @register_passable 205 | struct RaisingCoroutine[type: AnyRegType]: 206 | """Represents a coroutine that can raise. 207 | 208 | Coroutines can pause execution saving the state of the program (including 209 | values of local variables and the location of the next instruction to be 210 | executed). When the coroutine is resumed, execution continues from where it 211 | left off, with the saved state restored. 212 | 213 | Parameters: 214 | type: Type of value returned upon completion of the coroutine. 215 | """ 216 | 217 | alias _var_type = __mlir_type[`!kgen.variant<`, Error, `, `, type, `>`] 218 | alias _handle_type = __mlir_type[ 219 | `!pop.coroutine<() throws -> `, Self._var_type, `>` 220 | ] 221 | alias _promise_type = __mlir_type[`!kgen.struct<(`, Self._var_type, `)>`] 222 | var _handle: Self._handle_type 223 | 224 | @always_inline 225 | fn _get_promise(self) -> Pointer[Self._var_type]: 226 | """Return the pointer to the beginning of the memory where the async 227 | function results are stored. 228 | 229 | Returns: 230 | The coroutine promise. 231 | """ 232 | var promise: Pointer[Self._promise_type] = __mlir_op.`pop.coroutine.promise`( 233 | self._handle 234 | ) 235 | return promise.bitcast[Self._var_type]() 236 | 237 | @always_inline 238 | fn get(self) raises -> type: 239 | """Get the value of the fulfilled coroutine promise. 240 | 241 | Returns: 242 | The value of the fulfilled promise. 243 | """ 244 | var variant = self._get_promise().load() 245 | if __mlir_op.`kgen.variant.is`[index = Int(0).value](variant): 246 | raise __mlir_op.`kgen.variant.take`[index = Int(0).value](variant) 247 | return __mlir_op.`kgen.variant.take`[index = Int(1).value](variant) 248 | 249 | @always_inline 250 | fn _get_ctx[ctx_type: AnyRegType](self) -> Pointer[ctx_type]: 251 | """Returns the pointer to the coroutine context. 252 | 253 | Parameters: 254 | ctx_type: The type of the coroutine context. 255 | 256 | Returns: 257 | The coroutine context. 258 | """ 259 | constrained[ 260 | sizeof[_CoroutineContext]() == sizeof[ctx_type](), 261 | "context size must be 16 bytes", 262 | ]() 263 | return self._get_promise().bitcast[ctx_type]() - 1 264 | 265 | @always_inline 266 | fn __init__(handle: Self._handle_type) -> Self: 267 | """Construct a coroutine object from a handle. 268 | 269 | Args: 270 | handle: The init handle. 271 | 272 | Returns: 273 | The constructed coroutine object. 274 | """ 275 | var self = Self {_handle: handle} 276 | var parent_hdl = __mlir_op.`pop.coroutine.opaque_handle`() 277 | self._get_ctx[_CoroutineContext]().store( 278 | _CoroutineContext { 279 | _resume_fn: _coro_resume_callback, _parent_hdl: parent_hdl 280 | } 281 | ) 282 | return self ^ 283 | 284 | @always_inline 285 | fn __del__(owned self): 286 | """Destroy the coroutine object.""" 287 | __mlir_op.`pop.coroutine.destroy`(self._handle) 288 | 289 | @always_inline 290 | fn __call__(self) raises -> type: 291 | """Execute the coroutine synchronously. 292 | 293 | Returns: 294 | The coroutine promise. 295 | """ 296 | 297 | fn _coro_noop_fn(handle: _CoroutineContext._opaque_handle): 298 | return 299 | 300 | self._get_ctx[_CoroutineContext]().store( 301 | _CoroutineContext { 302 | _resume_fn: _coro_resume_noop_callback, 303 | _parent_hdl: _CoroutineContext._opaque_handle(), 304 | } 305 | ) 306 | __mlir_op.`pop.coroutine.resume`(self._handle) 307 | return self.get() 308 | 309 | @always_inline 310 | fn __await__(self) raises -> type: 311 | """Suspends the current coroutine until the coroutine is complete. 312 | 313 | Returns: 314 | The coroutine promise. 315 | """ 316 | 317 | __mlir_region await_body(): 318 | __mlir_op.`pop.coroutine.resume`(self._handle) 319 | __mlir_op.`pop.coroutine.await.end`() 320 | 321 | __mlir_op.`pop.coroutine.await`[_region = "await_body".value]() 322 | return self.get() 323 | -------------------------------------------------------------------------------- /ylstdlib/dict.mojo: -------------------------------------------------------------------------------- 1 | from collections.dict import Dict, DictEntry 2 | 3 | 4 | fn reap_value[ 5 | K: KeyElement, V: CollectionElement 6 | ](owned v: DictEntry[K, V]) -> V: 7 | """Take the value from an owned entry. 8 | 9 | Returns: 10 | The value of the entry. 11 | """ 12 | __mlir_op.`lit.ownership.mark_destroyed`(__get_mvalue_as_litref(v)) 13 | return v.value^ 14 | 15 | 16 | # fn dict_pop[ 17 | # K: KeyElement, V: CollectionElement 18 | # ](inout dict: Dict[K, V], key: K) raises -> V: 19 | # """Remove a value from the dictionary by key. 20 | 21 | # Args: 22 | # dict: The dictionary to remove from. 23 | # key: The key to remove from the dictionary. 24 | 25 | # Returns: 26 | # The value associated with the key, if it was in the dictionary. 27 | # Raises otherwise. 28 | 29 | # Raises: 30 | # "KeyError" if the key was not present in the dictionary. 31 | # """ 32 | # var hash = hash(key) 33 | # var found: Bool 34 | # var slot: Int 35 | # var index: Int 36 | # found, slot, index = dict._find_index(hash, key) 37 | # if found: 38 | # dict._set_index(slot, dict.REMOVED) 39 | # var entry = Reference(dict._entries[index]) 40 | # debug_assert(entry[].__bool__(), "entry in index must be full") 41 | # var entry_value = entry[].unsafe_take() 42 | # entry[] = None 43 | # dict.size -= 1 44 | # # return entry_value^.reap_value() 45 | # return reap_value(entry_value^) 46 | # raise "KeyError" -------------------------------------------------------------------------------- /ylstdlib/floatutils.mojo: -------------------------------------------------------------------------------- 1 | fn format_float(f: Float64, dec_places: Int) -> String: 2 | # get input number as a string 3 | var f_str = String(f) 4 | # use position of the decimal point to determine the number of decimal places 5 | var int_places = f_str.find(".") 6 | # build a multiplier to shift the digits before the decimal point 7 | var mult = 10 ** (dec_places + 1) 8 | # note the use of an extra power of 10 to get the rounding digit 9 | # use the multiplier build the integer value of the input number 10 | var i = Float64(f * mult).cast[DType.int64]().to_int() 11 | # get the integer value as a string 12 | var i_str_full = String(i) 13 | # grab the last digit to be used to adjust/leave the previous digit 14 | var last_digit = i_str_full[len(i_str_full) - 1] 15 | # grab the last but one digit in the integer string 16 | var prev_digit_pos = len(i_str_full) - 1 17 | var prev_digit = i_str_full[prev_digit_pos - 1] 18 | # if last digit is >= to 5 then we... 19 | if ord(last_digit) >= ord(5): 20 | # ... increment it by 1 21 | prev_digit = chr(ord(prev_digit) + 1) 22 | # isolate the unchanging part of integer string 23 | var i_str_less_2 = i_str_full[0 : len(i_str_full) - 2] 24 | # grab the integer part of the output float string 25 | var i_str_int = i_str_full[0:int_places] 26 | # chop the integer part from the unchanging part of the number 27 | i_str_less_2 = i_str_less_2[int_places : len(i_str_less_2)] 28 | # build the output float string 29 | var i_str_out = i_str_int + "." + i_str_less_2 + prev_digit 30 | return i_str_out 31 | -------------------------------------------------------------------------------- /ylstdlib/list.mojo: -------------------------------------------------------------------------------- 1 | from collections.list import List 2 | 3 | 4 | alias i1 = __mlir_attr.`1: i1` 5 | 6 | 7 | @always_inline 8 | fn _max(a: Int, b: Int) -> Int: 9 | return a if a > b else b 10 | -------------------------------------------------------------------------------- /ylstdlib/queue.mojo: -------------------------------------------------------------------------------- 1 | from testing import assert_true 2 | from collections.list import List 3 | from collections.optional import Optional 4 | from math import max 5 | 6 | alias IndexError = Error("IndexError") 7 | 8 | 9 | @value 10 | struct Queue[T: CollectionElement](Sized): 11 | var items: List[T] 12 | var begin: Int 13 | 14 | fn __init__(inout self): 15 | self.items = List[T]() 16 | self.begin = 0 17 | 18 | fn __init__(inout self, capacity: Int): 19 | self.items = List[T](capacity=capacity) 20 | self.begin = 0 21 | 22 | @always_inline 23 | fn __len__(self) -> Int: 24 | return len(self.items) - self.begin 25 | 26 | @always_inline 27 | fn enqueue(inout self, item: T): 28 | self.items.append(item) 29 | 30 | @always_inline 31 | fn front(self) -> T: # Note: The return value can't be a reference, yet. 32 | return self.items[self.begin] 33 | 34 | @always_inline 35 | fn remove_front(inout self): 36 | # assert_true(len(self) > 0) 37 | if len(self) == 0: 38 | return 39 | self.begin += 1 40 | if self.begin == len(self.items): 41 | # reached the end of the underlying vector, reset state. 42 | self.items.clear() 43 | self.begin = 0 44 | 45 | @always_inline 46 | fn dequeue(inout self) -> Optional[T]: 47 | if len(self) == 0: 48 | return Optional[T](None) 49 | var item = self.items[self.begin] 50 | self.remove_front() 51 | return item 52 | -------------------------------------------------------------------------------- /ylstdlib/time.mojo: -------------------------------------------------------------------------------- 1 | from sys import external_call 2 | from sys.info import os_is_linux, os_is_windows 3 | from memory import UnsafePointer 4 | 5 | # Enums used in time.h 's glibc 6 | alias _CLOCK_REALTIME = 0 7 | alias _CLOCK_MONOTONIC = 1 if os_is_linux() else 6 8 | alias _CLOCK_PROCESS_CPUTIME_ID = 2 if os_is_linux() else 12 9 | alias _CLOCK_THREAD_CPUTIME_ID = 3 if os_is_linux() else 16 10 | alias _CLOCK_MONOTONIC_RAW = 4 11 | 12 | # Constants 13 | alias _NSEC_PER_USEC = 1000 14 | alias _NSEC_PER_MSEC = 1000000 15 | alias _USEC_PER_MSEC = 1000 16 | alias _MSEC_PER_SEC = 1000 17 | alias _NSEC_PER_SEC = _NSEC_PER_USEC * _USEC_PER_MSEC * _MSEC_PER_SEC 18 | 19 | # LARGE_INTEGER in Windows represent a signed 64 bit integer. Internally it 20 | # is implemented as a union of of one 64 bit integer or two 32 bit integers 21 | # for 64/32 bit compilers. 22 | # https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-large_integer-r1 23 | alias _WINDOWS_LARGE_INTEGER = Int64 24 | 25 | 26 | @value 27 | @register_passable("trivial") 28 | struct _CTimeSpec(Stringable): 29 | var tv_sec: Int # Seconds 30 | var tv_subsec: Int # subsecond (nanoseconds on linux and usec on mac) 31 | 32 | fn __init__(inout self): 33 | self.tv_sec = 0 34 | self.tv_subsec = 0 35 | 36 | fn as_nanoseconds(self) -> Int: 37 | @parameter 38 | if os_is_linux(): 39 | return self.tv_sec * _NSEC_PER_SEC + self.tv_subsec 40 | else: 41 | return self.tv_sec * _NSEC_PER_SEC + self.tv_subsec * _NSEC_PER_USEC 42 | 43 | fn __str__(self) -> String: 44 | return str(self.as_nanoseconds()) + "ns" 45 | 46 | 47 | @always_inline 48 | fn _clock_gettime(clockid: Int) -> _CTimeSpec: 49 | """Low-level call to the clock_gettime libc function""" 50 | var ts = _CTimeSpec() 51 | 52 | # Call libc's clock_gettime. 53 | _ = external_call["clock_gettime", Int32](Int32(clockid), UnsafePointer.address_of(ts)) 54 | 55 | return ts 56 | 57 | 58 | @always_inline 59 | fn _gettime_as_nsec_unix(clockid: Int) -> Int: 60 | if os_is_linux(): 61 | var ts = _clock_gettime(clockid) 62 | return ts.as_nanoseconds() 63 | else: 64 | return int(external_call["clock_gettime_nsec_np", Int64](Int32(clockid))) 65 | 66 | 67 | @always_inline 68 | fn time_ns() -> Int: 69 | return _gettime_as_nsec_unix(_CLOCK_REALTIME) 70 | -------------------------------------------------------------------------------- /ylstdlib/twofish.mojo: -------------------------------------------------------------------------------- 1 | from python.python import Python, PythonObject 2 | 3 | 4 | # pip install twofish 5 | 6 | 7 | fn twofish_encrypt(text: String, key: String) raises -> String: 8 | var twofish = Python.import_module("twofish") 9 | var binascii = Python.import_module("binascii") 10 | var key_ = binascii.a2b_hex(key) 11 | var T = twofish.Twofish(key_) 12 | var text_bytes = PythonObject(text).encode("utf-8") 13 | var x = T.encrypt(text_bytes) 14 | var x_hex = binascii.b2a_hex(x).decode("utf-8") 15 | return str(x_hex) 16 | 17 | 18 | fn twofish_decrypt(hex_text: String, key: String) raises -> String: 19 | var twofish = Python.import_module("twofish") 20 | var binascii = Python.import_module("binascii") 21 | var key_ = binascii.a2b_hex(PythonObject(key)) 22 | var T = twofish.Twofish(key_) 23 | var py_hex_text = PythonObject(hex_text) 24 | var bytes = binascii.a2b_hex(py_hex_text) 25 | var x = T.decrypt(bytes).decode() 26 | return str(x) 27 | 28 | 29 | fn test_twofish_raw() raises: 30 | var twofish = Python.import_module("twofish") 31 | var binascii = Python.import_module("binascii") 32 | var key_str = "0c6d6db61905400fee1f39e7fa26be87" 33 | var key = binascii.a2b_hex(key_str) 34 | print(key) 35 | var T = twofish.Twofish(key) 36 | var text = PythonObject("YELLOWSUBMARINES") 37 | var text_bytes = text.encode("utf-8") 38 | var x = T.encrypt(text_bytes) 39 | print(x) 40 | var x_hex = binascii.b2a_hex(x) 41 | print(x_hex.decode()) 42 | var y = T.decrypt(x).decode() 43 | print(str(y)) 44 | 45 | 46 | # fn test_twofish() raises: 47 | # var key = "0c6d6db61905400fee1f39e7fa26be87" 48 | # var text = "YELLOWSUBMARINES" 49 | # var v = twofish_encrypt(text, key) 50 | 51 | # print(v) 52 | 53 | # var c_text = twofish_decrypt(v, key) 54 | # print(c_text) 55 | 56 | 57 | # fn main() raises: 58 | # # test_twofish_raw() 59 | # test_twofish() 60 | --------------------------------------------------------------------------------