mm-toolbox 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mm_toolbox-0.1.0/LICENSE +21 -0
- mm_toolbox-0.1.0/PKG-INFO +123 -0
- mm_toolbox-0.1.0/README.md +90 -0
- mm_toolbox-0.1.0/mm_toolbox/__init__.py +11 -0
- mm_toolbox-0.1.0/mm_toolbox/src/__init__.py +23 -0
- mm_toolbox-0.1.0/mm_toolbox/src/candles/__init__.py +3 -0
- mm_toolbox-0.1.0/mm_toolbox/src/candles/base.py +400 -0
- mm_toolbox-0.1.0/mm_toolbox/src/candles/tick.py +41 -0
- mm_toolbox-0.1.0/mm_toolbox/src/candles/time.py +43 -0
- mm_toolbox-0.1.0/mm_toolbox/src/candles/volume.py +54 -0
- mm_toolbox-0.1.0/mm_toolbox/src/logging/__init__.py +1 -0
- mm_toolbox-0.1.0/mm_toolbox/src/logging/discord.py +97 -0
- mm_toolbox-0.1.0/mm_toolbox/src/logging/logger.py +128 -0
- mm_toolbox-0.1.0/mm_toolbox/src/logging/telegram.py +105 -0
- mm_toolbox-0.1.0/mm_toolbox/src/moving_average/__init__.py +2 -0
- mm_toolbox-0.1.0/mm_toolbox/src/moving_average/ema.py +108 -0
- mm_toolbox-0.1.0/mm_toolbox/src/moving_average/hma.py +92 -0
- mm_toolbox-0.1.0/mm_toolbox/src/numba/__init__.py +39 -0
- mm_toolbox-0.1.0/mm_toolbox/src/numba/array.py +182 -0
- mm_toolbox-0.1.0/mm_toolbox/src/numba/linalg.py +47 -0
- mm_toolbox-0.1.0/mm_toolbox/src/orderbook/__init__.py +18 -0
- mm_toolbox-0.1.0/mm_toolbox/src/orderbook/hft.py +751 -0
- mm_toolbox-0.1.0/mm_toolbox/src/orderbook/standard.py +345 -0
- mm_toolbox-0.1.0/mm_toolbox/src/ringbuffer/__init__.py +3 -0
- mm_toolbox-0.1.0/mm_toolbox/src/ringbuffer/multidim.py +201 -0
- mm_toolbox-0.1.0/mm_toolbox/src/ringbuffer/onedim.py +341 -0
- mm_toolbox-0.1.0/mm_toolbox/src/ringbuffer/twodim.py +349 -0
- mm_toolbox-0.1.0/mm_toolbox/src/rounding/__init__.py +1 -0
- mm_toolbox-0.1.0/mm_toolbox/src/rounding/rounding.py +130 -0
- mm_toolbox-0.1.0/mm_toolbox/src/time/__init__.py +8 -0
- mm_toolbox-0.1.0/mm_toolbox/src/time/time.py +79 -0
- mm_toolbox-0.1.0/mm_toolbox/src/websocket/__init__.py +1 -0
- mm_toolbox-0.1.0/mm_toolbox/src/websocket/stream.py +448 -0
- mm_toolbox-0.1.0/mm_toolbox/src/websocket/tools.py +93 -0
- mm_toolbox-0.1.0/mm_toolbox/src/weights/__init__.py +2 -0
- mm_toolbox-0.1.0/mm_toolbox/src/weights/ema.py +27 -0
- mm_toolbox-0.1.0/mm_toolbox/src/weights/geometric.py +27 -0
- mm_toolbox-0.1.0/pyproject.toml +46 -0
mm_toolbox-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 beatzxbt
|
|
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.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: mm-toolbox
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: "high-performance python tools for market making strategies."
|
|
5
|
+
Home-page: https://github.com/beatzxbt/mm-toolbox
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: market making,high performance python,orderbook
|
|
8
|
+
Author: beatzxbt
|
|
9
|
+
Author-email: 121855680+beatzxbt@users.noreply.github.com
|
|
10
|
+
Requires-Python: >=3.10.0,<3.13
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Natural Language :: English
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
|
|
24
|
+
Requires-Dist: aiohttp (>=3.10.4,<4.0.0)
|
|
25
|
+
Requires-Dist: aiosonic (>=0.21.0,<0.22.0)
|
|
26
|
+
Requires-Dist: ciso8601 (>=2.3.1,<3.0.0)
|
|
27
|
+
Requires-Dist: numba (>=0.60.0,<0.61.0)
|
|
28
|
+
Requires-Dist: numpy (>=2.1.0,<3.0.0)
|
|
29
|
+
Requires-Dist: orjson (>=3.10.7,<4.0.0)
|
|
30
|
+
Requires-Dist: scipy (>=1.14.0,<2.0.0)
|
|
31
|
+
Project-URL: Repository, https://github.com/beatzxbt/mm-toolbox
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# MM Toolbox
|
|
35
|
+
|
|
36
|
+
**MM Toolbox** is a Python library designed to provide high-performance tools for market making strategies.
|
|
37
|
+
|
|
38
|
+
## Contents
|
|
39
|
+
|
|
40
|
+
```plaintext
|
|
41
|
+
mm-toolbox/
|
|
42
|
+
├── mm_toolbox/
|
|
43
|
+
│ ├── src/
|
|
44
|
+
│ │ ├── candles/ # Tools for handling and aggregating candlestick data
|
|
45
|
+
│ │ ├── logging/ # Lightweight logging utilities
|
|
46
|
+
│ │ ├── moving_average/ # Implementations of various moving averages
|
|
47
|
+
│ │ ├── numba/ # Numba-optimized functions and utilities
|
|
48
|
+
│ │ ├── orderbook/ # Multiple orderbook implementations & tools
|
|
49
|
+
│ │ ├── ringbuffer/ # Efficient fixed-size circular buffers
|
|
50
|
+
│ │ ├── rounding/ # Utilities for rounding prices and sizes
|
|
51
|
+
│ │ ├── time/ # High-performance time utilities
|
|
52
|
+
│ │ ├── websocket/ # WebSocket handling utilities
|
|
53
|
+
│ │ ├── weights/ # Weight generators
|
|
54
|
+
│ ├── __init__.py # Package initialization
|
|
55
|
+
├── tests/ # Unit tests for all the modules
|
|
56
|
+
├── .gitignore # Git ignore file
|
|
57
|
+
├── LICENSE # License information
|
|
58
|
+
├── README.md # Main documentation file
|
|
59
|
+
├── requirements.txt # Python dependencies
|
|
60
|
+
└── setup.py # Setup script for pip installation
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
MM Toolbox is available on PyPI and can be installed using pip:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install mm_toolbox
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
To install directly from the source, clone the repository and install the dependencies:
|
|
72
|
+
```bash
|
|
73
|
+
git clone https://github.com/beatzxbt/mm-toolbox.git
|
|
74
|
+
cd mm-toolbox
|
|
75
|
+
pip install -r requirements.txt
|
|
76
|
+
python setup.py install
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Usage
|
|
80
|
+
|
|
81
|
+
After installation, you can start using MM Toolbox by importing the necessary modules:
|
|
82
|
+
```python
|
|
83
|
+
from mm_toolbox.src.orderbook import Orderbook
|
|
84
|
+
from mm_toolbox.src.moving_average import ExponentialMovingAverage
|
|
85
|
+
from mm_toolbox.src.time import time_iso8601
|
|
86
|
+
|
|
87
|
+
# Example usage:
|
|
88
|
+
ob = Orderbook(size=100)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Planned additions/upgrades
|
|
92
|
+
|
|
93
|
+
### v0.2.0
|
|
94
|
+
**Numba**: Complete coverage of [Numba's top-level functions](https://numba.readthedocs.io/en/stable/reference/numpysupported.html#other-functions) (with custom implementation if faster).
|
|
95
|
+
|
|
96
|
+
**Orderbook**: Directly update BBA, Imbalance Feature, ++Performance
|
|
97
|
+
|
|
98
|
+
**Candles**: ++Performance
|
|
99
|
+
|
|
100
|
+
**Websocket**: Fast websocket pool + auto swapping latency mechanism.
|
|
101
|
+
|
|
102
|
+
### v0.3.0
|
|
103
|
+
**Candles**: Multi-trigger candle (time/tick/volume).
|
|
104
|
+
|
|
105
|
+
**Logger**: High performance logger w/Database support integrated.
|
|
106
|
+
|
|
107
|
+
**Moving Average**: Weighted Moving Average (WMA).
|
|
108
|
+
|
|
109
|
+
### v0.4.0
|
|
110
|
+
**Orderbook**: [HFT Orderbook](/mm_toolbox/src/orderbook/hft.py), aiming to be fastest Python orderbook on GitHub.
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
MM Toolbox is licensed under the MIT License. See the [LICENSE](/LICENSE) file for more information.
|
|
115
|
+
|
|
116
|
+
## Contributing
|
|
117
|
+
|
|
118
|
+
Contributions are welcome! Please read the [CONTRIBUTING.md](/CONTRIBUTING.md) for guidelines on how to contribute to this project.
|
|
119
|
+
|
|
120
|
+
## Contact
|
|
121
|
+
|
|
122
|
+
For questions or support, please open an [issue](https://github.com/beatzxbt/mm-toolbox/issues).
|
|
123
|
+
I can also be reached on [Twitter](https://twitter.com/BeatzXBT) and [Discord](@gamingbeatz) :D
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# MM Toolbox
|
|
2
|
+
|
|
3
|
+
**MM Toolbox** is a Python library designed to provide high-performance tools for market making strategies.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
```plaintext
|
|
8
|
+
mm-toolbox/
|
|
9
|
+
├── mm_toolbox/
|
|
10
|
+
│ ├── src/
|
|
11
|
+
│ │ ├── candles/ # Tools for handling and aggregating candlestick data
|
|
12
|
+
│ │ ├── logging/ # Lightweight logging utilities
|
|
13
|
+
│ │ ├── moving_average/ # Implementations of various moving averages
|
|
14
|
+
│ │ ├── numba/ # Numba-optimized functions and utilities
|
|
15
|
+
│ │ ├── orderbook/ # Multiple orderbook implementations & tools
|
|
16
|
+
│ │ ├── ringbuffer/ # Efficient fixed-size circular buffers
|
|
17
|
+
│ │ ├── rounding/ # Utilities for rounding prices and sizes
|
|
18
|
+
│ │ ├── time/ # High-performance time utilities
|
|
19
|
+
│ │ ├── websocket/ # WebSocket handling utilities
|
|
20
|
+
│ │ ├── weights/ # Weight generators
|
|
21
|
+
│ ├── __init__.py # Package initialization
|
|
22
|
+
├── tests/ # Unit tests for all the modules
|
|
23
|
+
├── .gitignore # Git ignore file
|
|
24
|
+
├── LICENSE # License information
|
|
25
|
+
├── README.md # Main documentation file
|
|
26
|
+
├── requirements.txt # Python dependencies
|
|
27
|
+
└── setup.py # Setup script for pip installation
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
MM Toolbox is available on PyPI and can be installed using pip:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install mm_toolbox
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
To install directly from the source, clone the repository and install the dependencies:
|
|
39
|
+
```bash
|
|
40
|
+
git clone https://github.com/beatzxbt/mm-toolbox.git
|
|
41
|
+
cd mm-toolbox
|
|
42
|
+
pip install -r requirements.txt
|
|
43
|
+
python setup.py install
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
After installation, you can start using MM Toolbox by importing the necessary modules:
|
|
49
|
+
```python
|
|
50
|
+
from mm_toolbox.src.orderbook import Orderbook
|
|
51
|
+
from mm_toolbox.src.moving_average import ExponentialMovingAverage
|
|
52
|
+
from mm_toolbox.src.time import time_iso8601
|
|
53
|
+
|
|
54
|
+
# Example usage:
|
|
55
|
+
ob = Orderbook(size=100)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Planned additions/upgrades
|
|
59
|
+
|
|
60
|
+
### v0.2.0
|
|
61
|
+
**Numba**: Complete coverage of [Numba's top-level functions](https://numba.readthedocs.io/en/stable/reference/numpysupported.html#other-functions) (with custom implementation if faster).
|
|
62
|
+
|
|
63
|
+
**Orderbook**: Directly update BBA, Imbalance Feature, ++Performance
|
|
64
|
+
|
|
65
|
+
**Candles**: ++Performance
|
|
66
|
+
|
|
67
|
+
**Websocket**: Fast websocket pool + auto swapping latency mechanism.
|
|
68
|
+
|
|
69
|
+
### v0.3.0
|
|
70
|
+
**Candles**: Multi-trigger candle (time/tick/volume).
|
|
71
|
+
|
|
72
|
+
**Logger**: High performance logger w/Database support integrated.
|
|
73
|
+
|
|
74
|
+
**Moving Average**: Weighted Moving Average (WMA).
|
|
75
|
+
|
|
76
|
+
### v0.4.0
|
|
77
|
+
**Orderbook**: [HFT Orderbook](/mm_toolbox/src/orderbook/hft.py), aiming to be fastest Python orderbook on GitHub.
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MM Toolbox is licensed under the MIT License. See the [LICENSE](/LICENSE) file for more information.
|
|
82
|
+
|
|
83
|
+
## Contributing
|
|
84
|
+
|
|
85
|
+
Contributions are welcome! Please read the [CONTRIBUTING.md](/CONTRIBUTING.md) for guidelines on how to contribute to this project.
|
|
86
|
+
|
|
87
|
+
## Contact
|
|
88
|
+
|
|
89
|
+
For questions or support, please open an [issue](https://github.com/beatzxbt/mm-toolbox/issues).
|
|
90
|
+
I can also be reached on [Twitter](https://twitter.com/BeatzXBT) and [Discord](@gamingbeatz) :D
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Re-export all sub scripts for fast access.
|
|
2
|
+
from .src.candles import *
|
|
3
|
+
from .src.logging import *
|
|
4
|
+
from .src.moving_average import *
|
|
5
|
+
from .src.numba import *
|
|
6
|
+
from .src.orderbook import *
|
|
7
|
+
from .src.ringbuffer import *
|
|
8
|
+
from .src.rounding import *
|
|
9
|
+
from .src.time import *
|
|
10
|
+
from .src.websocket import *
|
|
11
|
+
from .src.weights import *
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
__VERSION__ = "0.1.0"
|
|
2
|
+
|
|
3
|
+
def clean_repo(repo_name: str) -> None:
|
|
4
|
+
"""Clean the repository"""
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
if os.name == "nt":
|
|
8
|
+
command = f"rmdir build /s /q & rmdir /s /q {repo_name}.egg-info & rmdir /s /q dist"
|
|
9
|
+
elif os.name == "posix":
|
|
10
|
+
command = f"rm -r build & rm -r {repo_name}.egg-info & rm -r dist"
|
|
11
|
+
else:
|
|
12
|
+
command = ""
|
|
13
|
+
|
|
14
|
+
os.system(command)
|
|
15
|
+
|
|
16
|
+
def install_package(package: str):
|
|
17
|
+
"""Install desired package"""
|
|
18
|
+
import pip
|
|
19
|
+
|
|
20
|
+
if hasattr(pip, 'main'):
|
|
21
|
+
pip.main(['install', package])
|
|
22
|
+
else:
|
|
23
|
+
pip._internal.main(['install', package])
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Tuple, Iterator, Union
|
|
4
|
+
|
|
5
|
+
from mm_toolbox.src.ringbuffer import RingBufferMultiDim
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseCandles(ABC):
|
|
9
|
+
"""
|
|
10
|
+
A class to aggregate trades into pre-defined fixed buckets.
|
|
11
|
+
|
|
12
|
+
Format
|
|
13
|
+
---------
|
|
14
|
+
Candle[]:
|
|
15
|
+
[0] = Open Price
|
|
16
|
+
[1] = High Price
|
|
17
|
+
[2] = Low Price
|
|
18
|
+
[3] = Close Price
|
|
19
|
+
[4] = Buy Volume
|
|
20
|
+
[5] = Sell Volume
|
|
21
|
+
[6] = VWAP Price
|
|
22
|
+
[7] = Total Trades
|
|
23
|
+
[8] = Open Timestamp
|
|
24
|
+
[9] = Close Timestamp
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
num_candles : int
|
|
29
|
+
The number of candles to maintain in the ring buffer
|
|
30
|
+
"""
|
|
31
|
+
def __init__(self, num_candles: int) -> None:
|
|
32
|
+
self.num_candles = num_candles
|
|
33
|
+
|
|
34
|
+
self.open_price = 0.0
|
|
35
|
+
self.high_price = -np.inf
|
|
36
|
+
self.low_price = np.inf
|
|
37
|
+
self.close_price = 0.0
|
|
38
|
+
self.buy_volume = 0.0
|
|
39
|
+
self.sell_volume = 0.0
|
|
40
|
+
self.vwap_price = 0.0
|
|
41
|
+
self.total_trades = 0.0
|
|
42
|
+
self.open_timestamp = 0.0
|
|
43
|
+
self.close_timestamp = 0.0
|
|
44
|
+
|
|
45
|
+
self._cum_price_volume_ = 0.0
|
|
46
|
+
self._total_volume_ = 0.0
|
|
47
|
+
|
|
48
|
+
self.ringbuffer = RingBufferMultiDim(
|
|
49
|
+
shape=(num_candles, 10),
|
|
50
|
+
dtype=np.float64
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def as_array(self) -> np.ndarray[np.ndarray]:
|
|
54
|
+
"""
|
|
55
|
+
Returns the aggregated candle data as a NumPy array.
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
np.ndarray[np.ndarray]
|
|
60
|
+
The array of aggregated candle data.
|
|
61
|
+
"""
|
|
62
|
+
if not self.ringbuffer.is_empty:
|
|
63
|
+
if self.open_timestamp != 0.0:
|
|
64
|
+
return np.concatenate((
|
|
65
|
+
self.ringbuffer.as_array(),
|
|
66
|
+
np.array([[
|
|
67
|
+
self.open_price,
|
|
68
|
+
self.high_price,
|
|
69
|
+
self.low_price,
|
|
70
|
+
self.close_price,
|
|
71
|
+
self.buy_volume,
|
|
72
|
+
self.sell_volume,
|
|
73
|
+
self.vwap_price,
|
|
74
|
+
self.total_trades,
|
|
75
|
+
self.open_timestamp,
|
|
76
|
+
self.close_timestamp
|
|
77
|
+
]])
|
|
78
|
+
))
|
|
79
|
+
|
|
80
|
+
else:
|
|
81
|
+
return self.ringbuffer.as_array()
|
|
82
|
+
|
|
83
|
+
else:
|
|
84
|
+
return np.array([[]], dtype=np.float64)
|
|
85
|
+
|
|
86
|
+
def reset_current_candle(self) -> None:
|
|
87
|
+
"""
|
|
88
|
+
Resets the current candle data to its initial state.
|
|
89
|
+
"""
|
|
90
|
+
self.open_price = 0.0
|
|
91
|
+
self.high_price = -np.inf
|
|
92
|
+
self.low_price = np.inf
|
|
93
|
+
self.close_price = 0.0
|
|
94
|
+
self.buy_volume = 0.0
|
|
95
|
+
self.sell_volume = 0.0
|
|
96
|
+
self.vwap_price = 0.0
|
|
97
|
+
self.total_trades = 0.0
|
|
98
|
+
self.open_timestamp = 0.0
|
|
99
|
+
self.close_timestamp = 0.0
|
|
100
|
+
self.total_volume = 0.0
|
|
101
|
+
|
|
102
|
+
self._cum_price_volume_ = 0.0
|
|
103
|
+
self._total_volume_ = 0.0
|
|
104
|
+
|
|
105
|
+
def insert_candle(
|
|
106
|
+
self,
|
|
107
|
+
open_price: float,
|
|
108
|
+
high_price: float,
|
|
109
|
+
low_price: float,
|
|
110
|
+
close_price: float,
|
|
111
|
+
buy_volume: float,
|
|
112
|
+
sell_volume: float,
|
|
113
|
+
vwap_price: float,
|
|
114
|
+
total_trades: float,
|
|
115
|
+
open_timestamp: float,
|
|
116
|
+
close_timestamp: float
|
|
117
|
+
) -> None:
|
|
118
|
+
"""
|
|
119
|
+
Inserts a completed candle into the ring buffer and resets the current candle.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
open_price : float
|
|
124
|
+
The open price of the candle.
|
|
125
|
+
|
|
126
|
+
high_price : float
|
|
127
|
+
The high price of the candle.
|
|
128
|
+
|
|
129
|
+
low_price : float
|
|
130
|
+
The low price of the candle.
|
|
131
|
+
|
|
132
|
+
close_price : float
|
|
133
|
+
The close price of the candle.
|
|
134
|
+
|
|
135
|
+
buy_volume : float
|
|
136
|
+
The buy volume of the candle.
|
|
137
|
+
|
|
138
|
+
sell_volume : float
|
|
139
|
+
The sell volume of the candle.
|
|
140
|
+
|
|
141
|
+
vwap_price : float
|
|
142
|
+
The volume-weighted average price of the candle.
|
|
143
|
+
|
|
144
|
+
total_trades : float
|
|
145
|
+
The total number of trades in the candle.
|
|
146
|
+
|
|
147
|
+
open_timestamp : float
|
|
148
|
+
The open timestamp of the candle.
|
|
149
|
+
|
|
150
|
+
close_timestamp : float
|
|
151
|
+
The close timestamp of the candle.
|
|
152
|
+
"""
|
|
153
|
+
current_candle = np.array([
|
|
154
|
+
open_price,
|
|
155
|
+
high_price,
|
|
156
|
+
low_price,
|
|
157
|
+
close_price,
|
|
158
|
+
buy_volume,
|
|
159
|
+
sell_volume,
|
|
160
|
+
vwap_price,
|
|
161
|
+
total_trades,
|
|
162
|
+
open_timestamp,
|
|
163
|
+
close_timestamp
|
|
164
|
+
])
|
|
165
|
+
self.ringbuffer.append(current_candle)
|
|
166
|
+
self.reset_current_candle()
|
|
167
|
+
|
|
168
|
+
@abstractmethod
|
|
169
|
+
def process_trade(self, timestamp: float, side: bool, price: float, size: float) -> None:
|
|
170
|
+
"""
|
|
171
|
+
Processes a single trade tick and updates the current candle data.
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
timestamp : float
|
|
176
|
+
The timestamp of the trade.
|
|
177
|
+
|
|
178
|
+
side : bool
|
|
179
|
+
Whether the trade is a buy (True) or sell (False).
|
|
180
|
+
|
|
181
|
+
price : float
|
|
182
|
+
The price at which the trade occurred.
|
|
183
|
+
|
|
184
|
+
size : float
|
|
185
|
+
The size (volume) of the trade.
|
|
186
|
+
"""
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
def initialize(self, trades: np.ndarray) -> None:
|
|
190
|
+
"""
|
|
191
|
+
Initializes the candle data with a batch of trades.
|
|
192
|
+
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
trades : np.ndarray
|
|
196
|
+
A 2D NumPy array of trades in the format [[Timestamp, Side, Price, Size]].
|
|
197
|
+
"""
|
|
198
|
+
for trade in trades:
|
|
199
|
+
self.process_trade(*trade)
|
|
200
|
+
|
|
201
|
+
def update(self, timestamp: float, side: bool, price: float, size: float) -> None:
|
|
202
|
+
"""
|
|
203
|
+
Updates the candle data with a new trade. Checks if the update is
|
|
204
|
+
as a result of stale data being recieved or not.
|
|
205
|
+
|
|
206
|
+
Parameters
|
|
207
|
+
----------
|
|
208
|
+
timestamp : float
|
|
209
|
+
The timestamp of the trade.
|
|
210
|
+
|
|
211
|
+
side : bool
|
|
212
|
+
Whether the trade is a buy (True) or sell (False).
|
|
213
|
+
|
|
214
|
+
price : float
|
|
215
|
+
The price at which the trade occurred.
|
|
216
|
+
|
|
217
|
+
size : float
|
|
218
|
+
The size (volume) of the trade.
|
|
219
|
+
"""
|
|
220
|
+
if timestamp >= self.open_timestamp:
|
|
221
|
+
self.process_trade(timestamp, side, price, size)
|
|
222
|
+
|
|
223
|
+
def calculate_vwap(self, price: float, size: float) -> float:
|
|
224
|
+
self._cum_price_volume_ += price * size
|
|
225
|
+
self._total_volume_ += size
|
|
226
|
+
return self._cum_price_volume_ / self._total_volume_
|
|
227
|
+
|
|
228
|
+
def durations(self) -> np.ndarray[float]:
|
|
229
|
+
candles = self.as_array()
|
|
230
|
+
return candles[:, 9] - candles[:, 8]
|
|
231
|
+
|
|
232
|
+
def imbalances(self) -> np.ndarray[float]:
|
|
233
|
+
candles = self.as_array()
|
|
234
|
+
return candles[:, 4] / candles[:, 5]
|
|
235
|
+
|
|
236
|
+
def average_true_range(self) -> np.ndarray[float]:
|
|
237
|
+
"""
|
|
238
|
+
Calculate the true range of a trading price bar.
|
|
239
|
+
|
|
240
|
+
The true range is the greatest of the following:
|
|
241
|
+
- The difference between the current high and the current low,
|
|
242
|
+
- The absolute difference between the current high and the previous close,
|
|
243
|
+
- The absolute difference between the current low and the previous close.
|
|
244
|
+
|
|
245
|
+
Returns
|
|
246
|
+
-------
|
|
247
|
+
np.ndarray[float]
|
|
248
|
+
The true range of price in the candles.
|
|
249
|
+
"""
|
|
250
|
+
candles = self.as_array()
|
|
251
|
+
|
|
252
|
+
if len(candles) < 2:
|
|
253
|
+
return np.array([], dtype=np.float64)
|
|
254
|
+
|
|
255
|
+
high_low_diff = candles[:, 1] - candles[:, 2]
|
|
256
|
+
high_prev_close_diff = np.abs(candles[1:, 1] - candles[:-1, 3])
|
|
257
|
+
low_prev_close_diff = np.abs(candles[1:, 2] - candles[:-1, 3])
|
|
258
|
+
|
|
259
|
+
# True Range is the maximum of the three calculated differences
|
|
260
|
+
true_range = np.maximum.reduce([
|
|
261
|
+
high_low_diff[1:],
|
|
262
|
+
high_prev_close_diff,
|
|
263
|
+
low_prev_close_diff
|
|
264
|
+
])
|
|
265
|
+
|
|
266
|
+
return true_range
|
|
267
|
+
|
|
268
|
+
def rsi(self, period: int = 14) -> np.ndarray[float]:
|
|
269
|
+
"""
|
|
270
|
+
Calculate the Relative Strength Index (RSI) for the given period.
|
|
271
|
+
|
|
272
|
+
Parameters
|
|
273
|
+
----------
|
|
274
|
+
period : int
|
|
275
|
+
The period over which to calculate the RSI (default is 14).
|
|
276
|
+
|
|
277
|
+
Returns
|
|
278
|
+
-------
|
|
279
|
+
np.ndarray
|
|
280
|
+
The RSI values for each candle.
|
|
281
|
+
"""
|
|
282
|
+
close_prices = self.close_prices
|
|
283
|
+
|
|
284
|
+
if period >= close_prices.size:
|
|
285
|
+
raise RuntimeWarning(f"Not enough candles for period {period}, calculating partial RSI only.")
|
|
286
|
+
|
|
287
|
+
delta = np.diff(close_prices, 1)
|
|
288
|
+
gain = np.where(delta > 0.0, delta, 0.0)
|
|
289
|
+
loss = np.where(delta < 0.0, -delta, 0.0)
|
|
290
|
+
|
|
291
|
+
avg_gain = np.zeros_like(close_prices)
|
|
292
|
+
avg_loss = np.zeros_like(close_prices)
|
|
293
|
+
|
|
294
|
+
for i in range(1, period):
|
|
295
|
+
# Partial RSI is just previous price's SMA
|
|
296
|
+
avg_gain[i] = gain[:i].mean()
|
|
297
|
+
avg_loss[i] = loss[:i].mean()
|
|
298
|
+
|
|
299
|
+
for i in range(period, len(close_prices) - 1):
|
|
300
|
+
avg_gain[i] = (avg_gain[i - 1] * (period - 1.0) + gain[i]) / period
|
|
301
|
+
avg_loss[i] = (avg_loss[i - 1] * (period - 1.0) + loss[i]) / period
|
|
302
|
+
|
|
303
|
+
rs = np.divide(avg_gain[1:], avg_loss[1:], where=avg_loss[1:] != 0)
|
|
304
|
+
rsi = 100.0 - (100.0 / (1.0 + rs))
|
|
305
|
+
|
|
306
|
+
return rsi
|
|
307
|
+
|
|
308
|
+
def bollinger_bands(self, period: int = 20, num_std_dev: float = 2.0) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
309
|
+
"""
|
|
310
|
+
Calculate Bollinger Bands (BB) for the given period.
|
|
311
|
+
|
|
312
|
+
Parameters
|
|
313
|
+
----------
|
|
314
|
+
period : int
|
|
315
|
+
The period over which to calculate the Bollinger Bands (default is 20).
|
|
316
|
+
|
|
317
|
+
num_std_dev : float
|
|
318
|
+
The number of standard deviations to use for the bands (default is 2.0).
|
|
319
|
+
|
|
320
|
+
Returns
|
|
321
|
+
-------
|
|
322
|
+
Tuple[np.ndarray, np.ndarray, np.ndarray]
|
|
323
|
+
The lower band, middle band (SMA), and upper band.
|
|
324
|
+
"""
|
|
325
|
+
close_prices = self.as_array()[:, 3]
|
|
326
|
+
|
|
327
|
+
sma = np.convolve(close_prices, np.ones(period) / period, mode='valid')
|
|
328
|
+
rolling_std = np.zeros_like(sma)
|
|
329
|
+
|
|
330
|
+
for i in range(sma.size):
|
|
331
|
+
rolling_std[i] = np.std(close_prices[i:i + period])
|
|
332
|
+
|
|
333
|
+
upper_band = sma + num_std_dev * rolling_std
|
|
334
|
+
lower_band = sma - num_std_dev * rolling_std
|
|
335
|
+
|
|
336
|
+
return lower_band, sma, upper_band
|
|
337
|
+
|
|
338
|
+
@property
|
|
339
|
+
def current_candle(self) -> np.ndarray[float]:
|
|
340
|
+
return np.array([
|
|
341
|
+
self.open_price,
|
|
342
|
+
self.high_price,
|
|
343
|
+
self.low_price,
|
|
344
|
+
self.close_price,
|
|
345
|
+
self.buy_volume,
|
|
346
|
+
self.sell_volume,
|
|
347
|
+
self.vwap_price,
|
|
348
|
+
self.total_trades,
|
|
349
|
+
self.open_timestamp,
|
|
350
|
+
self.close_timestamp
|
|
351
|
+
])
|
|
352
|
+
|
|
353
|
+
@property
|
|
354
|
+
def open_prices(self) -> np.ndarray[float]:
|
|
355
|
+
if not self.ringbuffer.is_empty:
|
|
356
|
+
return self.as_array()[:, 0]
|
|
357
|
+
|
|
358
|
+
@property
|
|
359
|
+
def high_prices(self) -> np.ndarray[float]:
|
|
360
|
+
if not self.ringbuffer.is_empty:
|
|
361
|
+
return self.as_array()[:, 1]
|
|
362
|
+
|
|
363
|
+
@property
|
|
364
|
+
def low_prices(self) -> np.ndarray[float]:
|
|
365
|
+
if not self.ringbuffer.is_empty:
|
|
366
|
+
return self.as_array()[:, 2]
|
|
367
|
+
|
|
368
|
+
@property
|
|
369
|
+
def close_prices(self) -> np.ndarray[float]:
|
|
370
|
+
if not self.ringbuffer.is_empty:
|
|
371
|
+
return self.as_array()[:, 3]
|
|
372
|
+
|
|
373
|
+
@property
|
|
374
|
+
def buy_volumes(self) -> np.ndarray[float]:
|
|
375
|
+
if not self.ringbuffer.is_empty:
|
|
376
|
+
return self.as_array()[:, 4]
|
|
377
|
+
|
|
378
|
+
@property
|
|
379
|
+
def sell_volumes(self) -> np.ndarray[float]:
|
|
380
|
+
if not self.ringbuffer.is_empty:
|
|
381
|
+
return self.as_array()[:, 5]
|
|
382
|
+
|
|
383
|
+
@property
|
|
384
|
+
def vwap_prices(self) -> np.ndarray[float]:
|
|
385
|
+
if not self.ringbuffer.is_empty:
|
|
386
|
+
return self.as_array()[:, 6]
|
|
387
|
+
|
|
388
|
+
@property
|
|
389
|
+
def all_trades(self) -> np.ndarray[float]:
|
|
390
|
+
if not self.ringbuffer.is_empty:
|
|
391
|
+
return self.as_array()[:, 7]
|
|
392
|
+
|
|
393
|
+
def __getitem__(self, index: Union[int, Tuple]) -> np.ndarray:
|
|
394
|
+
return self.as_array()[index]
|
|
395
|
+
|
|
396
|
+
def __len__(self) -> int:
|
|
397
|
+
return len(self.ringbuffer)
|
|
398
|
+
|
|
399
|
+
def __iter__(self) -> Iterator[np.ndarray]:
|
|
400
|
+
return iter(self.as_array())
|