├── .gitignore
├── .vscode
├── launch.json
├── microstructure-plotter.code-workspace
└── settings.json
├── LICENSE
├── README.md
├── assets
├── didnt_send_1.png
├── didnt_send_2.png
├── didnt_send_3.png
├── erratic_valuation_1.png
├── erratic_valuation_2.png
├── erratic_valuation_3.png
├── erratic_valuation_4.png
├── legend.png
├── order_book.png
├── plotter_video.gif
├── tts.png
└── zoomed_out_events.png
├── docs
├── legend
│ └── README.md
├── micro_primer
│ └── README.md
└── schema
│ └── README.md
├── examples
├── .DS_Store
├── README.md
└── example_1
│ ├── .DS_Store
│ ├── fake_data
│ ├── fill_data.csv
│ ├── order_data.csv
│ ├── quote_data.csv
│ ├── trade_data.csv
│ └── val_data.csv
│ ├── plot_example.py
│ └── plot_example.sh
├── microplot
├── __init__.py
├── data.py
├── plotter.py
├── schema.py
└── scripts
│ ├── __init__.py
│ └── plot_csv.py
├── requirements.txt
└── setup.py
/.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 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
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 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "name": "Python: Current File",
5 | "type": "python",
6 | "request": "launch",
7 | "program": "${file}",
8 | "console": "integratedTerminal",
9 | "cwd": "${fileDirname}",
10 | "env": {"PYTHONPATH": "${workspaceFolder}${pathSeparator}${env:PYTHONPATH}"}
11 | }
12 | ]
13 | }
--------------------------------------------------------------------------------
/.vscode/microstructure-plotter.code-workspace:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "path": ".."
5 | }
6 | ],
7 | "settings": {}
8 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "terminal.integrated.env.osx": {"PYTHONPATH": "${workspaceFolder}"}
3 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Will Thompson
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 | # microstructure-plotter
2 |
3 | **For those of you in the trenches, I offer ye this tool ⚔️**
4 |
5 | This is a plotting tool designed to help visualize financial market data 📈 and trading system behavior 🤖 on the smallest timescales 🔎.
6 |
7 | As all things in life, when it comes to financial microstructure: _the devil 😈 is in the details_
8 |
9 | Built using [Chaco](https://docs.enthought.com/chaco/index.html) 🌮
10 |
11 | See the [illustrated example](#illustrated-example) for a deep dive into what this plotting tool can uncover 🔎.
12 |
13 | 
14 |
15 | 
16 |
17 | 
18 |
19 | ## Contents
20 |
21 | - [⚠️Disclaimer](#%EF%B8%8Fdisclaimer)
22 | - [✨Features](#features)
23 | - [🧠Illustrated Example](#illustrated-example)
24 | - [🧪Test It Out Right Now](#test-it-out-right-now)
25 | - [🗺️Plots Legend](#%EF%B8%8Fplots-legend)
26 | - [💸Free Advice](#free-advice)
27 | - [✏️Data Schema](#%EF%B8%8Fdata-schema)
28 | - [🤔What is "Market Microstructure"?](#what-is-market-microstructure)
29 | - [🛞Installation](#installation)
30 | - [ 📜Reqs](#reqs)
31 | - [𓊍To Do](#𓊍to-do)
32 | - [Citation](#citation)
33 | - [License](#license)
34 |
35 | ## ⚠️Disclaimer
36 |
37 | There is no α here. This is just a fancy wrapper on top of an open-source plotting package.
38 |
39 | You should **B.Y.O.A** (_Bring Your Own Alpha_).
40 |
41 | **However**...for the trained eye 👁️, this simple type of visualization can be immensely helpful.
42 |
43 | ## ✨Features
44 |
45 | Here are some useful features to take note of 🥁:
46 |
47 | 1. ✨Plot **multiple** 🤹, asynchronous (unsampled) microstructure elements together on **the sample plot**: order book quotes, trades, orders, order acks, fills etc etc
48 | 2. ✨Analyze events on seconds, milliseconds, microseconds, _nanoseconds_ 🕳️ : zoom 🔎 in on the **most granular time unit** available to microstructure behavior
49 | 3. ✨All axes are 🔗**linked** : 🔎 _zoom in on one product, see what's happening in all others at that same timestamp_
50 |
51 | ## 🧠Illustrated Example
52 |
53 | To show why a tool like this might be useful, [here](/examples/README.md) is an example motivated from things seen in the wild.
54 |
55 | Note: This data was painstakingly created _by hand_ 🤌 to appear quasi-realistic. I am not an artist 🧑🎨 nor is this real 🌎 data.
56 |
57 | See the [legend](#%EF%B8%8Fplots-legend) 👇 to understand the plots further.
58 |
59 | ## 🧪Test It Out Right Now
60 |
61 | After installing the package either via [pip](#installation) or via [setup.py](#reqs), run this quick line to see this in action right now:
62 |
63 | ``` bash examples/example_1/plot_example.sh ```
64 |
65 | or
66 |
67 | ``` python examples/example_1/plot_example.py ```
68 |
69 | **Right-click** 🖱️ to zoom in on different parts of the plot to see what is happening on **smaller** and **_smaller_** timescales 🔎. Consult the [legend](#plots-legend) or [illustrated example](#illustrated-example) for more clarity on how to interpret the plots.
70 |
71 | ## 🗺️Plots Legend
72 |
73 | [Here](/docs/legend/README.md) is a complete legend of everything the plotter can visualize.
74 |
75 | ## 💸Free Advice
76 |
77 | 1. _Don't log in prod_ ✏️ (unless logging isn't occuring on the hot path).
78 | 2. Make sure the timestamps are from ⏰ _synchronized clocks_ ⏰ (better if geosync'd/GPS) with enough precision, otherwise these plots will be uninformative or misleading. If you don't trust others' timestamps, do your own capture.
79 | 3. This plotter is _memory intensive_ 🧠. Don't try to plot too much at once.
80 |
81 | ## ✏️Data Schema
82 |
83 | This plotting module expects a specific [data schema](/docs/schema/README.md).
84 |
85 | ## 🤔What is "Market Microstructure"?
86 |
87 | _For the uninitiated, here is a [⏰ 30 second primer ⏰ on market microstructure](/docs/micro_primer/README.md)_.
88 |
89 | ## 🛞Installation
90 |
91 | You can install the repo using pip directly:
92 |
93 | ```pip install git+https://github.com/will-thompson-k/microstructure-plotter```
94 |
95 | Alternatively you can use the setup.py.
96 |
97 | ## 📜Reqs
98 |
99 | Python 3.7+ recommended.
100 |
101 | Here are the package requirements (found in requirements.txt)
102 |
103 | - [ ] chaco==5.1.0
104 | - [ ] enable==5.3.1
105 | - [ ] traits==6.4.1
106 | - [ ] traitsui==7.4.2
107 | - [ ] pandas==1.3.5
108 | - [ ] pyqt
109 |
110 | ## 𓊍To Do
111 |
112 | - [ ] add more order types: modifies, equity order types, etc.
113 | - [ ] add "decision boundary" of model: bid/ask edges, etc.
114 | - [ ] unit tests
115 |
116 | ## Citation
117 |
118 | ```python
119 | @misc{microstructure-plotter,
120 | author = {Thompson, Will},
121 | url = {https://github.com/will-thompson-k/microstructure-plotter},
122 | year = {2023}
123 | }
124 | ```
125 | ## License
126 |
127 | MIT
--------------------------------------------------------------------------------
/assets/didnt_send_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/will-thompson-k/microstructure-plotter/ce5a1c5326c5709523758c8942a25fd8db45c8ee/assets/didnt_send_1.png
--------------------------------------------------------------------------------
/assets/didnt_send_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/will-thompson-k/microstructure-plotter/ce5a1c5326c5709523758c8942a25fd8db45c8ee/assets/didnt_send_2.png
--------------------------------------------------------------------------------
/assets/didnt_send_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/will-thompson-k/microstructure-plotter/ce5a1c5326c5709523758c8942a25fd8db45c8ee/assets/didnt_send_3.png
--------------------------------------------------------------------------------
/assets/erratic_valuation_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/will-thompson-k/microstructure-plotter/ce5a1c5326c5709523758c8942a25fd8db45c8ee/assets/erratic_valuation_1.png
--------------------------------------------------------------------------------
/assets/erratic_valuation_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/will-thompson-k/microstructure-plotter/ce5a1c5326c5709523758c8942a25fd8db45c8ee/assets/erratic_valuation_2.png
--------------------------------------------------------------------------------
/assets/erratic_valuation_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/will-thompson-k/microstructure-plotter/ce5a1c5326c5709523758c8942a25fd8db45c8ee/assets/erratic_valuation_3.png
--------------------------------------------------------------------------------
/assets/erratic_valuation_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/will-thompson-k/microstructure-plotter/ce5a1c5326c5709523758c8942a25fd8db45c8ee/assets/erratic_valuation_4.png
--------------------------------------------------------------------------------
/assets/legend.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/will-thompson-k/microstructure-plotter/ce5a1c5326c5709523758c8942a25fd8db45c8ee/assets/legend.png
--------------------------------------------------------------------------------
/assets/order_book.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/will-thompson-k/microstructure-plotter/ce5a1c5326c5709523758c8942a25fd8db45c8ee/assets/order_book.png
--------------------------------------------------------------------------------
/assets/plotter_video.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/will-thompson-k/microstructure-plotter/ce5a1c5326c5709523758c8942a25fd8db45c8ee/assets/plotter_video.gif
--------------------------------------------------------------------------------
/assets/tts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/will-thompson-k/microstructure-plotter/ce5a1c5326c5709523758c8942a25fd8db45c8ee/assets/tts.png
--------------------------------------------------------------------------------
/assets/zoomed_out_events.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/will-thompson-k/microstructure-plotter/ce5a1c5326c5709523758c8942a25fd8db45c8ee/assets/zoomed_out_events.png
--------------------------------------------------------------------------------
/docs/legend/README.md:
--------------------------------------------------------------------------------
1 | # Legend
2 |
3 | Here is a legend of the plot points to expect within a plot:
4 |
5 | 
6 |
7 | Note that the plotter can plot both "sim" and "prod" fills on the same plot. The only difference is the color of the plot points.
8 |
9 | To enable the legend in the plotter, set the flag in the constructor. Default is turned off.
10 |
11 | ## Interpretation
12 |
13 | Here are what these plot points mean:
14 |
15 | ### Quote & Trade Data
16 |
17 | - **ask price**: Top-of-book (TOB), "level 1" price of the best offer in the order book.
18 | - **bid price**: Top-of-book (TOB), "level 1" price of the best bid in the order book.
19 | - **micro price**: The "instanteous" price of the asset. Depends on your philosophy: is this "inverse weighted average"? is this "mid price"? Dealer's choice.
20 | - **trade price**: The price of a market trade event or "match" between a market maker and taker.
21 |
22 | ### System Data
23 |
24 | - **val price**: The "valuation" or fair value or predicted future value of the asset at any given time. Subjective.
25 | - **cancel order price**: The price of a cancellation order sent to the exchange.
26 | - **cancel order ack**: The acknowledgement or "ack" from the exchange of a cancellation request.
27 | - **new order price**: The price of a new order sent to the exchange.
28 | - **new order ack**: The acknowledgement or "ack" from the exchange of a new order request.
29 | - **order reject**: The rejection of a request (new, cancel, modify) of an order request. You probably got filled when you didn't want to.
30 | - **aggressive buy price**: The price at which the system was filled for an "aggressive" buy order (i.e. you are the taker).
31 | - **aggressive sell price**: The price at which the system was filled for an "aggressive" sell order (i.e. you are the taker).
32 | - **passive buy price**: The price at which the system was filled for a "passive" buy order (i.e. you are the market maker).
33 | - **passive sell price**: The price at which the system was filled for a "passive" sell order (i.e. you are the market maker).
--------------------------------------------------------------------------------
/docs/micro_primer/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Microstructure Primer
3 |
4 | Markets can be decomposed into exchanges and the participants that trade on those exchanges
5 |
6 | 
7 |
8 | ## Market Makers:
9 |
10 | **"market makers" or "scalpers" or "liquidity providers":** those offering to provide a quantity needed for a theoretical premium over what they think the security is worth.
11 |
12 | The market makers compete with one another either by providing **better prices**, or **offering more size**, or having **waited around in a queue longer** (time priority), or **paying for the right to match first** (think "free" brokerage companies selling your orders). Their price and quantities are **aggregated** by the exchange to form a **"central limit order book"** (CLOB), which reflect the current overall "best market price" to buy or sell a security at any time.
13 |
14 | ## Takers:
15 |
16 | **"takers"**: those who want a certain amount of a security and are willing to "take" the market makers' price. They could want to get into that position for a number of reasons:
17 |
18 | - they think the _market is mispriced_ (relative to their own view on their longer time horizon)
19 | - they are trying to _hedge a risk_ in their portfolio
20 | - they are executing on behalf of a client
21 |
22 | Of course, most sophisticated players don't fall cleanly into either of these categories.
23 |
24 | Generally, there are _more market makers_ than _takers_ at any given time.
25 |
26 | ## Exchange Software
27 |
28 | The exchange provides the platform (i.e. software) for the makers and takers to meet and trade. In the event that a taker wants to initiate a trade (be the "aggressor"), the exchange has a **matching engine** with a series of rules to determine how much of the taker's order to allocate to each of the market makers' orders (the "passive parties"). _Different matching engines rules incentivize different types of competition among market makers._
29 |
30 | The exchange software has a number of connected nodes, chief among them:
31 |
32 | - **an order book**: a data structure aggregating the market makers' orders into "the market" for a given security
33 | - **a matching engine**: adds orders to the order book and executes trades between parties, allocating a given taker's order among the market makers
34 | - **a pricefeed**: disseminates quotes, trades, etc - changes in public market state (unless it's a dark pool!)
35 | - **an order gateway**: an API for participants to communicate their price and quantity preferences to the exchange - depending on the exchange, there might be a multitude of order types (particularly if there are restrictions like REG NMS)
36 |
37 | How these participants interact, their impact on market prices, and the way exchange software behave are ✨**market microstructure**✨.
38 |
39 | ## Latency
40 |
41 | Since most of these participants are actually **trading algos** 🤖, the timescales at which all these interactions occur is _quite small_. In many web-based AI applications, **25 milliseconds (ms)** is considered a bleeding edge response time (also accounting for the fact that internet is a very unpredictable and slow medium). For most of these market participants to compete, they need response times of **<1 microsecond (us)** ⏰.
42 |
43 | The pace at which market prices change depends on the regime. If it's 7pm on a Thursday, there may be barely any updates within an hour. If it's [**non-farm payrolls**](https://www.cnbc.com/nonfarm-payrolls/), then there is likely a _ton_ of activity and the exchange is likely overloaded/queuing.
44 |
45 | This means that raw market data is fundamentally **_asynchronous_, _non-uniform_, _event-level_ time series**📈.
46 |
47 | ## Resources
48 |
49 | For a very confusing, vague explanation, read the Wikipedia article [here](https://en.wikipedia.org/wiki/Market_microstructure).
--------------------------------------------------------------------------------
/docs/schema/README.md:
--------------------------------------------------------------------------------
1 | # Schema
2 |
3 | Here are the fields and data types expected for each data input. Only the quote data is a required input. All others are optional.
4 |
5 | ## Quote Data
6 |
7 | - **timestamp:** Nanosecond precision EPOCH timestamp INT
8 | - **symbol:** STR
9 | - **bid_price:** FLOAT
10 | - **ask_price:** FLOAT
11 | - **micro_price:** FLOAT
12 |
13 | ## Trade Data
14 |
15 | - **timestamp:** Nanosecond precision EPOCH timestamp INT
16 | - **symbol:** STR
17 | - **price:** FLOAT
18 |
19 | ## Fill Data
20 |
21 | - **timestamp:** Nanosecond precision EPOCH timestamp INT
22 | - **symbol:** STR
23 | - **price:** FLOAT
24 | - **is_buy:** BOOL
25 | - **is_aggressive:** BOOL
26 |
27 | ## Orders Data
28 |
29 | - **timestamp:** Nanosecond precision EPOCH timestamp INT
30 | - **symbol:** STR
31 | - **price:** FLOAT
32 | - **is_new:** BOOL
33 | - **is_cancel:** BOOL
34 | - **is_reject:** BOOL
35 | - **is_ack:** BOOL
36 |
37 | ## Valuation Data
38 |
39 | - **timestamp:** Nanosecond precision EPOCH timestamp INT
40 | - **symbol:** STR
41 | - **theo_price:** FLOAT
42 |
43 | ## Code
44 |
45 | Reference [microplot::schema](/microplot/schema.py).
--------------------------------------------------------------------------------
/examples/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/will-thompson-k/microstructure-plotter/ce5a1c5326c5709523758c8942a25fd8db45c8ee/examples/.DS_Store
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | Here is a gallery of hand-crafted examples to illustrate the value of such a tool.
4 |
5 | Disclaimer: This data was painstakingly created _by hand_ 🤌 to appear quasi-realistic. I am not an artist 🧑🎨 nor is this real 🌎 data.
6 |
7 | You may want to first check out [the legend](/docs/legend/README.md) and perhaps [the 30 second primer on microstructure](/docs/micro_primer/README.md) prior.
8 |
9 | ## Example
10 |
11 | This example contains 3 different "events" of interest commonly found in the wild:
12 |
13 | 1. Event 1: [💸 Erratic valuation](#event-1--erratic-valuation)
14 | 2. Event 2: [😔 Didn't send order](#event-2--didnt-send-order)
15 | 3. Event 3: [🚀 Trade-through-the-stack event](#event-3--trade-through-the-stack-event)
16 |
17 | 
18 |
19 |
20 | ### Event 1: 💸 Erratic valuation
21 |
22 | Here is an example of a valuation gone amuck and leading to bad trades.
23 |
24 | 
25 |
26 | The valuation jumped through the best offer and bid and sent crossing/taking/aggressive orders that were filled. This all happened in <2 seconds.
27 |
28 | **⚠️ IMPORTANT Note ⚠️:** You won't get filled at those prices in reality. The matchine engine will provide "price improvement". I didn't feel like re-working the example. Let us both pretend those fills happened at the best bid and offer instead.
29 |
30 | Zoom in further via right clicking 🖱️ to see further details 🔎.
31 |
32 | 
33 |
34 | Here we zoom in further to the milliseconds timescale to see what is happening.
35 |
36 | Zoom in further via right clicking 🖱️ to see further details 🔎.
37 |
38 | 
39 |
40 | On the ⏰ _microsecond_ ⏰ timescale, we can start to appreciate what happened....
41 |
42 | 1. Our 🤖 trading algo's theoretical value spiked through the offer 3 ticks/levels, causing the algo to send cancels for those 3 levels.
43 |
44 | 2. There clearly is also logic for aggressive/taking orders based on this theoretical price. The system sent such a new order and got immediately filled. (Note again it's pratically impossible to get filled at that level and you would have bought a level below.)
45 |
46 | 3. The exchange acks from these requests came back serially, probably due to being sent to the same exchange order router or having an order gateway priority queue internally (this varies by exchange architecture).
47 |
48 | Now looking at the other blip, we see that the same thing happened where the trading algo sold due to a spike down in valuation.
49 |
50 | 
51 |
52 | For a refresher on plot items, check out [the legend](/docs/legend/README.md).
53 |
54 | ### Event 2: 😔 Didn't send order
55 |
56 | 
57 |
58 | In the second event, we see that the trading algo 🤖 was "run over" during a series of trades where both correlated products ticked down. In this situation, a market trade pushed the market down by making the "bid turn offer". This is a common occurence in price point transitions.
59 |
60 | The question is: could have the trading algo 🤖 avoided the fill? Did it try to?
61 |
62 | Zoom in further via right clicking 🖱️ to see further details 🔎.
63 |
64 | 
65 |
66 | We can also see that the 🤖 trading algo's theoretical valuation for Coin B is responding to _something_ and seems informative (most likely Coin A's pricepoint changes).
67 |
68 | Zoom in further via right clicking 🖱️ to see further details 🔎.
69 |
70 | 
71 |
72 | Finally, the picture becomes a little clearer. Coin A's market ticked down ("bid turned offer"). We see that the trading algo's valuation in Coin B did in fact reflect this information, yet... the system did not even attempt to send a cancel/modify request (let alone attempt to tag along and cross Coin B).
73 |
74 | This is suspicious, and needs to be investigated further to see if it is a bug 🐛.
75 |
76 | ### Event 3: 🚀 Trade-through-the-stack event
77 |
78 | Finally, we examine the 3rd event, where a very large trade occurs in the market. The trade is so large, it exhausts multiple levels in the order book. This is known as a "trade through the stack" or "sweep" depending on who you ask.
79 |
80 | 
81 |
82 | We see that the TTS was first reported in Coin A. The 🤖 trading algo's valuation immediately reflects this information in Coin B's theoretical valuation.
83 |
84 | Responding to this change in valuation, the system attempts to cancel it's offer at the Best Offer and send a New Order several levels through the book (likely, an aggressive order). Notice that the exchange rejected both of these requests and later showed that the system was filled in Coin B. We can see later that a TTS also happened in Coin B, likely by the same aggressor as Coin A.
85 |
86 | Reporting of events can sometimes come in an on odd order depending on the exchange design. However, it looks like the trading algo's behavior was optimal in this situation - it just wasn't able to successfully act. This is just the cost of doing business.
87 |
--------------------------------------------------------------------------------
/examples/example_1/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/will-thompson-k/microstructure-plotter/ce5a1c5326c5709523758c8942a25fd8db45c8ee/examples/example_1/.DS_Store
--------------------------------------------------------------------------------
/examples/example_1/fake_data/fill_data.csv:
--------------------------------------------------------------------------------
1 | timestamp,symbol,price,is_buy,is_aggressive
2 | 1672543072001007000,COIN_B,10.0,True,False
3 | 1672542962000015000,COIN_B,12.0,True,True
4 | 1672543099002670000,COIN_B,10.0,False,False
5 | 1672542963000005000,COIN_B,9.0,False,True
6 |
7 |
--------------------------------------------------------------------------------
/examples/example_1/fake_data/order_data.csv:
--------------------------------------------------------------------------------
1 | timestamp,symbol,price,is_new,is_cancel,is_reject,is_ack
2 | 1672542962000000000,COIN_B,11.0,False,True,False,False
3 | 1672542962000000000,COIN_B,12.0,False,True,False,False
4 | 1672542962000000000,COIN_B,13.0,False,True,False,False
5 | 1672542962000010000,COIN_B,12.0,True,False,False,False
6 | 1672542962000500000,COIN_B,11.0,False,True,False,True
7 | 1672542962000600000,COIN_B,12.0,False,True,False,True
8 | 1672542962000800000,COIN_B,13.0,False,True,False,True
9 | 1672542962000900000,COIN_B,12.0,True,False,False,True
10 | 1672542963000000000,COIN_B,10.0,False,True,False,False
11 | 1672542963000000000,COIN_B,9.0,False,True,False,False
12 | 1672542963000000000,COIN_B,8.0,False,True,False,False
13 | 1672542963000001000,COIN_B,9.0,True,False,False,False
14 | 1672542963000050000,COIN_B,10.0,False,True,False,True
15 | 1672542963000060000,COIN_B,9.0,False,True,False,True
16 | 1672542963000070000,COIN_B,8.0,False,True,False,True
17 | 1672542963000080000,COIN_B,8.0,True,False,False,True
18 | 1672543099002650000,COIN_B,10.0,False,True,False,False
19 | 1672543099002651000,COIN_B,24.0,True,False,False,False
20 | 1672543099002660000,COIN_B,10.0,False,False,True,True
21 | 1672543099002661000,COIN_B,24.0,False,False,True,True
22 |
--------------------------------------------------------------------------------
/examples/example_1/fake_data/quote_data.csv:
--------------------------------------------------------------------------------
1 | timestamp,symbol,bid_price,ask_price,micro_price
2 | 1672542960000000000,COIN_A,4.0,5.0,4.5
3 | 1672542961000000000,COIN_B,10.0,11.0,10.5
4 | 1672542971000000000,COIN_A,4.0,5.0,4.5
5 | 1672542972000000000,COIN_A,4.0,5.0,4.5
6 | 1672543072000000000,COIN_A,4.0,5.0,4.5
7 | 1672543072001000000,COIN_A,3.0,4.0,3.5
8 | 1672543072001010000,COIN_B,9.0,10.0,9.5
9 | 1672543072001020000,COIN_B,9.0,10.0,9.5
10 | 1672543072001070000,COIN_B,9.0,10.0,9.5
11 | 1672543072002070000,COIN_A,3.0,4.0,3.5
12 | 1672543072002370000,COIN_A,4.0,5.0,4.5
13 | 1672543072002380000,COIN_B,10.0,11.0,10.5
14 | 1672543072002480000,COIN_A,4.0,5.0,4.5
15 | 1672543072002580000,COIN_A,3.0,4.0,3.5
16 | 1672543072002680000,COIN_B,9.0,10.0,9.5
17 | 1672543082002680000,COIN_A,3.0,4.0,3.5
18 | 1672543087002680000,COIN_B,9.0,10.0,9.5
19 | 1672543099002680000,COIN_A,15.0,16.0,15.5
20 | 1672543099002690000,COIN_B,25.0,26.0,25.5
21 | 1672543099002700000,COIN_A,15.0,16.0,15.5
22 | 1672543099002710000,COIN_B,25.0,26.0,25.5
23 | 1672543099003210000,COIN_A,15.0,16.0,15.5
24 | 1672543099063210000,COIN_B,25.0,26.0,25.5
25 | 1672543099064210000,COIN_A,15.0,16.0,15.5
--------------------------------------------------------------------------------
/examples/example_1/fake_data/trade_data.csv:
--------------------------------------------------------------------------------
1 | timestamp,symbol,price
2 | 1672543071000000000,COIN_A,4.0
3 | 1672543071100000000,COIN_A,4.0
4 | 1672543071200000000,COIN_A,4.0
5 | 1672543071500000000,COIN_A,4.0
6 | 1672543071900000000,COIN_A,4.0
7 | 1672543071910000000,COIN_A,4.0
8 | 1672543071950000000,COIN_A,4.0
9 | 1672543071970000000,COIN_A,4.0
10 | 1672543071990000000,COIN_A,4.0
11 | 1672543072001000000,COIN_A,4.0
12 | 1672543072001001000,COIN_B,10.0
13 | 1672543072001005000,COIN_B,10.0
14 | 1672543072001009000,COIN_B,10.0
15 | 1672543072002170000,COIN_A,4.0
16 | 1672543072002270000,COIN_A,4.0
17 | 1672543072002280000,COIN_B,10.0
18 | 1672543099002650000,COIN_A,4.0
19 | 1672543099002650000,COIN_A,5.0
20 | 1672543099002650000,COIN_A,6.0
21 | 1672543099002650000,COIN_A,7.0
22 | 1672543099002650000,COIN_A,8.0
23 | 1672543099002650000,COIN_A,9.0
24 | 1672543099002650000,COIN_A,10.0
25 | 1672543099002650000,COIN_A,11.0
26 | 1672543099002650000,COIN_A,12.0
27 | 1672543099002650000,COIN_A,13.0
28 | 1672543099002650000,COIN_A,14.0
29 | 1672543099002650000,COIN_A,15.0
30 | 1672543099002670000,COIN_B,10.0
31 | 1672543099002670000,COIN_B,11.0
32 | 1672543099002670000,COIN_B,12.0
33 | 1672543099002670000,COIN_B,13.0
34 | 1672543099002670000,COIN_B,14.0
35 | 1672543099002670000,COIN_B,15.0
36 | 1672543099002670000,COIN_B,16.0
37 | 1672543099002670000,COIN_B,17.0
38 | 1672543099002670000,COIN_B,18.0
39 | 1672543099002670000,COIN_B,19.0
40 | 1672543099002670000,COIN_B,20.0
41 | 1672543099002670000,COIN_B,21.0
42 | 1672543099002670000,COIN_B,22.0
43 | 1672543099002670000,COIN_B,23.0
44 | 1672543099002670000,COIN_B,24.0
45 | 1672543099002670000,COIN_B,25.0
--------------------------------------------------------------------------------
/examples/example_1/fake_data/val_data.csv:
--------------------------------------------------------------------------------
1 | timestamp,symbol,theo_price
2 | 1672542960000000000,COIN_B,10.4
3 | 1672542960010000000,COIN_B,10.25
4 | 1672542961000000000,COIN_B,10.55
5 | 1672542962000000000,COIN_B,13.0
6 | 1672542963000000000,COIN_B,8.0
7 | 1672542964000000000,COIN_B,10.9
8 | 1672542965000000000,COIN_B,10.25
9 | 1672542967000000000,COIN_B,10.6
10 | 1672542968000000000,COIN_B,10.75
11 | 1672542969000000000,COIN_B,10.4
12 | 1672542970000000000,COIN_B,10.6
13 | 1672542971000000000,COIN_B,10.3
14 | 1672542972000000000,COIN_B,10.9
15 | 1672542973000000000,COIN_B,10.3
16 | 1672542974000000000,COIN_B,10.4
17 | 1672542975000000000,COIN_B,10.2
18 | 1672542977000000000,COIN_B,10.9
19 | 1672542978000000000,COIN_B,10.1
20 | 1672542979000000000,COIN_B,10.2
21 | 1672542980000000000,COIN_B,10.6
22 | 1672543030000000000,COIN_B,10.1
23 | 1672543050000000000,COIN_B,10.6
24 | 1672543072000000000,COIN_B,10.1
25 | 1672543072001000000,COIN_B,9.2
26 | 1672543072001010000,COIN_B,9.8
27 | 1672543072001020000,COIN_B,9.1
28 | 1672543072001070000,COIN_B,9.9
29 | 1672543072002070000,COIN_B,9.2
30 | 1672543072002370000,COIN_B,10.5
31 | 1672543072002380000,COIN_B,10.3
32 | 1672543072002480000,COIN_B,10.8
33 | 1672543072002580000,COIN_B,9.5
34 | 1672543072002680000,COIN_B,9.3
35 | 1672543082002680000,COIN_B,9.2
36 | 1672543087002680000,COIN_B,9.1
37 | 1672543099002650000,COIN_B,25.0
38 | 1672543099002670000,COIN_B,25.5
39 | 1672543099002690000,COIN_B,25.5
40 | 1672543099002700000,COIN_B,25.5
41 | 1672543099002710000,COIN_B,25.5
42 | 1672543099003210000,COIN_B,25.5
43 | 1672543099063210000,COIN_B,25.5
44 | 1672543099064210000,COIN_B,25.5
--------------------------------------------------------------------------------
/examples/example_1/plot_example.py:
--------------------------------------------------------------------------------
1 | """
2 | Interactive microstructure plot example using fake data. Python function call.
3 | """
4 |
5 | from microplot.scripts.plot_csv import run_plotter_csv
6 |
7 | fake_quote_data_path = "examples/example_1/fake_data/quote_data.csv"
8 | fake_trade_data_path = "examples/example_1/fake_data/trade_data.csv"
9 | fake_fill_data_path = "examples/example_1/fake_data/fill_data.csv"
10 | fake_orders_data_path = "examples/example_1/fake_data/order_data.csv"
11 | fake_valuation_data_path = "examples/example_1/fake_data/val_data.csv"
12 |
13 | args = ["-quote_data_file",fake_quote_data_path,"-trade_data_file",fake_trade_data_path,"-fill_data_sim_file",fake_fill_data_path,"-orders_data_file",fake_orders_data_path,"-valuation_data_file",fake_valuation_data_path]
14 |
15 | if __name__ == "__main__":
16 | run_plotter_csv(args)
--------------------------------------------------------------------------------
/examples/example_1/plot_example.sh:
--------------------------------------------------------------------------------
1 | # Interactive microstructure plot example using fake data.
2 | # Bash script.
3 | python microplot/scripts/plot_csv.py -quote_data_file examples/example_1/fake_data/quote_data.csv -trade_data_file examples/example_1/fake_data/trade_data.csv -fill_data_sim_file examples/example_1/fake_data/fill_data.csv -orders_data_file examples/example_1/fake_data/order_data.csv -valuation_data_file examples/example_1/fake_data/val_data.csv
--------------------------------------------------------------------------------
/microplot/__init__.py:
--------------------------------------------------------------------------------
1 | # pylint: disable=C0111
2 |
3 | __all__ = ["scripts","data","plotter","schema"]
4 |
--------------------------------------------------------------------------------
/microplot/data.py:
--------------------------------------------------------------------------------
1 | """
2 | This module contains the PlotterDataClass for the plotting object.
3 | """
4 |
5 | from dataclasses import dataclass
6 | import pandas as pd
7 | from typing import Tuple, List
8 | from microplot.schema import (
9 | QUOTE_DATA_COLUMNS,
10 | TRADE_DATA_COLUMNS,
11 | FILL_DATA_COLUMNS,
12 | ORDERS_DATA_COLUMNS,
13 | VAL_DATA_COLUMNS,
14 | )
15 |
16 |
17 | @dataclass
18 | class PlotterDataClass:
19 | """
20 | A dataclass to pass data to the microstructure plotter.
21 |
22 | NOTE: See required fields in each property setter function or the data schema.
23 |
24 | _quote_data (pd.DataFrame): time series of best bid and offer prices and microprice ("weight ave")
25 | _trade_data (pd.DataFrame): time series of trade prices.
26 | _fill_data_sim (pd.DataFrame): time series of system fill data (but for simulations).
27 | _fill_data_prod (pd.DataFrame): time series of system fill data (but for prod).
28 | _orders (pd.DataFrame): time series of exchange order activity. Currently only news, cancels, rejects.
29 | _val_data (pd.DataFrame): time series of system valuation data-> theoretical price, "bid edge", "ask edge" (if market-making).
30 | """
31 |
32 | _quote_data: pd.DataFrame = None
33 | _trade_data: pd.DataFrame = None
34 | _fill_data_sim: pd.DataFrame = None
35 | _fill_data_prod: pd.DataFrame = None
36 | _orders: pd.DataFrame = None
37 | _val_data: pd.DataFrame = None
38 |
39 | def get_symbols(self) -> List[str]:
40 | """
41 | Method for grabbing all symbols in dataclass.
42 |
43 | Returns:
44 | List[str]: list of symbols found across all data series.
45 | """
46 | symbol_set = set()
47 | time_series = [
48 | self._quote_data,
49 | self.trade_data,
50 | self._fill_data_prod,
51 | self._fill_data_sim,
52 | self._orders,
53 | self._val_data,
54 | ]
55 | for series in time_series:
56 | series_symbols = series["symbol"].ravel() if series is not None else None
57 | if series_symbols is not None:
58 | for symbol in series_symbols:
59 | symbol_set.add(symbol)
60 |
61 | return list(symbol_set)
62 |
63 | @staticmethod
64 | def _validate_timestamps(timeseries: pd.Series) -> bool:
65 | """
66 | Timestamps should be EPOCH nanosecond precision, integer timestamps.
67 |
68 | This equates to 19 digit ints.
69 |
70 | Args:
71 | ts (pd.Series): pandas Series object containing timestamps.
72 |
73 | Returns:
74 | bool: true/false indicating whether timestamps are valid.
75 | """
76 | return all(timeseries.apply(lambda x: len(list(str(x))) == 19)) == True
77 |
78 | @staticmethod
79 | def _convert_timestamps(timeseries: pd.Series) -> pd.Series:
80 | """
81 | Convert timestamps from nanosecond ints -> second-level floats.
82 |
83 | Args:
84 | ts (pd.Series): pandas Series object containing timestamps.
85 |
86 | Returns:
87 | pd.Series: converted timeseries.
88 | """
89 | return timeseries.apply(lambda x: x * 1e-9)
90 |
91 | @classmethod
92 | def _check_columns(cls, columns: List[str], data: pd.DataFrame, name: str):
93 | """
94 | Checks a pandas dataframe timeseries that it contains the required fields
95 | prior to setting the value. Also sanity-checks the timestamps are valid.
96 |
97 | Args:
98 | columns (List[str]): Names of required columns for data schema.
99 | data (pd.DataFrame): Pandas dataframe containing time series.
100 | name (str): Name of time series to be set.
101 |
102 | Raises:
103 | Exception: Required column not found.
104 | """
105 |
106 | assert type(data) is pd.DataFrame
107 |
108 | for column in columns:
109 | if column not in data.columns:
110 | raise Exception(f"column:{column} not in {name}")
111 |
112 | assert "timestamp" in data.columns
113 |
114 | assert cls._validate_timestamps(data["timestamp"])
115 |
116 | data["timestamp"] = cls._convert_timestamps(data["timestamp"])
117 |
118 | @property
119 | def quote_data(self) -> pd.DataFrame:
120 | """
121 | Quote data property getter.
122 |
123 | Returns:
124 | pd.DataFrame: Quote data.
125 | """
126 | return self._quote_data
127 |
128 | @quote_data.setter
129 | def quote_data(self, data: pd.DataFrame):
130 | """
131 | Quote data property setter.
132 |
133 | Args:
134 | data (pd.DataFrame): Quote data.
135 | """
136 | # timestamp, symbol, bid_price, ask_price, micro_price
137 |
138 | self._check_columns(QUOTE_DATA_COLUMNS, data, "quote_data")
139 | self._quote_data = data
140 |
141 | @property
142 | def trade_data(self) -> pd.DataFrame:
143 | """
144 | Trade data property getter.
145 |
146 | Returns:
147 | pd.DataFrame: Trade data.
148 | """
149 | return self._trade_data
150 |
151 | @trade_data.setter
152 | def trade_data(self, data: pd.DataFrame):
153 | """
154 | Trade data property setter.
155 |
156 | Args:
157 | data (pd.DataFrame): Trade data.
158 | """
159 | # timestamp, symbol, price
160 |
161 | self._check_columns(TRADE_DATA_COLUMNS, data, "trade_data")
162 | self._trade_data = data
163 |
164 | @property
165 | def fill_data_sim(
166 | self,
167 | ) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame, pd.DataFrame]:
168 | """
169 | Fill data sim property getter.
170 |
171 | Returns:
172 | Tuple[pd.DataFrame,pd.DataFrame,pd.DataFrame,pd.DataFrame]:
173 | [buy_aggressive_fills, buy_passive_fills, sell_aggressive_fills, sell_passive_fills]
174 | """
175 |
176 | if self._fill_data_sim is None:
177 | return None, None, None, None
178 |
179 | buy_aggressive_fills = self._fill_data_sim[
180 | self._fill_data_sim.is_buy & self._fill_data_sim.is_aggressive
181 | ]
182 | buy_passive_fills = self._fill_data_sim[
183 | self._fill_data_sim.is_buy & ~self._fill_data_sim.is_aggressive
184 | ]
185 | sell_aggressive_fills = self._fill_data_sim[
186 | ~self._fill_data_sim.is_buy & self._fill_data_sim.is_aggressive
187 | ]
188 | sell_passive_fills = self._fill_data_sim[
189 | ~self._fill_data_sim.is_buy & ~self._fill_data_sim.is_aggressive
190 | ]
191 |
192 | return (
193 | buy_aggressive_fills,
194 | buy_passive_fills,
195 | sell_aggressive_fills,
196 | sell_passive_fills,
197 | )
198 |
199 | @fill_data_sim.setter
200 | def fill_data_sim(self, data: pd.DataFrame):
201 | """
202 | Fill data property setter.
203 |
204 | Args:
205 | data (pd.DataFrame): Fill data.
206 | """
207 | # timestamp, symbol, price, is_buy, is_aggressive
208 |
209 | self._check_columns(FILL_DATA_COLUMNS, data, "fill_data_sim")
210 | self._fill_data_sim = data
211 |
212 | @property
213 | def fill_data_prod(
214 | self,
215 | ) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame, pd.DataFrame]:
216 | """
217 | Fill data prod property getter.
218 |
219 | Returns:
220 | Tuple[pd.DataFrame,pd.DataFrame,pd.DataFrame,pd.DataFrame]:
221 | [buy_aggressive_fills, buy_passive_fills, sell_aggressive_fills, sell_passive_fills]
222 | """
223 | if self._fill_data_prod is None:
224 | return None, None, None, None
225 |
226 | buy_aggressive_fills = self._fill_data_prod[
227 | self._fill_data_prod.is_buy & self._fill_data_prod.is_aggressive
228 | ]
229 | buy_passive_fills = self._fill_data_prod[
230 | self._fill_data_prod.is_buy & ~self._fill_data_prod.is_aggressive
231 | ]
232 | sell_aggressive_fills = self._fill_data_prod[
233 | ~self._fill_data_prod.is_buy & self._fill_data_prod.is_aggressive
234 | ]
235 | sell_passive_fills = self._fill_data_prod[
236 | ~self._fill_data_prod.is_buy & ~self._fill_data_prod.is_aggressive
237 | ]
238 |
239 | return (
240 | buy_aggressive_fills,
241 | buy_passive_fills,
242 | sell_aggressive_fills,
243 | sell_passive_fills,
244 | )
245 |
246 |
247 | @fill_data_prod.setter
248 | def fill_data_prod(self, data: pd.DataFrame):
249 | """
250 | Fill data property setter.
251 |
252 | Args:
253 | data (pd.DataFrame): Fill data.
254 | """
255 | # timestamp, symbol, price, is_buy, is_aggressive
256 |
257 | self._check_columns(FILL_DATA_COLUMNS, data, "fill_data_prod")
258 | self._fill_data_prod = data
259 |
260 | @property
261 | def orders(
262 | self,
263 | ) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame, pd.DataFrame, pd.DataFrame]:
264 | """
265 | Order data property getter.
266 |
267 | Returns:
268 | Tuple[pd.DataFrame,pd.DataFrame,pd.DataFrame,pd.DataFrame,pd.DataFrame]:
269 | [new_orders,new_order_acks,cancel_orders,cancel_order_acks, rejects]
270 | """
271 | # new, new_ack, cxl, cxl_ack, rej
272 | # TODO: add modifies and all the other types
273 |
274 | if self._orders is None:
275 | return None, None, None, None, None
276 |
277 | new_orders = self._orders[self._orders.is_new & ~self._orders.is_ack]
278 | new_order_acks = self._orders[self._orders.is_new & self._orders.is_ack]
279 | cancel_orders = self._orders[self._orders.is_cancel & ~self._orders.is_ack]
280 | cancel_orders_acks = self._orders[self._orders.is_cancel & self._orders.is_ack]
281 | rejects = self._orders[self._orders.is_reject]
282 |
283 | return new_orders, new_order_acks, cancel_orders, cancel_orders_acks, rejects
284 |
285 | @orders.setter
286 | def orders(self, data: pd.DataFrame):
287 | """
288 | Order data property setter.
289 |
290 | Args:
291 | data (pd.DataFrame): Order data.
292 | """
293 | # timestamp, symbol, price, is_new, is_cancel, is_reject, is_ack
294 |
295 | self._check_columns(ORDERS_DATA_COLUMNS, data, "orders")
296 | self._orders = data
297 |
298 | @property
299 | def val_data(self) -> pd.DataFrame:
300 | """
301 | Valuation data property getter.
302 |
303 | Returns:
304 | pd.DataFrame: valuation data.
305 | """
306 | return self._val_data
307 |
308 |
309 | @val_data.setter
310 | def val_data(self, data: pd.DataFrame):
311 | """
312 | Valuation data property setter.
313 |
314 | Args:
315 | data (pd.DataFrame): Val data.
316 | """
317 | # timestamp, symbol, theo_price
318 |
319 | self._check_columns(VAL_DATA_COLUMNS, data, "val_data")
320 | self._val_data = data
--------------------------------------------------------------------------------
/microplot/plotter.py:
--------------------------------------------------------------------------------
1 | """
2 | This module contains the microstructure plotter.
3 | """
4 |
5 | from enable.api import ComponentEditor
6 | from traits.api import HasTraits, Instance
7 | from traitsui.api import Handler, Item, View
8 |
9 | from chaco.scales.api import CalendarScaleSystem
10 | from chaco.scales_tick_generator import ScalesTickGenerator
11 | from chaco.tools.api import PanTool, ZoomTool
12 | from chaco.api import ArrayPlotData, Plot, PlotAxis, PlotGrid, VPlotContainer
13 |
14 | from microplot.data import PlotterDataClass
15 |
16 |
17 | class DummyPlotterHandler(Handler):
18 | """
19 | This is a "dummy" plotter handler class.
20 | This forces a silent close of the plotter.
21 | """
22 |
23 | def closed(self, info, is_ok):
24 | return
25 |
26 |
27 | class MicroPlotter(HasTraits):
28 | """
29 | This is the microstructure plotting class.
30 | """
31 |
32 | container = Instance(VPlotContainer)
33 |
34 | traits_view = View(
35 | Item("container", editor=ComponentEditor(), show_label=False),
36 | width=900,
37 | height=500,
38 | resizable=True,
39 | title="Microstructure Plotter",
40 | handler=DummyPlotterHandler,
41 | )
42 |
43 | def __init__(self, data: PlotterDataClass, show_legend: bool = False):
44 | """
45 | Args:
46 | data (PlotterDataClass): dataclass for plotting object.
47 | show_legend (bool): flag to show legend on plots. Crowded image. Default = False.
48 | """
49 |
50 | # plotterdataclass ingested from datasource
51 | self._data = data
52 | # flag to show legend in plots (warning: crowds screen)
53 | self._show_legend = show_legend
54 | # find union of symbols across time series
55 | self._symbols = data.get_symbols()
56 |
57 | # to cache Plot objects
58 | self._subplots = []
59 | # to be used later for connecting plots
60 | self._top_plot_indexer = None
61 | self._top_plot_index_range = None
62 | self._top_plot_bottom_axis = None
63 |
64 | super().__init__()
65 |
66 | def plot(self, *args, **kws):
67 | """
68 | One stop "public" method for rendering all plots.
69 |
70 | Call this after instantiating object to render plots.
71 |
72 | Raises:
73 | Exception: HasTraits:: unable to configure.
74 | """
75 |
76 | try:
77 | super().configure_traits(*args, **kws)
78 | except:
79 | raise Exception("Unable to configure traits.")
80 |
81 | def _container_default(self) -> VPlotContainer:
82 | """
83 | Required method for HasTraits class to return a
84 | "Vertical Plot Container" object containing
85 | the plots of interest.
86 |
87 | Returns:
88 | VPlotContainer: Container object with
89 | the subplots of interest.
90 | """
91 |
92 | return self._generate_subplots()
93 |
94 | def _generate_subplots(self) -> VPlotContainer:
95 | """
96 | Method for instantiating the VPlotContainer.
97 | Creates subplot Plot objects for each symbol.
98 |
99 | Returns:
100 | VPlotContainer: Container object with
101 | the subplots of interest.
102 | """
103 |
104 | # run through each symbol, instantiate subplots
105 | for symbol in self._symbols:
106 | plot = self._generate_subplot(symbol)
107 | self._link_subplot(plot)
108 |
109 | # instantiate a container to hold all the plots
110 | container = VPlotContainer(bgcolor="transparent")
111 |
112 | # add all the linked sub-plots to the VPlotContainer
113 | for plot in self._subplots:
114 | container.add(plot)
115 | # clear cache
116 | self._subplots = []
117 |
118 | return container
119 |
120 | def _generate_subplot(self, symbol: str) -> Plot:
121 | """
122 | For a given symbol, instantiate a subplot
123 | timeseries with all the relevant plot datapoints.
124 |
125 | Args:
126 | symbol (str): The symbol of interest.
127 |
128 | Returns:
129 | Plot: The subplot object for a given symbol.
130 | """
131 | # set plot data
132 | data_array = self._set_plot_data_array(symbol)
133 | plot_attributes = set(data_array.list_data())
134 |
135 | # instantiate plot object
136 | plot = Plot(data_array, auto_grid=False, auto_axis=False)
137 |
138 | # render plots
139 | self._render_plots(plot, plot_attributes)
140 |
141 | # setup plot stuff
142 | self._setup_plot(plot, symbol)
143 |
144 | return plot
145 |
146 | def _setup_plot(self, plot: Plot, symbol: str):
147 | """
148 | Setup Plot attributes (zooming, etc).
149 |
150 | Args:
151 | plot (Plot): The Plot class (for a given symbol).
152 | symbol (str): The symbol of interest.
153 | """
154 |
155 | # setup plot details
156 | plot.title = "Symbol: %s " % (symbol)
157 | plot.legend.visible = self._show_legend
158 |
159 | # create pan tool
160 | plot.tools.append(PanTool(plot))
161 | # create zooming ability
162 | zoom_controller = ZoomTool(
163 | component=plot, tool_mode="box", drag_button="right", always_on=True
164 | )
165 | plot.overlays.append(zoom_controller)
166 | # setup plot axis
167 | left_axis = PlotAxis(plot, orientation="left")
168 | plot.overlays.append(left_axis)
169 | # setup plot grid
170 | h_mapper = plot.value_mapper
171 | h_grid = PlotGrid(
172 | mapper=h_mapper,
173 | orientation="horizontal",
174 | component=plot,
175 | line_color="lightgray",
176 | line_style="dot",
177 | )
178 | plot.underlays.append(h_grid)
179 |
180 | def _render_plots(self, plot: Plot, plot_attributes: set):
181 | """
182 | Render Plot elements within instantiated class. Set title and other properties.
183 |
184 | Args:
185 | plot (Plot): The Plot class (for a given symbol).
186 | plot_attributes (set): plot attributes set in data_array object.
187 | """
188 | # quote_data
189 | if "quote_timestamp" in plot_attributes:
190 | plot.plot(
191 | ("quote_timestamp", "quote_bid_price"),
192 | line_width=4,
193 | name="bid_price",
194 | color="black",
195 | line_style="solid",
196 | render_style="connectedhold",
197 | )
198 | plot.plot(
199 | ("quote_timestamp", "quote_ask_price"),
200 | line_width=4,
201 | name="ask_price",
202 | color="black",
203 | line_style="solid",
204 | render_style="connectedhold",
205 | )
206 | plot.plot(
207 | ("quote_timestamp", "quote_micro_price"),
208 | line_width=1,
209 | name="micro_price",
210 | color="blue",
211 | line_style="solid",
212 | render_style="connectedhold",
213 | )
214 |
215 | # trade_data
216 | if "trade_timestamp" in plot_attributes:
217 | plot.plot(
218 | ("trade_timestamp", "trade_price"),
219 | type="scatter",
220 | marker="circle",
221 | name="trade_price",
222 | color="red",
223 | marker_size=8,
224 | render_style="hold",
225 | )
226 |
227 | # aggr_buy_sim
228 | if "sim_aggr_buy_timestamp" in plot_attributes:
229 | plot.plot(
230 | ("sim_aggr_buy_timestamp", "sim_aggr_buy_price"),
231 | type="scatter",
232 | marker="triangle",
233 | name="sim_aggr_buy_price",
234 | color="blue",
235 | marker_size=12,
236 | render_style="hold",
237 | )
238 |
239 | # pass_buy_sim
240 | if "sim_pass_buy_timestamp" in plot_attributes:
241 | plot.plot(
242 | ("sim_pass_buy_timestamp", "sim_pass_buy_price"),
243 | type="scatter",
244 | marker="circle",
245 | name="sim_pass_buy_price",
246 | color="blue",
247 | marker_size=12,
248 | render_style="hold",
249 | )
250 |
251 | # aggr_sell_sim
252 | if "sim_aggr_sell_timestamp" in plot_attributes:
253 | plot.plot(
254 | ("sim_aggr_sell_timestamp", "sim_aggr_sell_price"),
255 | type="scatter",
256 | marker="triangle",
257 | name="sim_aggr_sell_price",
258 | color="gold",
259 | marker_size=12,
260 | render_style="hold",
261 | )
262 |
263 | # pass_sell_sim
264 | if "sim_pass_sell_timestamp" in plot_attributes:
265 | plot.plot(
266 | ("sim_pass_sell_timestamp", "sim_pass_sell_price"),
267 | type="scatter",
268 | marker="circle",
269 | name="sim_pass_sell_price",
270 | color="gold",
271 | marker_size=12,
272 | render_style="hold",
273 | )
274 |
275 | # aggr_buy_prod
276 | if "prod_aggr_buy_timestamp" in plot_attributes:
277 | plot.plot(
278 | ("prod_aggr_buy_timestamp", "prod_aggr_buy_price"),
279 | type="scatter",
280 | marker="triangle",
281 | name="prod_aggr_buy_price",
282 | color="purple",
283 | marker_size=12,
284 | render_style="hold",
285 | )
286 |
287 | # pass_buy_prod
288 | if "prod_pass_buy_timestamp" in plot_attributes:
289 | plot.plot(
290 | ("prod_pass_buy_timestamp", "prod_pass_buy_price"),
291 | type="scatter",
292 | marker="circle",
293 | name="prod_pass_buy_price",
294 | color="purple",
295 | marker_size=12,
296 | render_style="hold",
297 | )
298 |
299 | # aggr_sell_prod
300 | if "prod_aggr_sell_timestamp" in plot_attributes:
301 | plot.plot(
302 | ("prod_aggr_sell_timestamp", "prod_aggr_sell_price"),
303 | type="scatter",
304 | marker="triangle",
305 | name="prod_aggr_sell_price",
306 | color="green",
307 | marker_size=12,
308 | render_style="hold",
309 | )
310 |
311 | # pass_sell_prod
312 | if "prod_pass_sell_timestamp" in plot_attributes:
313 | plot.plot(
314 | ("prod_pass_sell_timestamp", "prod_pass_sell_price"),
315 | type="scatter",
316 | marker="circle",
317 | name="prod_pass_sell_price",
318 | color="green",
319 | marker_size=12,
320 | render_style="hold",
321 | )
322 |
323 | # new_orders
324 | if "new_order_timestamp" in plot_attributes:
325 | plot.plot(
326 | ("new_order_timestamp", "new_order_price"),
327 | type="scatter",
328 | marker="diamond",
329 | name="new_order_price",
330 | color="green",
331 | marker_size=6,
332 | render_style="hold",
333 | )
334 |
335 | # new_order_acks
336 | if "new_order_ack_timestamp" in plot_attributes:
337 | plot.plot(
338 | ("new_order_ack_timestamp", "new_order_ack_price"),
339 | type="scatter",
340 | marker="diamond",
341 | name="new_order_ack_price",
342 | color="lightgreen",
343 | marker_size=6,
344 | render_style="hold",
345 | )
346 |
347 | # cancel_orders
348 | if "cancel_order_timestamp" in plot_attributes:
349 | plot.plot(
350 | ("cancel_order_timestamp", "cancel_order_price"),
351 | type="scatter",
352 | marker="diamond",
353 | name="cancel_order_price",
354 | color="red",
355 | marker_size=6,
356 | render_style="hold",
357 | )
358 |
359 | # cancel_order_acks
360 | if "cancel_order_ack_timestamp" in plot_attributes:
361 | plot.plot(
362 | ("cancel_order_ack_timestamp", "cancel_order_ack_price"),
363 | type="scatter",
364 | marker="diamond",
365 | name="cancel_order_ack_price",
366 | color="pink",
367 | marker_size=6,
368 | render_style="hold",
369 | )
370 |
371 | # rejects
372 | if "reject_orders_timestamp" in plot_attributes:
373 | plot.plot(
374 | ("reject_orders_timestamp", "reject_orders_price"),
375 | type="scatter",
376 | marker="diamond",
377 | name="reject_orders_price",
378 | color="black",
379 | marker_size=6,
380 | render_style="hold",
381 | )
382 |
383 | # val_data
384 | if "val_data_timestamp" in plot_attributes:
385 | plot.plot(
386 | ("val_data_timestamp", "val_data_price"),
387 | color="green",
388 | line_style="solid",
389 | name="val_data_price",
390 | line_width=1,
391 | render_style="connectedhold",
392 | )
393 |
394 | def _set_plot_data_array(self, symbol: str) -> ArrayPlotData:
395 | """
396 | Instantiate ArrayPlotData class for Plot object.
397 |
398 | Args:
399 | symbol (str): The symbol of interest.
400 |
401 | Returns:
402 | ArrayPlotData: The array plot data class with required data fields set.
403 | """
404 |
405 | array_plot_data = ArrayPlotData()
406 |
407 | # quote data
408 | quote_data = self._data.quote_data
409 | if quote_data is not None:
410 | quote_data = self._data.quote_data[
411 | self._data.quote_data["symbol"] == symbol
412 | ]
413 | array_plot_data.set_data("quote_timestamp", quote_data.timestamp.ravel())
414 | array_plot_data.set_data("quote_bid_price", quote_data.bid_price.ravel())
415 | array_plot_data.set_data("quote_ask_price", quote_data.ask_price.ravel())
416 | array_plot_data.set_data(
417 | "quote_micro_price", quote_data.micro_price.ravel()
418 | )
419 |
420 | # trade data
421 | trade_data = self._data.trade_data
422 | if trade_data is not None:
423 | trade_data = self._data.trade_data[
424 | self._data.trade_data["symbol"] == symbol
425 | ]
426 | array_plot_data.set_data("trade_timestamp", trade_data.timestamp.ravel())
427 | array_plot_data.set_data("trade_price", trade_data.price.ravel())
428 |
429 | # fill data - sim
430 | (
431 | buy_aggressive_fills,
432 | buy_passive_fills,
433 | sell_aggressive_fills,
434 | sell_passive_fills,
435 | ) = self._data.fill_data_sim
436 | # aggr_buy
437 | if not (buy_aggressive_fills is None or buy_aggressive_fills.empty):
438 | buy_aggressive_fills = buy_aggressive_fills[
439 | buy_aggressive_fills["symbol"] == symbol
440 | ]
441 | array_plot_data.set_data(
442 | "sim_aggr_buy_timestamp", buy_aggressive_fills.timestamp.ravel()
443 | )
444 | array_plot_data.set_data(
445 | "sim_aggr_buy_price", buy_aggressive_fills.price.ravel()
446 | )
447 | # pass_buy
448 | if not (buy_passive_fills is None or buy_passive_fills.empty):
449 | buy_passive_fills = buy_passive_fills[buy_passive_fills["symbol"] == symbol]
450 | array_plot_data.set_data(
451 | "sim_pass_buy_timestamp", buy_passive_fills.timestamp.ravel()
452 | )
453 | array_plot_data.set_data(
454 | "sim_pass_buy_price", buy_passive_fills.price.ravel()
455 | )
456 | # aggr_sell
457 | if not (sell_aggressive_fills is None or sell_aggressive_fills.empty):
458 | sell_aggressive_fills = sell_aggressive_fills[
459 | sell_aggressive_fills["symbol"] == symbol
460 | ]
461 | array_plot_data.set_data(
462 | "sim_aggr_sell_timestamp", sell_aggressive_fills.timestamp.ravel()
463 | )
464 | array_plot_data.set_data(
465 | "sim_aggr_sell_price", sell_aggressive_fills.price.ravel()
466 | )
467 | # pass_sell
468 | if not (sell_passive_fills is None or sell_passive_fills.empty):
469 | sell_passive_fills = sell_passive_fills[
470 | sell_passive_fills["symbol"] == symbol
471 | ]
472 | array_plot_data.set_data(
473 | "sim_pass_sell_timestamp", sell_passive_fills.timestamp.ravel()
474 | )
475 | array_plot_data.set_data(
476 | "sim_pass_sell_price", sell_passive_fills.price.ravel()
477 | )
478 |
479 | # fill data - prod
480 | (
481 | buy_aggressive_fills,
482 | buy_passive_fills,
483 | sell_aggressive_fills,
484 | sell_passive_fills,
485 | ) = self._data.fill_data_prod
486 | # aggr_buy
487 | if not (buy_aggressive_fills is None or buy_aggressive_fills.empty):
488 | buy_aggressive_fills = buy_aggressive_fills[
489 | buy_aggressive_fills["symbol"] == symbol
490 | ]
491 | array_plot_data.set_data(
492 | "prod_aggr_buy_timestamp", buy_aggressive_fills.timestamp.ravel()
493 | )
494 | array_plot_data.set_data(
495 | "prod_aggr_buy_price", buy_aggressive_fills.price.ravel()
496 | )
497 | # pass_buy
498 | if not (buy_passive_fills is None or buy_passive_fills.empty):
499 | buy_passive_fills = buy_passive_fills[buy_passive_fills["symbol"] == symbol]
500 | array_plot_data.set_data(
501 | "prod_pass_buy_timestamp", buy_passive_fills.timestamp.ravel()
502 | )
503 | array_plot_data.set_data(
504 | "prod_pass_buy_price", buy_passive_fills.price.ravel()
505 | )
506 | # aggr_sell
507 | if not (sell_aggressive_fills is None or sell_aggressive_fills.empty):
508 | sell_aggressive_fills = sell_aggressive_fills[
509 | sell_aggressive_fills["symbol"] == symbol
510 | ]
511 | array_plot_data.set_data(
512 | "prod_aggr_sell_timestamp", sell_aggressive_fills.timestamp.ravel()
513 | )
514 | array_plot_data.set_data(
515 | "prod_aggr_sell_price", sell_aggressive_fills.price.ravel()
516 | )
517 | # pass_sell
518 | if not (sell_passive_fills is None or sell_passive_fills.empty):
519 | sell_passive_fills = sell_passive_fills[
520 | sell_passive_fills["symbol"] == symbol
521 | ]
522 | array_plot_data.set_data(
523 | "prod_pass_sell_timestamp", sell_passive_fills.timestamp.ravel()
524 | )
525 | array_plot_data.set_data(
526 | "prod_pass_sell_price", sell_passive_fills.price.ravel()
527 | )
528 |
529 | # orders
530 | self._data.orders
531 | (
532 | new_orders,
533 | new_order_acks,
534 | cancel_orders,
535 | cancel_order_acks,
536 | rejects,
537 | ) = self._data.orders
538 | # new orders
539 | if not (new_orders is None or new_orders.empty):
540 | new_orders = new_orders[new_orders["symbol"] == symbol]
541 | array_plot_data.set_data(
542 | "new_order_timestamp", new_orders.timestamp.ravel()
543 | )
544 | array_plot_data.set_data("new_order_price", new_orders.price.ravel())
545 | # new orders - ack
546 | if not (new_order_acks is None or new_order_acks.empty):
547 | new_order_acks = new_order_acks[new_order_acks["symbol"] == symbol]
548 | array_plot_data.set_data(
549 | "new_order_ack_timestamp", new_order_acks.timestamp.ravel()
550 | )
551 | array_plot_data.set_data(
552 | "new_order_ack_price", new_order_acks.price.ravel()
553 | )
554 | # cancels
555 | if not (cancel_orders is None or cancel_orders.empty):
556 | cancel_orders = cancel_orders[cancel_orders["symbol"] == symbol]
557 | array_plot_data.set_data(
558 | "cancel_order_timestamp", cancel_orders.timestamp.ravel()
559 | )
560 | array_plot_data.set_data("cancel_order_price", cancel_orders.price.ravel())
561 | # cancels - ack
562 | if not (cancel_order_acks is None or cancel_order_acks.empty):
563 | cancel_order_acks = cancel_order_acks[cancel_order_acks["symbol"] == symbol]
564 | array_plot_data.set_data(
565 | "cancel_order_ack_timestamp", cancel_order_acks.timestamp.ravel()
566 | )
567 | array_plot_data.set_data(
568 | "cancel_order_ack_price", cancel_order_acks.price.ravel()
569 | )
570 | # rejects
571 | if not (rejects is None or rejects.empty):
572 | rejects = rejects[rejects["symbol"] == symbol]
573 | array_plot_data.set_data(
574 | "reject_orders_timestamp", rejects.timestamp.ravel()
575 | )
576 | array_plot_data.set_data("reject_orders_price", rejects.price.ravel())
577 |
578 | # val data
579 | val_data = self._data.val_data
580 | if val_data is not None:
581 | val_data = self._data.val_data[self._data.val_data["symbol"] == symbol]
582 | array_plot_data.set_data("val_data_timestamp", val_data.timestamp.ravel())
583 | array_plot_data.set_data("val_data_price", val_data.theo_price.ravel())
584 |
585 | return array_plot_data
586 |
587 | def _link_subplot(self, plot: Plot):
588 | """
589 | Link a subplot object to others via PlotAxis object.
590 |
591 | Args:
592 | plot (Plot): The subplot Plot object.
593 | """
594 |
595 | if self._top_plot_indexer is None:
596 | # add a calendar-based timescale on the x-axis
597 | bottom_axis = PlotAxis(
598 | plot,
599 | orientation="bottom",
600 | tick_generator=ScalesTickGenerator(scale=CalendarScaleSystem()),
601 | )
602 | plot.underlays.append(bottom_axis)
603 | # persist plot::indexer and plotaxis - need indices and tick_generator to link axes later
604 | self._top_plot_indexer = plot.index_mapper
605 | self._top_plot_index_range = plot.index_range
606 | self._top_plot_bottom_axis = bottom_axis
607 | else:
608 | bottom_axis = PlotAxis(
609 | plot,
610 | orientation="bottom",
611 | # link the first plot's index mapper
612 | mapper=self._top_plot_indexer,
613 | tick_generator=ScalesTickGenerator(scale=CalendarScaleSystem()),
614 | )
615 | # link other attributes
616 | bottom_axis.tick_generator = self._top_plot_bottom_axis.tick_generator
617 | plot.index_range = self._top_plot_index_range
618 | plot.underlays.append(bottom_axis)
619 |
620 | # store to be added to VPlot later
621 | self._subplots.append(plot)
622 |
--------------------------------------------------------------------------------
/microplot/schema.py:
--------------------------------------------------------------------------------
1 | """
2 | This module contains the column names ("data schema") for the required inputs.
3 | """
4 |
5 | QUOTE_DATA_COLUMNS = ["timestamp", "symbol", "bid_price", "ask_price", "micro_price"]
6 | TRADE_DATA_COLUMNS = ["timestamp", "symbol", "price"]
7 | FILL_DATA_COLUMNS = ["timestamp", "symbol", "price", "is_buy", "is_aggressive"]
8 | ORDERS_DATA_COLUMNS = [
9 | "timestamp",
10 | "symbol",
11 | "price",
12 | "is_new",
13 | "is_cancel",
14 | "is_reject",
15 | "is_ack",
16 | ]
17 | VAL_DATA_COLUMNS = ["timestamp", "symbol", "theo_price"]
18 |
--------------------------------------------------------------------------------
/microplot/scripts/__init__.py:
--------------------------------------------------------------------------------
1 | # pylint: disable=C0111
2 |
3 | __all__ = ["plot_csv"]
4 |
--------------------------------------------------------------------------------
/microplot/scripts/plot_csv.py:
--------------------------------------------------------------------------------
1 | """
2 | Command-line script to load a csv and generate a microstructure plot.
3 | """
4 |
5 | from microplot.data import PlotterDataClass
6 | from microplot.plotter import MicroPlotter
7 | from microplot.schema import (
8 | QUOTE_DATA_COLUMNS,
9 | TRADE_DATA_COLUMNS,
10 | FILL_DATA_COLUMNS,
11 | ORDERS_DATA_COLUMNS,
12 | VAL_DATA_COLUMNS,
13 | )
14 | import logging
15 | import argparse
16 | import os.path
17 | import pandas as pd
18 | import sys
19 | from typing import Any, List
20 |
21 | def _check_file_path(file_path: str) -> bool:
22 | """
23 | Check the file_path before trying to open.
24 |
25 | Args:
26 | file_path (str): file path.
27 |
28 | Returns:
29 | bool: Boolean whether file path is valid.
30 | """
31 |
32 | if not file_path.endswith(".csv"):
33 | return False
34 |
35 | return os.path.isfile(file_path)
36 |
37 | def _check(file_path: str):
38 | """
39 | Red-face test the file.
40 |
41 | Args:
42 | file_path (str): file path.
43 |
44 | Raises:
45 | Exception: file path either does not exist or is not a csv.
46 | """
47 |
48 | if not _check_file_path(file_path):
49 | raise Exception(f"{file_path} either does not exist or is not a csv file.")
50 |
51 |
52 | def run_plotter_csv(command_args:List[Any]):
53 | """
54 | Main function for creating plotter via csv.
55 |
56 | Args:
57 | command_args (List[Any]): command-line args.
58 | """
59 | # logger
60 | logger = logging.getLogger("microplotter")
61 | logger.setLevel(logging.INFO)
62 | # console handler
63 | console_handler = logging.StreamHandler()
64 | console_handler.setLevel(logging.INFO)
65 | # formatter
66 | formatter = logging.Formatter(
67 | "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
68 | )
69 | # add formatter to ch
70 | console_handler.setFormatter(formatter)
71 | # add ch to logger
72 | logger.addHandler(console_handler)
73 |
74 | parser = argparse.ArgumentParser()
75 | parser.add_argument(
76 | "-quote_data_file",
77 | "--quote_data_file",
78 | help="[Required] csv containing market quotes",
79 | type=str,
80 | required=True
81 | )
82 | parser.add_argument(
83 | "-trade_data_file",
84 | "--trade_data_file",
85 | help="csv containing market trades",
86 | type=str,
87 | required=False
88 | )
89 | parser.add_argument(
90 | "-fill_data_sim_file",
91 | "--fill_data_sim_file",
92 | help="csv containing fill data from sims",
93 | type=str,
94 | required=False
95 | )
96 | parser.add_argument(
97 | "-fill_data_prod_file",
98 | "--fill_data_prod_file",
99 | help="csv containing fill data from production",
100 | type=str,
101 | required=False
102 | )
103 | parser.add_argument(
104 | "-orders_data_file",
105 | "--orders_data_file",
106 | help="csv containing order data",
107 | type=str,
108 | required=False
109 | )
110 | parser.add_argument(
111 | "-valuation_data_file",
112 | "--valuation_data_file",
113 | help="csv containing valuation data",
114 | type=str,
115 | required=False
116 | )
117 | # read in command-line args
118 | args = parser.parse_args(command_args)
119 |
120 | # data class
121 | data = PlotterDataClass()
122 |
123 | # read in quote data
124 | if args.quote_data_file is not None:
125 | _check(args.quote_data_file)
126 | logger.info("Reading in quote data....")
127 | with open(args.quote_data_file) as file:
128 | data.quote_data = pd.read_csv(file, usecols=QUOTE_DATA_COLUMNS)
129 | logger.info("Done")
130 |
131 | # read in trade data
132 | if args.trade_data_file is not None:
133 | _check(args.trade_data_file)
134 | logger.info("Reading in trade data....")
135 | with open(args.trade_data_file) as file:
136 | data.trade_data = pd.read_csv(file, usecols=TRADE_DATA_COLUMNS)
137 | logger.info("Done")
138 |
139 | # read in fill data -- sim
140 | if args.fill_data_sim_file is not None:
141 | _check(args.fill_data_sim_file)
142 | logger.info("Reading in fill data sim....")
143 | with open(args.fill_data_sim_file) as file:
144 | data.fill_data_sim = pd.read_csv(file, usecols=FILL_DATA_COLUMNS)
145 | logger.info("Done")
146 |
147 | # read in fill data -- prod
148 | if args.fill_data_prod_file is not None:
149 | _check(args.fill_data_prod_file)
150 | logger.info("Reading in fill data prod....")
151 | with open(args.fill_data_prod_file) as file:
152 | data.fill_data_prod = pd.read_csv(file, usecols=FILL_DATA_COLUMNS)
153 | logger.info("Done")
154 |
155 | # read in orders
156 | if args.orders_data_file is not None:
157 | _check(args.orders_data_file)
158 | logger.info("Reading in orders data....")
159 | with open(args.orders_data_file) as file:
160 | data.orders = pd.read_csv(file, usecols=ORDERS_DATA_COLUMNS)
161 | logger.info("Done")
162 |
163 | # read in valuation data
164 | if args.valuation_data_file is not None:
165 | _check(args.valuation_data_file)
166 | logger.info("Reading in valuation data....")
167 | with open(args.valuation_data_file) as file:
168 | data.val_data = pd.read_csv(file, usecols=VAL_DATA_COLUMNS)
169 | logger.info("Done")
170 |
171 | # plotter
172 | logger.info("Creating plotter....")
173 | plotter = MicroPlotter(data)
174 | logger.info("Done")
175 | # call plot() method
176 | logger.info("Rendering plots....")
177 | plotter.plot()
178 | logger.info("Done")
179 |
180 |
181 | if __name__ == "__main__":
182 | # set up this way to be able to import function
183 | run_plotter_csv(sys.argv[1:])
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | chaco==5.1.0
2 | enable==5.3.1
3 | pandas==1.3.5
4 | setuptools==61.2.0
5 | traits==6.4.1
6 | traitsui==7.4.2
7 | pyqt
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | """
2 | setup.py
3 | """
4 | from setuptools import setup
5 |
6 | setup(
7 | name='microstructure-plotter',
8 | version='0.1',
9 | packages=['microplot', 'microplots.scripts'],
10 | setup_requires=['chaco==5.1.0','enable==5.3.1','traits==6.4.1','traitsui==7.4.2','pandas==1.3.5','pyqt'],
11 | url='https://github.com/will-thompson-k/microstructure-plotter',
12 | license='MIT',
13 | author='Will Thompson',
14 | author_email='',
15 | description='Visualize financial market data 📈 on infinitesimal timescales 🔎',
16 | )
--------------------------------------------------------------------------------