├── .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 | 
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 | 
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 |
--------------------------------------------------------------------------------