The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── docker-compose.yml
├── main.py
├── models
    ├── __init__.py
    ├── base_model.py
    └── hft_model_1.py
├── requirements.txt
├── sample_output
    ├── sample_output_1.txt
    └── videoshot.gif
└── util
    ├── __init__.py
    ├── dt_util.py
    └── order_util.py


/.gitignore:
--------------------------------------------------------------------------------
  1 | # Created by .ignore support plugin (hsz.mobi)
  2 | 
  3 | # IntelliJ project files
  4 | .idea
  5 | *.iml
  6 | out
  7 | gen
  8 | 
  9 | ### Visual Studio Code template
 10 | .vscode/*
 11 | !.vscode/settings.json
 12 | !.vscode/tasks.json
 13 | !.vscode/launch.json
 14 | !.vscode/extensions.json
 15 | 
 16 | ### Python template
 17 | 
 18 | # Byte-compiled / optimized / DLL files
 19 | __pycache__/
 20 | *.py[cod]
 21 | *$py.class
 22 | 
 23 | # C extensions
 24 | *.so
 25 | 
 26 | # Distribution / packaging
 27 | .Python
 28 | build/
 29 | develop-eggs/
 30 | dist/
 31 | downloads/
 32 | eggs/
 33 | .eggs/
 34 | lib/
 35 | lib64/
 36 | parts/
 37 | sdist/
 38 | var/
 39 | wheels/
 40 | pip-wheel-metadata/
 41 | share/python-wheels/
 42 | *.egg-info/
 43 | .installed.cfg
 44 | *.egg
 45 | MANIFEST
 46 | 
 47 | # PyInstaller
 48 | #  Usually these files are written by a python script from a template
 49 | #  before PyInstaller builds the exe, so as to inject date/other infos into it.
 50 | *.manifest
 51 | *.spec
 52 | 
 53 | # Installer logs
 54 | pip-log.txt
 55 | pip-delete-this-directory.txt
 56 | 
 57 | # Unit test / coverage reports
 58 | htmlcov/
 59 | .tox/
 60 | .nox/
 61 | .coverage
 62 | .coverage.*
 63 | .cache
 64 | nosetests.xml
 65 | coverage.xml
 66 | *.cover
 67 | .hypothesis/
 68 | .pytest_cache/
 69 | 
 70 | # Translations
 71 | *.mo
 72 | *.pot
 73 | 
 74 | # Django stuff:
 75 | *.log
 76 | local_settings.py
 77 | db.sqlite3
 78 | 
 79 | # Flask stuff:
 80 | instance/
 81 | .webassets-cache
 82 | 
 83 | # Scrapy stuff:
 84 | .scrapy
 85 | 
 86 | # Sphinx documentation
 87 | docs/_build/
 88 | 
 89 | # PyBuilder
 90 | target/
 91 | 
 92 | # Jupyter Notebook
 93 | .ipynb_checkpoints
 94 | 
 95 | # IPython
 96 | profile_default/
 97 | ipython_config.py
 98 | 
 99 | # pyenv
100 | .python-version
101 | 
102 | # pipenv
103 | #   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
104 | #   However, in case of collaboration, if having platform-specific dependencies or dependencies
105 | #   having no cross-platform support, pipenv may install dependencies that don't work, or not
106 | #   install all needed dependencies.
107 | #Pipfile.lock
108 | 
109 | # celery beat schedule file
110 | celerybeat-schedule
111 | 
112 | # SageMath parsed files
113 | *.sage.py
114 | 
115 | # Environments
116 | .env
117 | .venv
118 | env/
119 | venv/
120 | ENV/
121 | env.bak/
122 | venv.bak/
123 | 
124 | # Spyder project settings
125 | .spyderproject
126 | .spyproject
127 | 
128 | # Rope project settings
129 | .ropeproject
130 | 
131 | # mkdocs documentation
132 | /site
133 | 
134 | # mypy
135 | .mypy_cache/
136 | .dmypy.json
137 | dmypy.json
138 | 
139 | # Pyre type checker
140 | .pyre/
141 | 
142 | ### JetBrains template
143 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
144 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
145 | 
146 | # User-specific stuff
147 | .idea/**/workspace.xml
148 | .idea/**/tasks.xml
149 | .idea/**/usage.statistics.xml
150 | .idea/**/dictionaries
151 | .idea/**/shelf
152 | 
153 | # Generated files
154 | .idea/**/contentModel.xml
155 | 
156 | # Sensitive or high-churn files
157 | .idea/**/dataSources/
158 | .idea/**/dataSources.ids
159 | .idea/**/dataSources.local.xml
160 | .idea/**/sqlDataSources.xml
161 | .idea/**/dynamic.xml
162 | .idea/**/uiDesigner.xml
163 | .idea/**/dbnavigator.xml
164 | 
165 | # Gradle
166 | .idea/**/gradle.xml
167 | .idea/**/libraries
168 | 
169 | # Gradle and Maven with auto-import
170 | # When using Gradle or Maven with auto-import, you should exclude module files,
171 | # since they will be recreated, and may cause churn.  Uncomment if using
172 | # auto-import.
173 | # .idea/modules.xml
174 | # .idea/*.iml
175 | # .idea/modules
176 | # *.iml
177 | # *.ipr
178 | 
179 | # CMake
180 | cmake-build-*/
181 | 
182 | # Mongo Explorer plugin
183 | .idea/**/mongoSettings.xml
184 | 
185 | # File-based project format
186 | *.iws
187 | 
188 | # IntelliJ
189 | out/
190 | 
191 | # mpeltonen/sbt-idea plugin
192 | .idea_modules/
193 | 
194 | # JIRA plugin
195 | atlassian-ide-plugin.xml
196 | 
197 | # Cursive Clojure plugin
198 | .idea/replstate.xml
199 | 
200 | # Crashlytics plugin (for Android Studio and IntelliJ)
201 | com_crashlytics_export_strings.xml
202 | crashlytics.properties
203 | crashlytics-build.properties
204 | fabric.properties
205 | 
206 | # Editor-based Rest Client
207 | .idea/httpRequests
208 | 
209 | # Android studio 3.1+ serialized cache file
210 | .idea/caches/build_file_checksums.ser
211 | 


--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.7-stretch
2 | ENV PYTHONUNBUFFERED 1
3 | RUN mkdir /app
4 | WORKDIR /app
5 | COPY . /app/
6 | RUN pip install -r requirements.txt


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | The MIT License (MIT)
 2 | 
 3 | Copyright (c) 2015 James Ma
 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.


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | Purpose
  2 | ===
  3 | A basic trading model on Interactive Brokers' API dealing with high-frequency data studies.
  4 | 
  5 | ![alt text](https://github.com/jamesmawm/High-Frequency-Trading-Model-with-IB/blob/v3.0/sample_output/videoshot.gif?raw=true "Chart output")
  6 | 
  7 | Requirements
  8 | ===
  9 | 
 10 | - Python 3.7
 11 | - IB Trader Workstation Build 973.2
 12 | - IB paper or live trading account
 13 | - (Optional) Docker and docker-compose
 14 | 
 15 | What's new
 16 | ===
 17 | 
 18 | *19 Jun 2019*
 19 | 
 20 | - <a href="https://github.com/jamesmawm/High-Frequency-Trading-Model-with-IB/tree/v3.0">Version 3.0</a> released
 21 | - `ibpy` library is dropped in favour of the newer `ib_insync` library.
 22 | - The same code logic is ported over to use the features of `ib_insync`, compatible with Python 3.7. Includes various code cleanup.
 23 | - Dropped `matplotlib` charting in favour of headless running inside Docker.
 24 | 
 25 | 
 26 | *14 Jun 2019*
 27 | 
 28 | - <a href="https://github.com/jamesmawm/High-Frequency-Trading-Model-with-IB/tree/v2.0">Version 2.0</a> released
 29 | - Merged pull request from: https://github.com/chicago-joe/IB_PairsTrading_Algo
 30 |     
 31 |     Thanks to chicago-joe for updating to work with Python 3.
 32 |     
 33 |     As this is only a compatibility update, there are many outdated components and the trading model is quite unlikely to be working as intended.
 34 |     
 35 | 
 36 | *8 Jun 2015*
 37 | - <a href="https://github.com/jamesmawm/High-Frequency-Trading-Model-with-IB/tree/v1.0">Version 1.0</a> released
 38 | - Refactor and conform to PEP8 standards
 39 | - New chart display with 4 subplots
 40 | 
 41 | 
 42 | Setting up
 43 | ===
 44 | 
 45 | You can choose to run this model in your console OR in Docker.
 46 | 
 47 | ## Running on a local Python console 
 48 | 
 49 | Steps to run the trading model on your command line:
 50 | 
 51 | - Within a Python 3.7 environment, install the requirements:
 52 |     
 53 |         pip install -r requirements.txt
 54 | 
 55 | - In IB Trader Workstation (TWS), go to **Configuration** > **Api** > **Settings** and:
 56 | 
 57 |     - enable ActiveX and Socket Clients
 58 |     - check the port number you will be using
 59 |     - If using Docker, uncheck **Allow connections from localhost only** and enter the machine IP running this model to **Trusted IPs**.
 60 | 
 61 | - Update `main.py` with the required parameters and run the model with the command:
 62 | 
 63 |         python main.py
 64 |     
 65 | ## Running from a Docker container
 66 | 
 67 | This step is optional. You can choose to deploy one or several instances of these algos on a remote machine for execution using Docker.
 68 | 
 69 | A Docker container helps to automatically build your running environment and isolate changes, all in just a few simple commands!
 70 | 
 71 | To run this trading model in headless mode:
 72 | 
 73 | - In TWS, ensure that remote API connections are accepted and the Docker machine's IP is added to **Trusted IPs**.
 74 | 
 75 | - Ensure your machine has docker and docker-compose installed. Build the image with this command:
 76 | 
 77 |         docker-compose build
 78 |         
 79 | - Update the parameters in `docker-compose.yml`. I've set the `TWS_HOST` value in my environment variables. This is the IP address of the remote machine running TWS. Or, you can just manually enter the IP address value directly. Then, run the image as a container instance:
 80 | 
 81 |         docker-compose up
 82 |         
 83 |     To run in headless mode, simply add the detached command `-d`, like this:
 84 |     
 85 |         docker-compose up -d
 86 |         
 87 |     In headless mode, you would have to start and stop the containers manually.
 88 | 
 89 | Key concepts
 90 | ===
 91 | At the present moment, this model utilizes statistical arbitrage incorporating these methodologies:
 92 | - Bootstrapping the model with historical data to derive usable strategy parameters
 93 | - Resampling inhomogeneous time series to homogeneous time series
 94 | - Selection of highly-correlated tradable pair
 95 | - The ability to short one instrument and long the other.
 96 | - Using volatility ratio to detect up or down trend.
 97 | - Fair valuation of security using beta, or the mean over some past interval.
 98 | - One pandas DataFrame to store historical prices
 99 | 
100 | Other functions:
101 | - Generate trade signals and place buy/sell market orders based on every incoming tick data.
102 | - Re-evaluating beta every some interval in seconds.
103 | 
104 | And greatly inspired by these papers:
105 | - MIT - Developing high-frequency equities trading model 
106 |   @ http://dspace.mit.edu/handle/1721.1/59122
107 | - SMU - Profiting from mean-reverting yield-curve trading strategies
108 |   @ http://ink.library.smu.edu.sg/cgi/viewcontent.cgi?article=3488&context=lkcsb_research
109 | 
110 | And book:
111 | - Introduction to High-Frequency Finance
112 |   @ http://www.amazon.com/Introduction-High-Frequency-Finance-Ramazan-Gen%C3%A7ay/dp/0122796713
113 | 
114 | Step-by-step guide to more trading models
115 | ===
116 | 
117 | <a href="https://www.packtpub.com/big-data-and-business-intelligence/mastering-python-finance-second-edition?utm_source=github&utm_medium=repository&utm_campaign=9781789346466"><img src="https://www.packtpub.com/media/catalog/product/cache/e4d64343b1bc593f1c5348fe05efa4a6/b/1/b11165.png" alt="Mastering Python for Finance - Second Edition" height="256px" align="right"></a>
118 | 
119 | I published a book titled 'Mastering Python for Finance - Second Edition', discussing additional algorithmic trading ideas, statistical analysis, machine learning and deep learning, which you might find it useful.
120 | It is available on major sales channels including Amazon, Safari Online and Barnes & Noble,
121 | in paperback, Kindle and ebook.
122 | Get it from:
123 | - https://www.amazon.com/dp/1789346460
124 | 
125 | Source codes and table of contents on GitHub:
126 | - https://github.com/jamesmawm/mastering-python-for-finance-second-edition
127 | 
128 | Topics covered with source codes:
129 | 
130 | - <a href="https://github.com/jamesmawm/mastering-python-for-finance-second-edition/blob/master/Chapter%2006%20-%20Statistical%20Analysis%20of%20Time%20Series%20Data.ipynb">Applying kernel PCA</a>. Forecasting and predicting a time series.
131 | - <a href="https://github.com/jamesmawm/mastering-python-for-finance-second-edition/blob/master/Chapter%2007%20-%20Interactive%20Financial%20Analytics%20with%20VIX.ipynb">Replicating the VIX index</a>
132 | - <a href="https://github.com/jamesmawm/mastering-python-for-finance-second-edition/blob/master/Chapter%2008%20-%20Building%20an%20Algorithmic%20Trading%20Platform.ipynb">Building a mean-reverting and trend-following trading model</a>
133 | - <a href="https://github.com/jamesmawm/mastering-python-for-finance-second-edition/blob/master/Chapter%2009%20-%20Implementing%20a%20Backtesting%20System.ipynb">Implementing a backtesting system</a>
134 | - <a href="https://github.com/jamesmawm/mastering-python-for-finance-second-edition/blob/master/Chapter%2010%20-%20Machine%20Learning%20for%20Finance.ipynb">Predicting returns with a cross-asset momentum machine learning model</a>
135 | - <a href="https://github.com/jamesmawm/mastering-python-for-finance-second-edition/blob/master/Chapter%2011%20-%20Deep%20Learning%20for%20Finance.ipynb">Credit card payment default prediction with Keras</a>. Get started in deep learning with TensorFlow for predicting prices.
136 | 
137 | Suggested enhancements
138 | ===
139 | Some ideas that you can extend this model for better results:
140 | 
141 | - Extending to more than 2 securities and trade on optimum prices
142 | - Generate trade signals based on correlation and co-integration
143 | - Using PCA for next-period evaluation. In my book I've described the use of PCA to reconstruct the DOW index. Source codes <a href="https://github.com/jamesmawm/mastering-python-for-finance-second-edition/blob/master/Chapter%2006%20-%20Statistical%20Analysis%20of%20Time%20Series%20Data.ipynb">here</a>.
144 | - Include vector auto-regressions
145 | - Account for regime shifts (trending or mean-reverting states)
146 | - Account for structural breaks
147 | - Using EMA kernels instead of a rectangular one
148 | - Add in alphas(P/E, B/P ratios) and Kalman filter prediction
149 | 
150 | Disclaimer
151 | ===
152 | - Any securities listed is not a solicitation to trade.
153 | - This model has not been proven to be profitable in a live account.
154 | - I am not liable for any outcome of your trades.
155 | 
156 | 
157 | Is this HFT?
158 | ===
159 | Sure, I had some questions "how is this high-frequency" or "not for UHFT" or "this is not front-running". Let's take a closer look at these definitions:
160 | - High-frequency finance: the studying of incoming tick data arriving at high frequencies,
161 | say hundreds of ticks per second. High frequency finance aims to derive stylized facts from high frequency signals.
162 | - High-frequency trading: the turnover of positions at high frequencies;
163 | positions are typically held at most in seconds, which amounts to hundreds of trades per second.
164 | 
165 | This models aims to incorporate the above two functions and present a simplistic view to traders who wish to automate their trades, get started in Python trading or use a free trading platform.
166 | 
167 | Other software of interest
168 | ===
169 | I write software in my free time. One of them for trading futures was simply called 'The Gateway'. 
170 | It is a C# application that exposes a socket and public API method calls for interfacing Python with futures markets including CME,
171 | CBOT, NYSE, Eurex and ICE. Targets the T4 API.
172 | 
173 | More information on GitHub: https://github.com/hftstrat/The-Gateway-code-samples or view on the <a href="https://scctrader.herodevice.com/the-gateway/">website</a>.
174 | 
175 | 
176 | Final notes
177 | ========================
178 | - I haven't come across any complete high-frequency trading model lying around, so here's one to get started off the ground and running.
179 | - This model has never been used with a real account. All testing was done in demo account only.
180 | - The included strategy parameters are theoretical ideal conditions, which have not been adjusted for back-tested results.
181 | - This project is still a work in progress. A good model could take months or even years!
182 | 


--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
 1 | version: '3'
 2 | 
 3 | services:
 4 |   hft_model_1:
 5 |     build: .
 6 |     command: python3 main.py
 7 |     volumes:
 8 |       - .:/app
 9 |     environment:
10 |       - TWS_HOST=${TWS_HOST}


--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | 
 3 | from ib_insync import Forex
 4 | 
 5 | from models.hft_model_1 import HftModel1
 6 | 
 7 | if __name__ == '__main__':
 8 | 	TWS_HOST = os.environ.get('TWS_HOST', '127.0.0.1')
 9 | 	TWS_PORT = os.environ.get('TWS_PORT', 7497)
10 | 
11 | 	print('Connecting on host:', TWS_HOST, 'port:', TWS_PORT)
12 | 
13 | 	model = HftModel1(
14 | 		host=TWS_HOST,
15 | 		port=TWS_PORT,
16 | 		client_id=2,
17 | 	)
18 | 
19 | 	to_trade = [
20 | 		('EURUSD', Forex('EURUSD')),
21 | 		('USDJPY', Forex('USDJPY'))
22 | 	]
23 | 
24 | 	model.run(to_trade=to_trade, trade_qty=100)
25 | 


--------------------------------------------------------------------------------
/models/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesmawm/High-Frequency-Trading-Model-with-IB/8e96ade54a8e3eb94ead04827a846225ae122864/models/__init__.py


--------------------------------------------------------------------------------
/models/base_model.py:
--------------------------------------------------------------------------------
 1 | from ib_insync import IB, Forex, Stock, MarketOrder
 2 | 
 3 | from util import order_util
 4 | 
 5 | """
 6 | A base model containing common IB functions. 
 7 | 
 8 | For other models to extend and use.
 9 | """
10 | 
11 | 
12 | class BaseModel(object):
13 | 	def __init__(self, host='127.0.0.1', port=7497, client_id=1):
14 | 		self.host = host
15 | 		self.port = port
16 | 		self.client_id = client_id
17 | 
18 | 		self.__ib = None
19 | 		self.pnl = None  # stores IB PnL object
20 | 		self.positions = {}  # stores IB Position object by symbol
21 | 
22 | 		self.symbol_map = {}  # maps contract to symbol
23 | 		self.symbols, self.contracts = [], []
24 | 
25 | 	def init_model(self, to_trade):
26 | 		"""
27 | 		Initialize the model given inputs before running.
28 | 		Stores the input symbols and contracts that will be used for reading positions.
29 | 
30 | 		:param to_trade: list of a tuple of symbol and contract, Example:
31 | 			[('EURUSD', Forex('EURUSD'), ]
32 | 		"""
33 | 		self.symbol_map = {str(contract): ident for (ident, contract) in to_trade}
34 | 		self.contracts = [contract for (_, contract) in to_trade]
35 | 		self.symbols = list(self.symbol_map.values())
36 | 
37 | 	def connect_to_ib(self):
38 | 		self.ib.connect(self.host, self.port, clientId=self.client_id)
39 | 
40 | 	def request_pnl_updates(self):
41 | 		account = self.ib.managedAccounts()[0]
42 | 		self.ib.reqPnL(account)
43 | 		self.ib.pnlEvent += self.on_pnl
44 | 
45 | 	def on_pnl(self, pnl):
46 | 		""" Simply store a copy of the latest PnL whenever where are changes """
47 | 		self.pnl = pnl
48 | 
49 | 	def request_position_updates(self):
50 | 		self.ib.reqPositions()
51 | 		self.ib.positionEvent += self.on_position
52 | 
53 | 	def on_position(self, position):
54 | 		""" Simply store a copy of the latest Position object for the provided contract """
55 | 		symbol = self.get_symbol(position.contract)
56 | 		if symbol not in self.symbols:
57 | 			print('[warn]symbol not found for position:', position)
58 | 			return
59 | 
60 | 		self.positions[symbol] = position
61 | 
62 | 	def request_all_contracts_data(self, fn_on_tick):
63 | 		for contract in self.contracts:
64 | 			self.ib.reqMktData(contract)
65 | 
66 | 		self.ib.pendingTickersEvent += fn_on_tick
67 | 
68 | 	def place_market_order(self, contract, qty, fn_on_filled):
69 | 		order = MarketOrder(order_util.get_order_action(qty), abs(qty))
70 | 		trade = self.ib.placeOrder(contract, order)
71 | 		trade.filledEvent += fn_on_filled
72 | 		return trade
73 | 
74 | 	def get_symbol(self, contract):
75 | 		"""
76 | 		Finds the symbol given the contract.
77 | 
78 | 		:param contract: The Contract object
79 | 		:return: the symbol given for the specific contract
80 | 		"""
81 | 		symbol = self.symbol_map.get(str(contract), None)
82 | 		if symbol:
83 | 			return symbol
84 | 
85 | 		symbol = ''
86 | 		if type(contract) is Forex:
87 | 			symbol = contract.localSymbol.replace('.', '')
88 | 		elif type(contract) is Stock:
89 | 			symbol = contract.symbol
90 | 
91 | 		return symbol if symbol in self.symbols else ''
92 | 
93 | 	@property
94 | 	def ib(self):
95 | 		if not self.__ib:
96 | 			self.__ib = IB()
97 | 
98 | 		return self.__ib
99 | 


--------------------------------------------------------------------------------
/models/hft_model_1.py:
--------------------------------------------------------------------------------
  1 | import datetime as dt
  2 | import time
  3 | 
  4 | import pandas as pd
  5 | 
  6 | from models.base_model import BaseModel
  7 | from util import dt_util
  8 | 
  9 | """
 10 | This is a simple high-frequency model that processes incoming market data at tick level.
 11 | 
 12 | Statistical calculations involved:
 13 | - beta: the mean prices of A over B
 14 | - volatility ratio: the standard deviation of pct changes of A over B
 15 | 
 16 | The signals are then calculated based on these stats:
 17 | - whether it is a downtrend or uptrend
 18 | - whether the expected price given from the beta is overbought or oversold
 19 | 
 20 | This model takes a mean-reverting approach:
 21 | - On a BUY signal indicating oversold and uptrend, we take a LONG position. 
 22 |   Then close the LONG position on a SELL signal.
 23 | - Conversely, on a SELL signal, we take a SHORT position and closeout on a BUY signal.
 24 | """
 25 | 
 26 | 
 27 | class HftModel1(BaseModel):
 28 | 	def __init__(self, *args, **kwargs):
 29 | 		super().__init__(*args, **kwargs)
 30 | 
 31 | 		self.df_hist = None  # stores mid prices in a pandas DataFrame
 32 | 
 33 | 		self.pending_order_ids = set()
 34 | 		self.is_orders_pending = False
 35 | 
 36 | 		# Input params
 37 | 		self.trade_qty = 0
 38 | 
 39 | 		# Strategy params
 40 | 		self.volatility_ratio = 1
 41 | 		self.beta = 0
 42 | 		self.moving_window_period = dt.timedelta(hours=1)
 43 | 		self.is_buy_signal, self.is_sell_signal = False, False
 44 | 
 45 | 	def run(self, to_trade=[], trade_qty=0):
 46 | 		""" Entry point """
 47 | 
 48 | 		print('[{time}]started'.format(
 49 | 			time=str(pd.to_datetime('now')),
 50 | 		))
 51 | 
 52 | 		# Initialize model based on inputs
 53 | 		self.init_model(to_trade)
 54 | 		self.trade_qty = trade_qty
 55 | 		self.df_hist = pd.DataFrame(columns=self.symbols)
 56 | 
 57 | 		# Establish connection to IB
 58 | 		self.connect_to_ib()
 59 | 		self.request_pnl_updates()
 60 | 		self.request_position_updates()
 61 | 		self.request_historical_data()
 62 | 		self.request_all_contracts_data(self.on_tick)
 63 | 
 64 | 		# Recalculate and/or print account updates at intervals
 65 | 		while self.ib.waitOnUpdate():
 66 | 			self.ib.sleep(1)
 67 | 			self.recalculate_strategy_params()
 68 | 
 69 | 			if not self.is_position_flat:
 70 | 				self.print_account()
 71 | 
 72 | 	def on_tick(self, tickers):
 73 | 		""" When a tick data is received, store it and make calculations out of it """
 74 | 		for ticker in tickers:
 75 | 			self.get_incoming_tick_data(ticker)
 76 | 
 77 | 		self.perform_trade_logic()
 78 | 
 79 | 	def perform_trade_logic(self):
 80 | 		"""
 81 | 		This part is the 'secret-sauce' where actual trades takes place.
 82 | 		My take is that great experience, good portfolio construction,
 83 | 		and together with robust backtesting will make your strategy viable.
 84 | 		GOOD PORTFOLIO CONSTRUCTION CAN SAVE YOU FROM BAD RESEARCH,
 85 | 		BUT BAD PORTFOLIO CONSTRUCTION CANNOT SAVE YOU FROM GREAT RESEARCH
 86 | 
 87 | 		This trade logic uses volatility ratio and beta as our indicators.
 88 | 		- volatility ratio > 1 :: uptrend, volatility ratio < 1 :: downtrend
 89 | 		- beta is calculated as: mean(price A) / mean(price B)
 90 | 
 91 | 		We use the assumption that price levels will mean-revert.
 92 | 		Expected price A = beta x price B
 93 | 		"""
 94 | 		self.calculate_signals()
 95 | 
 96 | 		if self.is_orders_pending or self.check_and_enter_orders():
 97 | 			return  # Do nothing while waiting for orders to be filled
 98 | 
 99 | 		if self.is_position_flat:
100 | 			self.print_strategy_params()
101 | 
102 | 	def print_account(self):
103 | 		[symbol_a, symbol_b] = self.symbols
104 | 		position_a, position_b = self.positions.get(symbol_a), self.positions.get(symbol_b)
105 | 
106 | 		print('[{time}][account]{symbol_a} pos={pos_a} avgPrice={avg_price_a}|'
107 | 			  '{symbol_b} pos={pos_b}|rpnl={rpnl:.2f} upnl={upnl:.2f}|beta:{beta:.2f} volatility:{vr:.2f}'.format(
108 | 			time=str(pd.to_datetime('now')),
109 | 			symbol_a=symbol_a,
110 | 			pos_a=position_a.position if position_a else 0,
111 | 			avg_price_a=position_a.avgCost if position_a else 0,
112 | 			symbol_b=symbol_b,
113 | 			pos_b=position_b.position if position_b else 0,
114 | 			avg_price_b=position_b.avgCost if position_b else 0,
115 | 			rpnl=self.pnl.realizedPnL,
116 | 			upnl=self.pnl.unrealizedPnL,
117 | 			beta=self.beta,
118 | 			vr=self.volatility_ratio,
119 | 		))
120 | 
121 | 	def print_strategy_params(self):
122 | 		print('[{time}][strategy params]beta:{beta:.2f} volatility:{vr:.2f}|rpnl={rpnl:.2f}'.format(
123 | 			time=str(pd.to_datetime('now')),
124 | 			beta=self.beta,
125 | 			vr=self.volatility_ratio,
126 | 			rpnl=self.pnl.realizedPnL,
127 | 		))
128 | 
129 | 	def check_and_enter_orders(self):
130 | 		if self.is_position_flat and self.is_sell_signal:
131 | 			print('*** OPENING SHORT POSITION ***')
132 | 			self.place_spread_order(-self.trade_qty)
133 | 			return True
134 | 
135 | 		if self.is_position_flat and self.is_buy_signal:
136 | 			print('*** OPENING LONG POSITION ***')
137 | 			self.place_spread_order(self.trade_qty)
138 | 			return True
139 | 
140 | 		if self.is_position_short and self.is_buy_signal:
141 | 			print('*** CLOSING SHORT POSITION ***')
142 | 			self.place_spread_order(self.trade_qty)
143 | 			return True
144 | 
145 | 		if self.is_position_long and self.is_sell_signal:
146 | 			print('*** CLOSING LONG POSITION ***')
147 | 			self.place_spread_order(-self.trade_qty)
148 | 			return True
149 | 
150 | 		return False
151 | 
152 | 	def place_spread_order(self, qty):
153 | 		print('Placing spread orders...')
154 | 
155 | 		[contract_a, contract_b] = self.contracts
156 | 
157 | 		trade_a = self.place_market_order(contract_a, qty, self.on_filled)
158 | 		print('Order placed:', trade_a)
159 | 
160 | 		trade_b = self.place_market_order(contract_b, -qty, self.on_filled)
161 | 		print('Order placed:', trade_b)
162 | 
163 | 		self.is_orders_pending = True
164 | 
165 | 		self.pending_order_ids.add(trade_a.order.orderId)
166 | 		self.pending_order_ids.add(trade_b.order.orderId)
167 | 		print('Order IDs pending execution:', self.pending_order_ids)
168 | 
169 | 	def on_filled(self, trade):
170 | 		print('Order filled:', trade)
171 | 		self.pending_order_ids.remove(trade.order.orderId)
172 | 		print('Order IDs pending execution:', self.pending_order_ids)
173 | 
174 | 		# Update flag when all pending orders are filled
175 | 		if not self.pending_order_ids:
176 | 			self.is_orders_pending = False
177 | 
178 | 	def recalculate_strategy_params(self):
179 | 		""" Calculating beta and volatility ratio for our signal indicators """
180 | 		[symbol_a, symbol_b] = self.symbols
181 | 
182 | 		resampled = self.df_hist.resample('30s').ffill().dropna()
183 | 		mean = resampled.mean()
184 | 		self.beta = mean[symbol_a] / mean[symbol_b]
185 | 
186 | 		stddevs = resampled.pct_change().dropna().std()
187 | 		self.volatility_ratio = stddevs[symbol_a] / stddevs[symbol_b]
188 | 
189 | 	def calculate_signals(self):
190 | 		self.trim_historical_data()
191 | 
192 | 		is_up_trend, is_down_trend = self.volatility_ratio > 1, self.volatility_ratio < 1
193 | 		is_overbought, is_oversold = self.is_overbought_or_oversold()
194 | 
195 | 		# Our final trade signals
196 | 		self.is_buy_signal = is_up_trend and is_oversold
197 | 		self.is_sell_signal = is_down_trend and is_overbought
198 | 
199 | 	def trim_historical_data(self):
200 | 		""" Ensure historical data don't grow beyond a certain size """
201 | 		cutoff_time = dt.datetime.now(tz=dt_util.LOCAL_TIMEZONE) - self.moving_window_period
202 | 		self.df_hist = self.df_hist[self.df_hist.index >= cutoff_time]
203 | 
204 | 	def is_overbought_or_oversold(self):
205 | 		[symbol_a, symbol_b] = self.symbols
206 | 		last_price_a = self.df_hist[symbol_a].dropna().values[-1]
207 | 		last_price_b = self.df_hist[symbol_b].dropna().values[-1]
208 | 
209 | 		expected_last_price_a = last_price_b * self.beta
210 | 
211 | 		is_overbought = last_price_a < expected_last_price_a  # Cheaper than expected
212 | 		is_oversold = last_price_a > expected_last_price_a  # Higher than expected
213 | 
214 | 		return is_overbought, is_oversold
215 | 
216 | 	def get_incoming_tick_data(self, ticker):
217 | 		"""
218 | 		Stores the midpoint of incoming price data to a pandas DataFrame `df_hist`.
219 | 
220 | 		:param ticker: The incoming tick data as a Ticker object.
221 | 		"""
222 | 		symbol = self.get_symbol(ticker.contract)
223 | 
224 | 		dt_obj = dt_util.convert_utc_datetime(ticker.time)
225 | 		bid = ticker.bid
226 | 		ask = ticker.ask
227 | 		mid = (bid + ask) / 2
228 | 
229 | 		self.df_hist.loc[dt_obj, symbol] = mid
230 | 
231 | 	def request_historical_data(self):
232 | 		"""
233 | 		Bootstrap our model by downloading historical data for each contract.
234 | 
235 | 		The midpoint of prices are stored in the pandas DataFrame `df_hist`.
236 | 		"""
237 | 		for contract in self.contracts:
238 | 			self.set_historical_data(contract)
239 | 
240 | 	def set_historical_data(self, contract):
241 | 		symbol = self.get_symbol(contract)
242 | 
243 | 		bars = self.ib.reqHistoricalData(
244 | 			contract,
245 | 			endDateTime=time.strftime('%Y%m%d %H:%M:%S'),
246 | 			durationStr='3600 S',
247 | 			barSizeSetting='5 secs',
248 | 			whatToShow='MIDPOINT',
249 | 			useRTH=True,
250 | 			formatDate=1
251 | 		)
252 | 		for bar in bars:
253 | 			dt_obj = dt_util.convert_local_datetime(bar.date)
254 | 			self.df_hist.loc[dt_obj, symbol] = bar.close
255 | 
256 | 	@property
257 | 	def is_position_flat(self):
258 | 		position_obj = self.positions.get(self.symbols[0])
259 | 		if not position_obj:
260 | 			return True
261 | 
262 | 		return position_obj.position == 0
263 | 
264 | 	@property
265 | 	def is_position_short(self):
266 | 		position_obj = self.positions.get(self.symbols[0])
267 | 		return position_obj and position_obj.position < 0
268 | 
269 | 	@property
270 | 	def is_position_long(self):
271 | 		position_obj = self.positions.get(self.symbols[0])
272 | 		return position_obj and position_obj.position > 0
273 | 


--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | eventkit==0.8.5
2 | ib-insync==0.9.53
3 | nest-asyncio==1.0.0
4 | numpy==1.16.4
5 | pandas==0.24.2
6 | python-dateutil==2.8.0
7 | pytz==2019.1
8 | six==1.12.0
9 | 


--------------------------------------------------------------------------------
/sample_output/sample_output_1.txt:
--------------------------------------------------------------------------------
  1 | [2019-06-19 13:20:07.863028]started
  2 | [2019-06-19 13:20:14.626974][strategy params]beta:0.00 volatility:1.00|rpnl=-496.88
  3 | [2019-06-19 13:20:14.884730][strategy params]beta:0.00 volatility:1.00|rpnl=-496.88
  4 | [2019-06-19 13:20:15.065342][strategy params]beta:0.00 volatility:1.00|rpnl=-496.88
  5 | [2019-06-19 13:20:15.376828][strategy params]beta:0.00 volatility:1.00|rpnl=-496.88
  6 | [2019-06-19 13:20:15.735186][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
  7 | [2019-06-19 13:20:15.946102][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
  8 | [2019-06-19 13:20:15.954929][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
  9 | [2019-06-19 13:20:16.234155][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 10 | [2019-06-19 13:20:17.102221][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 11 | [2019-06-19 13:20:17.394180][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 12 | [2019-06-19 13:20:17.976145][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 13 | [2019-06-19 13:20:17.984933][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 14 | [2019-06-19 13:20:18.271036][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 15 | [2019-06-19 13:20:18.281813][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 16 | [2019-06-19 13:20:18.562996][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 17 | [2019-06-19 13:20:18.850072][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 18 | [2019-06-19 13:20:19.141056][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 19 | [2019-06-19 13:20:19.428133][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 20 | [2019-06-19 13:20:19.719114][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 21 | [2019-06-19 13:20:19.733763][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 22 | [2019-06-19 13:20:20.020840][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 23 | [2019-06-19 13:20:20.069662][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 24 | [2019-06-19 13:20:20.299131][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 25 | [2019-06-19 13:20:20.931871][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 26 | [2019-06-19 13:20:21.219922][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 27 | [2019-06-19 13:20:22.094826][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 28 | [2019-06-19 13:20:22.382877][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 29 | [2019-06-19 13:20:22.394597][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 30 | [2019-06-19 13:20:22.667026][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 31 | [2019-06-19 13:20:22.957035][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 32 | [2019-06-19 13:20:22.965820][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 33 | [2019-06-19 13:20:23.245087][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 34 | [2019-06-19 13:20:23.537047][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 35 | [2019-06-19 13:20:23.826078][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 36 | [2019-06-19 13:20:24.113192][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 37 | [2019-06-19 13:20:24.362190][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 38 | [2019-06-19 13:20:24.370955][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 39 | [2019-06-19 13:20:24.651179][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 40 | [2019-06-19 13:20:25.045666][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 41 | [2019-06-19 13:20:25.897135][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 42 | [2019-06-19 13:20:26.134411][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 43 | [2019-06-19 13:20:26.151011][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 44 | [2019-06-19 13:20:26.419536][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 45 | [2019-06-19 13:20:26.429302][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 46 | [2019-06-19 13:20:26.717354][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 47 | [2019-06-19 13:20:27.012242][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 48 | [2019-06-19 13:20:27.933037][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 49 | [2019-06-19 13:20:28.223082][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 50 | [2019-06-19 13:20:28.231833][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 51 | [2019-06-19 13:20:28.510132][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 52 | [2019-06-19 13:20:28.804043][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 53 | [2019-06-19 13:20:29.087215][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 54 | [2019-06-19 13:20:29.379175][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 55 | [2019-06-19 13:20:29.667229][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 56 | [2019-06-19 13:20:29.954341][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 57 | [2019-06-19 13:20:30.247243][strategy params]beta:0.01 volatility:0.88|rpnl=-496.88
 58 | [2019-06-19 13:20:30.553870][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 59 | [2019-06-19 13:20:30.824327][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 60 | [2019-06-19 13:20:32.159137][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 61 | [2019-06-19 13:20:32.731390][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 62 | [2019-06-19 13:20:33.021345][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 63 | [2019-06-19 13:20:33.601358][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 64 | [2019-06-19 13:20:33.900154][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 65 | [2019-06-19 13:20:34.431342][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 66 | [2019-06-19 13:20:34.440130][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 67 | [2019-06-19 13:20:34.719434][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 68 | [2019-06-19 13:20:35.009403][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 69 | [2019-06-19 13:20:35.327766][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 70 | [2019-06-19 13:20:35.589416][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 71 | [2019-06-19 13:20:36.167477][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 72 | [2019-06-19 13:20:36.457483][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 73 | [2019-06-19 13:20:36.757253][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 74 | [2019-06-19 13:20:37.672189][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 75 | [2019-06-19 13:20:37.682992][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 76 | [2019-06-19 13:20:38.258098][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 77 | [2019-06-19 13:20:38.542211][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 78 | [2019-06-19 13:20:38.830265][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 79 | [2019-06-19 13:20:39.119328][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 80 | [2019-06-19 13:20:39.128119][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 81 | [2019-06-19 13:20:39.408324][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 82 | [2019-06-19 13:20:39.698330][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 83 | [2019-06-19 13:20:39.988374][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 84 | [2019-06-19 13:20:40.283225][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 85 | [2019-06-19 13:20:40.588854][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 86 | [2019-06-19 13:20:40.859334][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 87 | [2019-06-19 13:20:41.148364][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 88 | [2019-06-19 13:20:41.438371][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 89 | [2019-06-19 13:20:41.730331][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 90 | [2019-06-19 13:20:42.023266][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 91 | [2019-06-19 13:20:42.311320][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 92 | [2019-06-19 13:20:42.322058][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 93 | [2019-06-19 13:20:42.886448][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 94 | [2019-06-19 13:20:43.176455][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 95 | [2019-06-19 13:20:43.466500][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 96 | [2019-06-19 13:20:43.756469][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 97 | [2019-06-19 13:20:44.049406][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 98 | [2019-06-19 13:20:44.063075][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
 99 | [2019-06-19 13:20:44.898919][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
100 | [2019-06-19 13:20:44.912590][strategy params]beta:0.01 volatility:0.89|rpnl=-496.88
101 | *** OPENING SHORT POSITION ***
102 | Placing spread orders...
103 | Order placed: Trade(contract=Forex('EURUSD', exchange='IDEALPRO'), order=MarketOrder(orderId=72, clientId=1, action='SELL', totalQuantity=100, conditions=[], softDollarTier=SoftDollarTier()), orderStatus=OrderStatus(status='PendingSubmit'), fills=[], log=[TradeLogEntry(time=datetime.datetime(2019, 6, 19, 13, 20, 45, 201621, tzinfo=datetime.timezone.utc), status='PendingSubmit', message='')])
104 | Order placed: Trade(contract=Forex('USDJPY', exchange='IDEALPRO'), order=MarketOrder(orderId=73, clientId=1, action='BUY', totalQuantity=100, conditions=[], softDollarTier=SoftDollarTier()), orderStatus=OrderStatus(status='PendingSubmit'), fills=[], log=[TradeLogEntry(time=datetime.datetime(2019, 6, 19, 13, 20, 45, 203573, tzinfo=datetime.timezone.utc), status='PendingSubmit', message='')])
105 | Order IDs pending execution: {72, 73}
106 | Order filled: Trade(contract=Forex('EURUSD', exchange='IDEALPRO'), order=MarketOrder(orderId=72, clientId=1, permId=527821719, action='SELL', totalQuantity=100, lmtPrice=0.0, auxPrice=0.0, tif='DAY', ocaType=3, trailStopPrice=0.12050000000000005, openClose='', eTradeOnly=False, firmQuoteOnly=False, volatilityType=0, deltaNeutralOrderType='None', referencePriceType=0, account='DU1524547', clearingIntent='IB', orderComboLegs=[], adjustedOrderType='None', conditions=[], softDollarTier=SoftDollarTier(), cashQty=0.0, dontUseAutoPriceForHedge=True), orderStatus=OrderStatus(status='Filled', filled=100.0, avgFillPrice=1.12054, permId=527821719, lastFillPrice=1.12054, clientId=1), fills=[Fill(contract=Forex('EURUSD', conId=12087792, exchange='IDEALPRO', localSymbol='EUR.USD', tradingClass='EUR.USD'), execution=Execution(execId='000132b0.5d09c6e2.01.01', time=datetime.datetime(2019, 6, 19, 13, 20, 47, tzinfo=datetime.timezone.utc), acctNumber='DU1524547', exchange='IDEALPRO', side='SLD', shares=100.0, price=1.12054, permId=527821719, clientId=1, orderId=72, cumQty=100.0, avgPrice=1.12054, lastLiquidity=2), commissionReport=CommissionReport(), time=datetime.datetime(2019, 6, 19, 13, 20, 46, 177905, tzinfo=datetime.timezone.utc))], log=[TradeLogEntry(time=datetime.datetime(2019, 6, 19, 13, 20, 45, 201621, tzinfo=datetime.timezone.utc), status='PendingSubmit', message=''), TradeLogEntry(time=datetime.datetime(2019, 6, 19, 13, 20, 46, 60729, tzinfo=datetime.timezone.utc), status='PreSubmitted', message=''), TradeLogEntry(time=datetime.datetime(2019, 6, 19, 13, 20, 46, 177905, tzinfo=datetime.timezone.utc), status='Submitted', message=''), TradeLogEntry(time=datetime.datetime(2019, 6, 19, 13, 20, 46, 177905, tzinfo=datetime.timezone.utc), status='Submitted', message='Fill 100.0@1.12054'), TradeLogEntry(time=datetime.datetime(2019, 6, 19, 13, 20, 46, 177905, tzinfo=datetime.timezone.utc), status='Filled', message='')])
107 | Order IDs pending execution: {73}
108 | Order filled: Trade(contract=Forex('USDJPY', exchange='IDEALPRO'), order=MarketOrder(orderId=73, clientId=1, permId=527821720, action='BUY', totalQuantity=100, lmtPrice=0.0, auxPrice=0.0, tif='DAY', ocaType=3, trailStopPrice=109.465, openClose='', eTradeOnly=False, firmQuoteOnly=False, volatilityType=0, deltaNeutralOrderType='None', referencePriceType=0, account='DU1524547', clearingIntent='IB', orderComboLegs=[], adjustedOrderType='None', conditions=[], softDollarTier=SoftDollarTier(), cashQty=0.0, dontUseAutoPriceForHedge=True), orderStatus=OrderStatus(status='Filled', filled=100.0, avgFillPrice=108.463, permId=527821720, lastFillPrice=108.463, clientId=1), fills=[Fill(contract=Forex('USDJPY', conId=15016059, exchange='IDEALPRO', localSymbol='USD.JPY', tradingClass='USD.JPY'), execution=Execution(execId='000132b0.5d09c6e3.01.01', time=datetime.datetime(2019, 6, 19, 13, 20, 47, tzinfo=datetime.timezone.utc), acctNumber='DU1524547', exchange='IDEALPRO', side='BOT', shares=100.0, price=108.463, permId=527821720, clientId=1, orderId=73, cumQty=100.0, avgPrice=108.463, lastLiquidity=2), commissionReport=CommissionReport(), time=datetime.datetime(2019, 6, 19, 13, 20, 46, 427875, tzinfo=datetime.timezone.utc))], log=[TradeLogEntry(time=datetime.datetime(2019, 6, 19, 13, 20, 45, 203573, tzinfo=datetime.timezone.utc), status='PendingSubmit', message=''), TradeLogEntry(time=datetime.datetime(2019, 6, 19, 13, 20, 46, 411278, tzinfo=datetime.timezone.utc), status='PreSubmitted', message=''), TradeLogEntry(time=datetime.datetime(2019, 6, 19, 13, 20, 46, 412254, tzinfo=datetime.timezone.utc), status='Submitted', message=''), TradeLogEntry(time=datetime.datetime(2019, 6, 19, 13, 20, 46, 427875, tzinfo=datetime.timezone.utc), status='Submitted', message='Fill 100.0@108.463'), TradeLogEntry(time=datetime.datetime(2019, 6, 19, 13, 20, 46, 427875, tzinfo=datetime.timezone.utc), status='Filled', message='')])
109 | Order IDs pending execution: set()
110 | [2019-06-19 13:20:47.229148][account]EURUSD pos=-100.0 avgPrice=1.12054|USDJPY pos=100.0|rpnl=-496.88 upnl=-4.00|beta:0.01 volatility:0.89
111 | [2019-06-19 13:20:48.297070][account]EURUSD pos=-100.0 avgPrice=1.12054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.00|beta:0.01 volatility:0.89
112 | [2019-06-19 13:20:49.851584][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.00|beta:0.01 volatility:0.89
113 | [2019-06-19 13:20:50.986670][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.00|beta:0.01 volatility:0.89
114 | [2019-06-19 13:20:52.084242][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.00|beta:0.01 volatility:0.89
115 | [2019-06-19 13:20:53.279381][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.89
116 | [2019-06-19 13:20:54.451124][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.89
117 | [2019-06-19 13:20:55.592597][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.89
118 | [2019-06-19 13:20:56.711630][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.89
119 | [2019-06-19 13:20:57.875390][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.89
120 | [2019-06-19 13:20:59.308724][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.89
121 | [2019-06-19 13:21:00.477952][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.89
122 | [2019-06-19 13:21:02.556097][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.89
123 | [2019-06-19 13:21:05.305911][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.89
124 | [2019-06-19 13:21:06.439571][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.89
125 | [2019-06-19 13:21:07.552726][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.89
126 | [2019-06-19 13:21:08.697131][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.89
127 | [2019-06-19 13:21:09.843487][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.89
128 | [2019-06-19 13:21:11.883295][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.89
129 | [2019-06-19 13:21:13.084226][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.89
130 | [2019-06-19 13:21:14.819594][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
131 | [2019-06-19 13:21:15.951300][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
132 | [2019-06-19 13:21:17.081055][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
133 | [2019-06-19 13:21:18.227413][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
134 | [2019-06-19 13:21:19.397749][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
135 | [2019-06-19 13:21:20.542636][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
136 | [2019-06-19 13:21:22.263146][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
137 | [2019-06-19 13:21:23.388054][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
138 | [2019-06-19 13:21:24.835160][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
139 | [2019-06-19 13:21:26.253907][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
140 | [2019-06-19 13:21:27.949202][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
141 | [2019-06-19 13:21:29.267706][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
142 | [2019-06-19 13:21:30.439290][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
143 | [2019-06-19 13:21:31.608061][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
144 | [2019-06-19 13:21:32.846019][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
145 | [2019-06-19 13:21:34.021263][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
146 | [2019-06-19 13:21:35.167059][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
147 | [2019-06-19 13:21:36.299834][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
148 | [2019-06-19 13:21:38.323869][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
149 | [2019-06-19 13:21:39.446353][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
150 | [2019-06-19 13:21:41.110650][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
151 | [2019-06-19 13:21:42.241010][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
152 | [2019-06-19 13:21:43.390851][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
153 | [2019-06-19 13:21:45.381832][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
154 | [2019-06-19 13:21:46.559531][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
155 | [2019-06-19 13:21:47.669996][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.03|beta:0.01 volatility:0.89
156 | [2019-06-19 13:21:49.113598][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.03|beta:0.01 volatility:0.89
157 | [2019-06-19 13:21:50.703867][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
158 | [2019-06-19 13:21:51.899108][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
159 | [2019-06-19 13:21:53.106001][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
160 | [2019-06-19 13:21:54.271887][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
161 | [2019-06-19 13:21:55.391932][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
162 | [2019-06-19 13:21:56.564599][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
163 | [2019-06-19 13:21:57.694375][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
164 | [2019-06-19 13:21:58.834886][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
165 | [2019-06-19 13:21:59.983158][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.89
166 | [2019-06-19 13:22:01.122682][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
167 | [2019-06-19 13:22:02.265131][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.88
168 | [2019-06-19 13:22:03.402699][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.02|beta:0.01 volatility:0.88
169 | [2019-06-19 13:22:04.572488][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
170 | [2019-06-19 13:22:06.450261][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
171 | [2019-06-19 13:22:07.611212][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
172 | [2019-06-19 13:22:09.708633][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
173 | [2019-06-19 13:22:10.849131][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
174 | [2019-06-19 13:22:12.413407][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
175 | [2019-06-19 13:22:13.886876][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
176 | [2019-06-19 13:22:15.077348][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
177 | [2019-06-19 13:22:16.228585][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
178 | [2019-06-19 13:22:17.359356][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
179 | [2019-06-19 13:22:18.529108][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
180 | [2019-06-19 13:22:21.492643][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
181 | [2019-06-19 13:22:22.686875][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
182 | [2019-06-19 13:22:23.859594][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
183 | [2019-06-19 13:22:25.419938][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
184 | [2019-06-19 13:22:27.151190][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
185 | [2019-06-19 13:22:28.734054][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
186 | [2019-06-19 13:22:29.889197][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
187 | [2019-06-19 13:22:32.229722][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
188 | [2019-06-19 13:22:33.387796][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
189 | [2019-06-19 13:22:34.556609][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
190 | [2019-06-19 13:22:35.683434][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
191 | [2019-06-19 13:22:37.468397][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88
192 | [2019-06-19 13:22:39.312562][account]EURUSD pos=-100.0 avgPrice=1.10054|USDJPY pos=100.0|rpnl=-496.87 upnl=-4.01|beta:0.01 volatility:0.88


--------------------------------------------------------------------------------
/sample_output/videoshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesmawm/High-Frequency-Trading-Model-with-IB/8e96ade54a8e3eb94ead04827a846225ae122864/sample_output/videoshot.gif


--------------------------------------------------------------------------------
/util/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesmawm/High-Frequency-Trading-Model-with-IB/8e96ade54a8e3eb94ead04827a846225ae122864/util/__init__.py


--------------------------------------------------------------------------------
/util/dt_util.py:
--------------------------------------------------------------------------------
 1 | import pandas as pd
 2 | from dateutil import tz
 3 | 
 4 | UTC_TIMEZONE = tz.tzutc()
 5 | LOCAL_TIMEZONE = tz.tzlocal()
 6 | 
 7 | 
 8 | def convert_utc_datetime(datetime):
 9 | 	utc = datetime.replace(tzinfo=UTC_TIMEZONE)
10 | 	local_time = utc.astimezone(LOCAL_TIMEZONE)
11 | 	return pd.to_datetime(local_time)
12 | 
13 | 
14 | def convert_local_datetime(datetime):
15 | 	local_time = datetime.replace(tzinfo=LOCAL_TIMEZONE)
16 | 	return pd.to_datetime(local_time)
17 | 


--------------------------------------------------------------------------------
/util/order_util.py:
--------------------------------------------------------------------------------
1 | def get_order_action(qty):
2 | 	return 'BUY' if qty >= 0 else 'SELL'
3 | 


--------------------------------------------------------------------------------