├── client
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── index.html
├── src
│ ├── Components
│ │ ├── Pages
│ │ │ ├── PageConstants.js
│ │ │ ├── images
│ │ │ │ ├── RCOSlogo.png
│ │ │ │ ├── BrokerBot.png
│ │ │ │ ├── alpacaLogo.png
│ │ │ │ ├── github-logo.png
│ │ │ │ └── stocks.svg
│ │ │ ├── HomePage
│ │ │ │ └── index.js
│ │ │ └── LoginPage
│ │ │ │ ├── LoginRegisterContent.js
│ │ │ │ ├── FormTextInput.js
│ │ │ │ ├── LoginBar.js
│ │ │ │ ├── RegisterBar.js
│ │ │ │ └── index.js
│ │ └── Routes
│ │ │ ├── PublicRoute.js
│ │ │ └── PrivateRoute.js
│ ├── redux
│ │ ├── store.js
│ │ └── userSlice.js
│ ├── index.js
│ ├── AppRouter.js
│ └── style.css
├── package.json
└── README.md
├── files
└── Symbols.csv
├── src
├── ENUMS.py
├── RL.py
├── MainControl.py
├── Searcher.py
├── Strategy.py
├── BrokerBot.py
├── StrategyHandler.py
├── DataHandler.py
├── Factory.py
├── PortfolioManager.py
└── ExecutionHandler.py
├── README.md
├── LICENSE
├── test
├── test_proxy2.py
├── test_bt_graphing.py
├── test_proxy.py
├── test_bt_analyzers.py
└── data
│ └── AAPL.csv
└── .gitignore
/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackMansfield2019/BrokerBot/HEAD/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackMansfield2019/BrokerBot/HEAD/client/public/logo192.png
--------------------------------------------------------------------------------
/client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackMansfield2019/BrokerBot/HEAD/client/public/logo512.png
--------------------------------------------------------------------------------
/client/src/Components/Pages/PageConstants.js:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | export const LoginPageScreenPosition = {Left: 0, Right: 35}
--------------------------------------------------------------------------------
/client/src/Components/Pages/images/RCOSlogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackMansfield2019/BrokerBot/HEAD/client/src/Components/Pages/images/RCOSlogo.png
--------------------------------------------------------------------------------
/files/Symbols.csv:
--------------------------------------------------------------------------------
1 | Symbols
2 | GOOGL
3 | FB
4 | AMZN
5 | MSFT
6 | AAPL
7 | TSLA
8 | TSM
9 | BABA
10 | JPM
11 | JNJ
12 | WMT
13 | NVDA
14 | BAC
15 |
--------------------------------------------------------------------------------
/client/src/Components/Pages/images/BrokerBot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackMansfield2019/BrokerBot/HEAD/client/src/Components/Pages/images/BrokerBot.png
--------------------------------------------------------------------------------
/client/src/Components/Pages/images/alpacaLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackMansfield2019/BrokerBot/HEAD/client/src/Components/Pages/images/alpacaLogo.png
--------------------------------------------------------------------------------
/client/src/Components/Pages/images/github-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackMansfield2019/BrokerBot/HEAD/client/src/Components/Pages/images/github-logo.png
--------------------------------------------------------------------------------
/client/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit';
2 | import userReducer from './userSlice';
3 |
4 |
5 | // Use ES6 object literal shorthand syntax to define the object shape
6 | export default configureStore({
7 | reducer: {
8 | userData: userReducer,
9 | }
10 | })
11 |
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | import AppRouter from './AppRouter';
5 | import reduxStore from './redux/store'
6 | import './style.css';
7 |
8 | //Need to add the redux store in Provider Tag
9 | ReactDOM.render(
10 |
11 |
12 | ,
13 | document.getElementById('root')
14 | );
--------------------------------------------------------------------------------
/client/src/Components/Pages/HomePage/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 |
4 | class HomePage extends React.Component {
5 | constructor(props){
6 | super(props);
7 |
8 | this.state = {
9 | /*Fill in the state variables later with Broker Data needed for graphs and bots */
10 | }
11 | }
12 |
13 |
14 | render(){
15 | return <>>
16 | }
17 | }
18 |
19 | export default connect()(HomePage);
--------------------------------------------------------------------------------
/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/client/src/redux/userSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 |
3 | export const userSlice = createSlice({
4 | name: 'userData',
5 | initialState: {
6 | userID: '',
7 | userEmail: '',
8 | isAuthenticated: false,
9 | },
10 | reducers: {
11 | userLogin: (state, actions) => {
12 | state.userEmail = actions.payload.email;
13 | state.userID = actions.payload.ID;
14 | state.isAuthenticated = true;
15 | }
16 | }
17 |
18 |
19 | });
20 |
21 |
22 | export const { userLogin } = userSlice.actions;
23 | export default userSlice.reducer;
--------------------------------------------------------------------------------
/client/src/Components/Routes/PublicRoute.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, Redirect, RouteProps } from 'react-router-dom';
3 | //import { ROUTES } from '../constants';
4 |
5 | interface PublicRouteProps {
6 | restricted?: boolean;
7 | }
8 |
9 | function PublicRoute(props: PublicRouteProps & RouteProps): React.ReactElement {
10 | const { component: Component, restricted = false, ...rest } = props;
11 |
12 | const render = props => {
13 | if ( restricted ) {
14 | return ;
15 | }
16 |
17 | return ;
18 | };
19 |
20 | return ;
21 | }
22 |
23 | export default PublicRoute;
--------------------------------------------------------------------------------
/client/src/Components/Routes/PrivateRoute.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, Redirect, RouteProps } from 'react-router-dom';
3 | import { connect } from 'react-redux';
4 | //import { ROUTES } from '/routes';
5 |
6 | function PrivateRoute(props: RouteProps): React.ReactElement {
7 | const { component: Component, ...rest } = props;
8 |
9 | const render = props => {
10 | if (!props.isAuthenticated) {
11 | return ;
12 | }
13 |
14 | return ;
15 | };
16 |
17 | return ;
18 | }
19 |
20 | const MapStateToProps = (state) => ({
21 | isAuthenticated: state.userData.isAuthenticated
22 | });
23 |
24 | export default connect(MapStateToProps)(PrivateRoute);
--------------------------------------------------------------------------------
/client/src/AppRouter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BrowserRouter, Switch } from 'react-router-dom';
3 |
4 | import PrivateRoute from './Components/Routes/PrivateRoute';
5 | import PublicRoute from './Components/Routes/PublicRoute';
6 |
7 | /*------------------------------ App Pages ------------------------------*/
8 | import LoginPage from './Components/Pages/LoginPage/index';
9 | import HomePage from './Components/Pages/HomePage/index';
10 |
11 |
12 |
13 | class AppRouter extends React.Component {
14 | render() {
15 | return (
16 |
17 |
18 | {/* ------------------ Public Routes Below ------------------- */}
19 |
20 |
21 | {/* ------------------ Private Routes Below ------------------ */}
22 |
23 |
24 |
25 | );
26 | }
27 | }
28 |
29 | export default AppRouter;
--------------------------------------------------------------------------------
/client/src/Components/Pages/LoginPage/LoginRegisterContent.js:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | export default function LoginRiegisterContent(props) {
5 | return
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Lorem ipsum dolor sit amet, consectetur adipiscing elit
15 |
16 |
17 | Ut enim ad minim veniam, quis nostrud
18 |
19 |
20 |
21 |
22 | }
--------------------------------------------------------------------------------
/src/ENUMS.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 | class Strategies(Enum):
4 | SHORT = 1
5 | MEDIUM = 2
6 | LONG = 3
7 |
8 | SHORT_LOW_RISK = 1
9 | MEDIUM_LOW_RISK = 2
10 | LONG_LOW_RISK = 3
11 |
12 | SHORT_MEDIUM_RISK = 4
13 | MEDIUM_MEDIUM_RISK = 5
14 | LONG_MEDIUM_RISK = 6
15 |
16 | SHORT_HIGH_RISK = 7
17 | MEDIUM_HIGH_RISK = 8
18 | LONG_HIGH_RISK = 9
19 |
20 | class Risk(Enum):
21 | SHORT_LOW_RISK = 1
22 | MEDIUM_LOW_RISK = 2
23 | LONG_LOW_RISK = 3
24 |
25 | SHORT_MEDIUM_RISK = 4
26 | MEDIUM_MEDIUM_RISK = 5
27 | LONG_MEDIUM_RISK = 6
28 |
29 | SHORT_HIGH_RISK = 7
30 | MEDIUM_HIGH_RISK = 8
31 | LONG_HIGH_RISK = 9
32 |
33 |
34 | class DH_API(Enum):
35 | ALPACA = 1
36 |
37 | BINANCE = 2
38 | POLYGON = 3
39 | IBKR = 4
40 | ALPHA = 5
41 |
42 |
43 | class EH_API(Enum):
44 | ALPACA = 1
45 |
46 | BINANCE = 2
47 | IBKR = 3
48 | ALPHA = 4
49 |
50 | class Stop_loss(Enum):
51 | RSI = 1
52 | STATIC = 2
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BrokerBot
2 | Broker Bot is an autonomous trading algorithm designed to continuously analyze New York Stock Exchange (NYSE) market conditions and execute profitable trades by utilizing advanced trading strategies. Built upon the Alpaca API, Broker Bot will be tuned through the extensive backtesting and paper trading capabilities provided.
3 |
4 | ## Installation Guide
5 | Before installing the following softwares, have the latest version of Python and Javascript(React) installed. Also note that these install commands are specific for the Ubuntu bash terminal.
6 | To Make sure ubuntu is up to date: `$sudo apt update`
7 | Step 1: Clone the repository locally onto your machine and open it up in your favorite editor
8 | (For this Project we Reccomend VScode which can be found here: https://code.visualstudio.com/).
9 | Step 2: Use the command `cd client` to go to the client directory
10 | Step 3: Use the following commands to install the packages onto your machine `npm install`
11 | Step 4: Start the application by running `npm run start`
12 |
--------------------------------------------------------------------------------
/client/src/Components/Pages/LoginPage/FormTextInput.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function FormTextInput(props) {
4 | return
5 |
{props.Variables.title}
6 |
7 | props.Functions.updateText(e)} autoComplete="off" spellCheck="false" style={{background: props.Variables.backgroundColor}} required >
9 | {props.children}
10 |
11 | {props.Variables.showForgotPassword ?
12 | props.Functions.forgotPasswordPressed()}>Forgot Password?
13 |
: <>>}
14 |
15 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 JackMansfield2019
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 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@reduxjs/toolkit": "^1.5.1",
7 | "@testing-library/jest-dom": "^5.12.0",
8 | "@testing-library/react": "^11.2.7",
9 | "@testing-library/user-event": "^12.8.3",
10 | "react": "^17.0.2",
11 | "react-dom": "^17.0.2",
12 | "react-icons": "^4.2.0",
13 | "react-redux": "^7.2.4",
14 | "react-router-dom": "^5.2.0",
15 | "react-scripts": "4.0.3",
16 | "web-vitals": "^1.1.2"
17 | },
18 | "scripts": {
19 | "start": "react-scripts start",
20 | "build": "react-scripts build",
21 | "test": "react-scripts test",
22 | "eject": "react-scripts eject"
23 | },
24 | "eslintConfig": {
25 | "extends": [
26 | "react-app",
27 | "react-app/jest"
28 | ]
29 | },
30 | "browserslist": {
31 | "production": [
32 | ">0.2%",
33 | "not dead",
34 | "not op_mini all"
35 | ],
36 | "development": [
37 | "last 1 chrome version",
38 | "last 1 firefox version",
39 | "last 1 safari version"
40 | ]
41 | },
42 | "devDependencies": {
43 | "redux-devtools": "^3.7.0"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test/test_proxy2.py:
--------------------------------------------------------------------------------
1 | import threading
2 | import websocket
3 | import json
4 |
5 |
6 | def on_open(ws):
7 | print("opened")
8 | auth_data = {
9 | "action": "authenticate",
10 | "data": {"key_id": "INSERT_KEY", "secret_key": "INSERT_KEY"}
11 | }
12 |
13 | ws.send(json.dumps(auth_data))
14 |
15 | listen_message = {"action": "listen", "data": {"streams": ["T.TSLA"]}}
16 |
17 | ws.send(json.dumps(listen_message))
18 |
19 |
20 | def on_message(ws, message):
21 | print("received a message")
22 | print(message)
23 |
24 |
25 | def on_close(ws):
26 | print("closed connection")
27 |
28 |
29 | socket = "ws://127.0.0.1:8765"
30 |
31 | # ws = websocket.WebSocketApp(socket, on_open=on_open,
32 | # on_message=on_message, on_close=on_close)
33 |
34 | ws = websocket.WebSocketApp(socket,
35 | on_message=lambda ws, msg: on_message(ws,
36 | msg),
37 | on_close=lambda ws: on_close(ws),
38 | on_open=lambda ws: on_open(ws))
39 |
40 |
41 | t1 = threading.Thread(target=ws.run_forever, args=())
42 | t1.start()
43 |
--------------------------------------------------------------------------------
/test/test_bt_graphing.py:
--------------------------------------------------------------------------------
1 | '''
2 | Backtrader Plotting - example taken from API docs and modified
3 | - takes in data from...
4 | * GenericCSVData *
5 | VisualChartCSVData
6 | YahooFinanceData (for online downloads)
7 | * YahooFinanceCSVData (for already downloaded data) *
8 | BacktraderCSVData (in-house … for testing purposed, but can be used)
9 |
10 | YahooFinanceCSVData has similar format to our DH, cols are
11 | Date Open High Low Close Adj Close Volume
12 |
13 | - essentially combines backtrader with matplotlib to make it easier to plot
14 | strategies and results
15 | - can use a number of technical indicators through bt.Strategy, not sure how this would
16 | integrate with our classes
17 |
18 | '''
19 |
20 | from __future__ import (absolute_import, division, print_function,
21 | unicode_literals)
22 |
23 | import backtrader as bt
24 |
25 |
26 | class St(bt.Strategy):
27 | def __init__(self):
28 | self.sma = bt.indicators.SimpleMovingAverage(self.data)
29 |
30 |
31 | data = bt.feeds.YahooFinanceCSVData(dataname='data/AAPL.csv')
32 |
33 | cerebro = bt.Cerebro()
34 | cerebro.adddata(data)
35 | cerebro.addstrategy(St)
36 | cerebro.run()
37 | cerebro.plot()
38 |
--------------------------------------------------------------------------------
/test/test_proxy.py:
--------------------------------------------------------------------------------
1 | import threading
2 | import websocket
3 | import json
4 | import time
5 |
6 |
7 | def on_open(ws):
8 | print("opened")
9 | auth_data = {
10 | "action": "authenticate",
11 | "data": {"key_id": "INSERT_KEY", "secret_key": "INSERT_KEY"}
12 | }
13 |
14 | ws.send(json.dumps(auth_data))
15 |
16 | listen_message = {"action": "listen", "data": {"streams": ["T.TSLA"]}}
17 |
18 | ws.send(json.dumps(listen_message))
19 |
20 |
21 | def on_message(ws, message):
22 | print("WS 1: received a message")
23 | print(message)
24 | print()
25 |
26 |
27 | def on_message2(ws, message):
28 | print("WS 2: received a message")
29 | print(message)
30 | print()
31 |
32 |
33 | def on_close(ws):
34 | print("closed connection")
35 |
36 |
37 | socket = "ws://127.0.0.1:8765"
38 |
39 | # ws = websocket.WebSocketApp(socket, on_open=on_open,
40 | # on_message=on_message, on_close=on_close)
41 |
42 | ws = websocket.WebSocketApp(socket,
43 | on_message=lambda ws, msg: on_message(ws,
44 | msg),
45 | on_close=lambda ws: on_close(ws),
46 | on_open=lambda ws: on_open(ws))
47 |
48 | ws2 = websocket.WebSocketApp(socket,
49 | on_message=lambda ws, msg: on_message2(ws,
50 | msg),
51 | on_close=lambda ws: on_close(ws),
52 | on_open=lambda ws: on_open(ws))
53 |
54 |
55 | t1 = threading.Thread(target=ws.run_forever, args=())
56 | t2 = threading.Thread(target=ws2.run_forever, args=())
57 |
58 | t1.start()
59 | t2.start()
60 |
61 | time.sleep(10)
62 | t1.kill()
63 | t2.kill()
64 |
65 | print("joined")
66 |
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #Front End
2 | # dependencies
3 | /client/node_modules
4 |
5 | # Byte-compiled / optimized / DLL files
6 | __pycache__/
7 | *.py[cod]
8 | *$py.class
9 |
10 | # C extensions
11 | *.so
12 |
13 | # Distribution / packaging
14 | .Python
15 | build/
16 | develop-eggs/
17 | dist/
18 | downloads/
19 | eggs/
20 | .eggs/
21 | lib/
22 | lib64/
23 | parts/
24 | sdist/
25 | var/
26 | wheels/
27 | pip-wheel-metadata/
28 | share/python-wheels/
29 | *.egg-info/
30 | .installed.cfg
31 | *.egg
32 | MANIFEST
33 |
34 | # PyInstaller
35 | # Usually these files are written by a python script from a template
36 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
37 | *.manifest
38 | *.spec
39 |
40 | # Installer logs
41 | pip-log.txt
42 | pip-delete-this-directory.txt
43 |
44 | # Unit test / coverage reports
45 | htmlcov/
46 | .tox/
47 | .nox/
48 | .coverage
49 | .coverage.*
50 | .cache
51 | nosetests.xml
52 | coverage.xml
53 | *.cover
54 | *.py,cover
55 | .hypothesis/
56 | .pytest_cache/
57 |
58 | # Translations
59 | *.mo
60 | *.pot
61 |
62 | # Django stuff:
63 | *.log
64 | local_settings.py
65 | db.sqlite3
66 | db.sqlite3-journal
67 |
68 | # Flask stuff:
69 | instance/
70 | .webassets-cache
71 |
72 | # Scrapy stuff:
73 | .scrapy
74 |
75 | # Sphinx documentation
76 | docs/_build/
77 |
78 | # PyBuilder
79 | target/
80 |
81 | # Jupyter Notebook
82 | .ipynb_checkpoints
83 |
84 | # IPython
85 | profile_default/
86 | ipython_config.py
87 |
88 | # pyenv
89 | .python-version
90 |
91 | # pipenv
92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
95 | # install all needed dependencies.
96 | #Pipfile.lock
97 |
98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
99 | __pypackages__/
100 |
101 | # Celery stuff
102 | celerybeat-schedule
103 | celerybeat.pid
104 |
105 | # SageMath parsed files
106 | *.sage.py
107 |
108 | # Environments
109 | .env
110 | .venv
111 | env/
112 | venv/
113 | ENV/
114 | env.bak/
115 | venv.bak/
116 |
117 | # Spyder project settings
118 | .spyderproject
119 | .spyproject
120 |
121 | # Rope project settings
122 | .ropeproject
123 |
124 | # mkdocs documentation
125 | /site
126 |
127 | # mypy
128 | .mypy_cache/
129 | .dmypy.json
130 | dmypy.json
131 |
132 | # Pyre type checker
133 | .pyre/
134 |
135 | src/secrets.py
136 | scr/config.py
137 |
--------------------------------------------------------------------------------
/client/src/Components/Pages/LoginPage/LoginBar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { HiOutlineMail } from "react-icons/hi";
3 | import { HiOutlineLockClosed } from "react-icons/hi";
4 | import FormTextInput from './FormTextInput'
5 | //import { Link } from 'react-router-dom';
6 | //Work on the redirect o the Home Page
7 |
8 | var CURRENT_ID = 0;
9 |
10 | export default function LoginBar(props) {
11 |
12 | return
13 |
14 |
15 | props.Functions.moveScreenTo(props.Variables.LeftScreenPosition)}>Register
16 |
17 |
18 | props.Functions.LearnMorePressed()}>Learn More
19 |
20 |
21 |
22 |
23 | Welcome Back!
24 |
25 |
26 | Sign into your account
27 |
28 |
29 |
55 |
56 |
57 | }
--------------------------------------------------------------------------------
/client/src/Components/Pages/LoginPage/RegisterBar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { HiOutlineMail } from "react-icons/hi";
3 | import { HiOutlineLockClosed } from "react-icons/hi";
4 |
5 | import FormTextInput from './FormTextInput'
6 |
7 | var CURRENT_ID = 0;
8 |
9 | export default function RegisterBar(props) {
10 |
11 | return
12 |
13 |
14 | console.log('im a button')}>Learn More
15 |
16 |
17 | props.Functions.moveScreenTo(props.Variables.RightScreenPosition)}>Log In
18 |
19 |
20 |
21 |
22 | Welcome!
23 |
24 |
25 | Sign up for your new account today
26 |
27 |
28 | {/* From here down not finished */}
29 |
60 |
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/src/RL.py:
--------------------------------------------------------------------------------
1 | class Trader():
2 | def __init__(self, state_size, is_eval=False, model_name=""):
3 | self.state_size = state_size # normalized previous days
4 | self.action_size = 3 # hold, buy, sell
5 | self.memory = deque(maxlen=1000)
6 | self.inventory = []
7 | self.model_name = model_name
8 | self.is_eval = is_eval
9 | self.gamma = 0.95
10 | self.epsilon = 1.0
11 | self.epsilon_min = 0.01
12 | self.epsilon_decay = 0.995
13 | self.model = load_model(model_name) if is_eval else self._model()
14 |
15 | def _model(self):
16 | model = Sequential()
17 | model.add(Dense(units=64, input_dim=self.state_size, activation="relu"))
18 | model.add(Dense(units=32, activation="relu"))
19 | model.add(Dense(units=8, activation="relu"))
20 | model.add(Dense(self.action_size, activation="linear"))
21 | model.compile(loss="mse", optimizer=Adam(lr=0.001))
22 | return model
23 |
24 | def act(self, state):
25 | if not self.is_eval and random.random() <= self.epsilon:
26 | return random.randrange(self.action_size)
27 | options = self.model.predict(state)
28 | return np.argmax(options[0])
29 |
30 | def expReplay(self, batch_size):
31 | mini_batch = []
32 | l = len(self.memory)
33 | for i in range(l - batch_size + 1, l):
34 | mini_batch.append(self.memory[i])
35 | for state, action, reward, next_state, done in mini_batch:
36 | target = reward
37 | if not done:
38 | target = reward + self.gamma * np.amax(self.model.predict(next_state)[0])
39 | target_f = self.model.predict(state)
40 | target_f[0][action] = target
41 | self.model.fit(state, target_f, epochs=1, verbose=0)
42 | if self.epsilon > self.epsilon_min:
43 | self.epsilon *= self.epsilon_decay
44 |
45 |
46 | class RL(Strategy):
47 | # need to get the stock data from data handler
48 | def getStockDataVec(key):
49 | data = []
50 | #rsi = []
51 | #lines = open(key+".csv","r").read().splitlines()
52 | for line in lines[15:]:
53 | data.append(float(line.split(",")[1]))
54 | #rsi.append(float(line.split(",")[8]))
55 | #return data, rsi
56 | return data
57 |
58 | def sigmoid(x):
59 | return 1/(1+math.exp(-x))
60 |
61 |
62 | # cerebro gives us next data in dataframe, so need to incorporate cerebro in order to get the state
63 | def getState(data, t, n): #getState(data, rsi, t, n)
64 | d = t - n + 1
65 | if d >= 0:
66 | block1 = data[d:t+1]
67 | #block2 = rsi[d:t+1]
68 | else:
69 | block1 = -d * [data[0]] + data[0:t+1] # pad with t0
70 | #block2 = -d * [rsi[0]] + rsi[0:t+1] # pad with t0
71 | res = []
72 | for i in range(n-1):
73 | res.append((sigmoid(block1[i+1] - block1[i])))
74 | #res.append((sigmoid(block1[i+1] - block1[i])) + (sigmoid(block2[i+1] - block2[i])))
75 | return np.array([res])
76 |
77 | """
78 | def formatPrice(n):
79 | return("-$" if n < 0 else "$") + "{0:.2f}".format(abs(n))
80 | """
81 |
82 | def run():
83 | # set as default
84 | window_size = 30
85 |
86 | trader = Trader(window_size)
87 |
88 | # set as default
89 | batch_size = 32
90 |
91 | # need to figure out what terminated should be
92 | while(not terminated):
93 | data = self.getStockDataVec(key):
94 | state = getState(data, t, window_size + 1) # need to fix t
95 | ction = trader.act(state)
96 |
97 | # hold
98 | next_state = getState(data, t + 1, window_size + 1)
99 | reward = 0
100 |
101 | #---------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------
/src/MainControl.py:
--------------------------------------------------------------------------------
1 | from BrokerBot import BrokerBot
2 | from Searcher import Searcher
3 | from multiprocessing import Process, Pipe
4 | # import Searcher
5 | import datetime
6 | import pytz # pip
7 | import holidays # pip
8 | import os
9 | import threading
10 | from dotenv import load_dotenv # pip
11 | import time
12 |
13 | DEBUG = True
14 |
15 |
16 | class MainControl:
17 |
18 | # TODO: figure out config format and proper parseing technique
19 | # TODO: make BB and searcher extend thread class
20 | def __init__(self):
21 | self.api_key = ""
22 | self.secret_key = ""
23 | self.base_url = ""
24 | self.socket = ""
25 | self.broker_bots = []
26 | self.searchers = []
27 |
28 | try:
29 | self.api_key = os.environ['API_KEY']
30 | self.secret_key = os.environ['SECRET_KEY']
31 | self.base_url = os.environ['BASE_URL']
32 | self.socket = os.environ['SOCKET']
33 | except Exception:
34 | load_dotenv()
35 | self.api_key = os.getenv('API_KEY')
36 | self.secret_key = os.getenv('SECRET_KEY')
37 | self.base_url = os.getenv('BASE_URL')
38 | self.socket = os.getenv('SOCKET')
39 |
40 | bb_conn, search_conn = Pipe()
41 | self.broker_bots.append(BrokerBot(
42 | self.api_key, self.secret_key, self.base_url, self.socket, bb_conn))
43 |
44 | # self.searchers.append(Searcher(
45 | # self.api_key, self.secret_key, self.base_url, self.socket, search_conn))
46 |
47 | def run(self):
48 | bb_proc = Process(target=self.broker_bots[0].run, args=())
49 | # search_proc = Process(target=self.searchers[0].run, args=())
50 | # TODO: Implement timing algo fully
51 | while market_closed and not DEBUG:
52 | print("MARKET CLOSED : SLEEPING FOR 1 MIN")
53 | time.sleep(60)
54 | continue
55 |
56 |
57 | # eventually make this loop starting bb and searchers for multiple users
58 |
59 | bb_proc.start()
60 | while True:
61 | if market_closed and not DEBUG:
62 | bb_proc.join()
63 | else:
64 | time.sleep(60)
65 | continue
66 | # search_proc.start()
67 |
68 | # def test_data_ingest(self):
69 | # # Goal : spin up several broker bots on different threads with same API key -> same socket
70 | # # see if proxy agent works with different socket instances w/ same key or need to
71 | # # instanitnate bb/searcher with one ws object
72 | # broker_bots = []
73 | # listening_threads = []
74 | # tickers = ["TSLA", "AAPL", "GME", "AMC", "ROKU"]
75 | # for i in range(2):
76 | # broker_bots.append(
77 | # BrokerBot(self.api_key, self.secret_key, self.base_url, self.data_url))
78 |
79 | # t1 = threading.Thread(target=broker_bots[0].test_stream_data, args=(tickers[0],))
80 | # t2 = threading.Thread(target=broker_bots[1].test_stream_data, args=(tickers[1],))
81 |
82 | # t1.start()
83 | # t2.start()
84 | # # ticker_counter = 0
85 | # # for bot in broker_bots:
86 | # # stream_thread = threading.Thread(
87 | # # target=bot.test_stream_data, args=(tickers[ticker_counter],))
88 |
89 | # # listening_threads.append(stream_thread)
90 | # # ticker_counter += 1
91 |
92 | # # for thread in listening_threads:
93 | # # thread.start()
94 |
95 |
96 | def market_closed(now=None):
97 | tz = pytz.timezone('US/Eastern')
98 | us_holidays = holidays.US()
99 |
100 | openTime = datetime.time(hour=9, minute=30, second=0)
101 | closeTime = datetime.time(hour=16, minute=0, second=0)
102 |
103 | if not now:
104 | now = datetime.datetime.now(tz)
105 |
106 | # If a holiday
107 | if now.strftime('%Y-%m-%d') in us_holidays:
108 | return True
109 | # If before 0930 or after 1600
110 | if (now.time() < openTime) or (now.time() > closeTime):
111 | return True
112 | # If it's a weekend
113 | if now.date().weekday() > 4:
114 | return True
115 |
116 | return False
117 |
118 |
119 | if __name__ == "__main__":
120 | main_control = MainControl()
121 | main_control.run()
122 |
--------------------------------------------------------------------------------
/src/Searcher.py:
--------------------------------------------------------------------------------
1 | import websocket
2 | import json
3 | import numpy as np
4 | import pandas as pd
5 | import requests
6 | import math
7 | import alpaca_trade_api as tradeapi
8 | import time # used for calculating time
9 | from statistics import mean # used to calculate avg volume
10 | from enum import Enum
11 |
12 | class TimeFrame(Enum):
13 | ONE_MIN = "1Min"
14 | FIVE_MIN = "5Min"
15 | FIFTEEN_MIN = "15Min"
16 | ONE_HOUR = "1Hour"
17 | ONE_DAY = "1Day"
18 |
19 | class Searcher:
20 | def __init__(self, API_key_id, API_secret_key, base_url, socket, strat_conns):
21 | self.headers = {"APCA-API-KEY-ID": API_key_id,"APCA-API-SECRET-KEY": API_secret_key}
22 | self.base_url = base_url
23 | self.account_url= "{}/v2/account".format(self.base_url)
24 | self.order_url = "{}/v2/orders".format(self.base_url)
25 | self.strat_conns = strat_conns
26 | self.strat_counter = 0
27 | self.stock_set = set()
28 | self.api = tradeapi.REST(
29 | self.headers["APCA-API-KEY-ID"],
30 | self.headers["APCA-API-SECRET-KEY"],
31 | base_url
32 | )
33 | # self.api_account = api.get_account()
34 | self.socket = socket
35 | self.DH = AlpacaDataHandler(API_key_id, API_secret_key, base_url)
36 |
37 | """
38 | Columns = ['Ticker', 'Time', 'Volume']
39 | self.dataframe = pd.DataFrame(columns = Columns)
40 | self.stock_data = dataframe.set_index("Ticker", drop = False)
41 |
42 | # sets the time for each stock to the time we first initialize the searcher.
43 | for stock in self.stocks:
44 | t = int(time.time())
45 | self.stock_data = self.stock_data.append( pd.Series([ stock, t], index = cols ), ignore_index = True)
46 | """
47 | self.stocks = pd.read_csv("files/Symbols.csv")
48 | time = int(time.time())
49 | stock_data = {}
50 | for stock in stocks:
51 | stock_data[stock] = time
52 | #self.queue = [[]] # priority queue
53 |
54 | def get_account(self):
55 | return account
56 |
57 | """
58 | Overview: updates the priority of each stock, THIS IS THE RUN METHOD OF SCREENER
59 | Effects: updates the weights of the priority queue's stocks
60 | """
61 | def search(self):
62 | for stock in self.stocks:
63 | time_initial = stock_data[stock]
64 | time_final, stock_volume = self.get_data(stock, time_initial, TimeFrame.FIVE_MIN)
65 | stock_data[stock] = time_final
66 | self.ACV(stock_volume, stock)
67 |
68 |
69 | """
70 | Overview: returns the previous 5-minute-volume for the given stock by the client
71 | Returns: volume of the stock that was passed in
72 | Throws:
73 | - Exception if time_initial < 0
74 | - Exception if stock is None/Null
75 | N.B.: Ticker Limit per API Request = 200
76 | """
77 | def get_data(self, stock, time_initial, timeframe):
78 | if time_initial < 0: raise Exception("Time Initial cannot be < 0!")
79 | if stock is None or stock == "": raise Exception("stock cannot be None/Null or blank!")
80 |
81 | time_current = int(time.time())
82 | bars = int((time_current - time_initial) / 300)
83 | barset = DH.get_bars(stock, time_initial, time_current, timeframe, bars)
84 |
85 | #assuming the previous bar from current is in the last index of barset
86 | #last = len(barset) - 1
87 | #stock_time = barset[last][0]
88 |
89 | stock_volume = []
90 | row_count = barset.shape[0]
91 | stock_time = time_current
92 | for i in range(0, row_count):
93 | stock_close.append(barset.iloc[i, 5])
94 | return stock_time, stock_volume
95 |
96 |
97 | """
98 | Overview: calculates the average change in volume
99 | Requires: volumes is not None
100 | Returns: updated priorities of each stock in stocks list
101 | """
102 | def ACV(self, volumes, stock):
103 | acv = int(mean(volumes))
104 | s = [acv, stock]
105 | length = len(self.queue)
106 |
107 | if len(self.queue) == 0:
108 | self.queue.append(s)
109 | else:
110 | for i in range(0,length):
111 | if s[0] > self.queue[i][0]:
112 | self.queue.insert(i, s)
113 | break
114 | elif s[0] < self.queue[i][0] and i == length - 1:
115 | self.queue.append(s)
116 |
117 | """
118 | # *** gets the data from the broker bot priority queue ***
119 |
120 | def set_socket(self,socket = "wss://data.alpaca.markets/stream"):
121 | self.socket = socket
122 |
123 | def submit_order(self,symbol, qty, side, type, time_in_force, limit_price=None, stop_price=None,
124 | client_order_id=None, order_class=None, take_profit=None, stop_loss=None,
125 | trail_price=None, trail_percent=None):
126 |
127 | api.submit_order(symbol, qty, side, type, time_in_force, limit_price, stop_price,
128 | client_order_id, order_class, take_profit, stop_loss,
129 | trail_price, trail_percent)
130 |
131 | def on_open():
132 | print("opened-stream")
133 | auth_data = {
134 | "action": "authenticate",
135 | "data": {"key_id": config.API_KEY, "secret_key": config.SECRET_KEY}
136 | }
137 |
138 | ws.send(json.dumps(auth_data))
139 |
140 | listen_message = {"action": "listen", "data": {"streams": ["AM.TSLA"]}}
141 |
142 | ws.send(json.dumps(listen_message))
143 |
144 |
145 | def on_message(ws, message):
146 | print("received a message")
147 | print(message)
148 |
149 | def on_close(ws):
150 | print("closed connection")
151 |
152 | socket = "wss://data.alpaca.markets/stream"
153 |
154 | ws = websocket.WebSocketApp(socket, on_open=on_open, on_message=on_message, on_close=on_close)
155 | ws.run_forever()
156 | """
--------------------------------------------------------------------------------
/client/src/Components/Pages/LoginPage/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MainImage from '../images/stocks.svg';
3 | import Logo from '../images/BrokerBot.png';
4 |
5 | /*------------------ React Redux --------------------*/
6 | import { connect } from 'react-redux';
7 | import { userLogin } from '../../../redux/userSlice';
8 | /*-------------- Page Constant Values -------------- */
9 | import * as Constants from '../PageConstants'
10 |
11 | /* ---------------- Icons for Links ---------------- */
12 | import RCOS from '../images/RCOSlogo.png'
13 | import GIT from '../images/github-logo.png'
14 | import ALPACA from '../images/alpacaLogo.png'
15 |
16 | /* -------------- Components for Page -------------- */
17 | import LoginBar from "./LoginBar"
18 | import LoginRegisterContent from "./LoginRegisterContent"
19 | import RegisterBar from "./RegisterBar"
20 |
21 | const LeftScreenPosition = Constants.LoginPageScreenPosition.Left;
22 | const RightScreenPosition = Constants.LoginPageScreenPosition.Right;
23 |
24 | class LoginPage extends React.Component {
25 | constructor(props) {
26 | super(props);
27 |
28 | this.state = {
29 | currentPosition: RightScreenPosition,
30 | email: '',
31 | password: '',
32 | confirmPassword: '',
33 | LoginFunctions: { moveScreenTo: this.moveScreenTo,
34 | LearnMorePressed: this.LearnMorePressed,
35 | loginSubmitClicked: this.loginSubmitClicked,
36 | updateText: this.updateText,
37 | forgotPasswordPressed: this.forgotPasswordPressed,
38 | },
39 | RegisterFunctions: { moveScreenTo: this.moveScreenTo,
40 | LearnMorePressed: this.LearnMorePressed,
41 | registerSubmitClicked: this.registerSubmitClicked,
42 | updateText: this.updateText },
43 | }
44 | }
45 |
46 | componentDidMount(){
47 | console.log(this.props.userData);
48 | }
49 |
50 | componentDidUpdate(prevProps){
51 | console.log(this.props.userData);
52 | }
53 |
54 | /**
55 | * Redirects the user to the forgot password page
56 | */
57 | forgotPasswordPressed = () => {
58 | alert('Forgot Password was Pressed but functionality is not finished');
59 | }
60 |
61 | /**
62 | * Redirects the user to the forgot password page
63 | */
64 | LearnMorePressed = () => {
65 | alert('Forgot Password was Pressed but functionality is not finished');
66 | }
67 |
68 | /**
69 | * Updates a text value to its new inputed value for input JSX tags
70 | * @param {[event]} e the event holding the variable and new text value
71 | */
72 | updateText = e => {
73 | this.setState({
74 | [e.target.name]: e.target.value
75 | });
76 | }
77 |
78 | /**
79 | * Moves the login/register page screens view position resets the forms and forces an Update for the Component
80 | * @param {[Number]} position a value between 0 and 35 where 0 is the farthest left and 35 is the farthest right
81 | */
82 | moveScreenTo = position => {
83 | this.setState({
84 | ...this.state,
85 | currentPosition: position,
86 | email: '',
87 | password: '',
88 | confirmPassword: '',
89 |
90 | })
91 | this.forceUpdate();
92 | }
93 |
94 | /**
95 | * Sends a Request to the backend checking to see if the if the User and Password
96 | * match a known user in the database. If the User exists re-route them to their
97 | * account if they dont then keep them at he login screen
98 | * @param {[event]} e the event holding the form that was submitted and its relevant data
99 | */
100 | loginSubmitClicked = event => {
101 | /*Check to see if Valid before prevent default*/
102 | var AlertValue = `Email: ${this.state.email}\nPassword: ${this.state.password}`;
103 | alert(AlertValue);
104 |
105 | if(this.state.password === "TESTING"){
106 | this.props.dispatch(userLogin({
107 | ID: 1,
108 | email: this.state.email,
109 | }));
110 | }
111 | //console.log(event);
112 | event.preventDefault();
113 | }
114 |
115 | /**
116 | * Sends a Request to the backend checking to see if the if the User email already exists in the database.
117 | * If the Request returns saying that the user has been made let them into their new account otherwise
118 | * keep them on this page.
119 | * @param {[event]} e the event holding the form that was submitted and its relevant data
120 | */
121 | registerSubmitClicked = event => {
122 | var AlertValue = `Email: ${this.state.email}\nPassword: ${this.state.password}\nConfirm Password: ${this.state.confirmPassword}`;
123 | alert(AlertValue);
124 | event.preventDefault();
125 | }
126 |
127 | render() {
128 | return (
129 |
130 |
131 |
139 |
140 |
148 |
149 |
150 | );
151 | }
152 | }
153 |
154 | const MapStateToProps = (state) => ({
155 | userData: state.userData
156 | });
157 |
158 | export default connect(MapStateToProps)(LoginPage);
--------------------------------------------------------------------------------
/test/test_bt_analyzers.py:
--------------------------------------------------------------------------------
1 | '''
2 | List of different Analyzers:
3 | AnnualReturn
4 | Calmar
5 | DrawDown
6 | TimeDrawDown
7 | GrossLeverage
8 | PositionsValue
9 | PyFolio
10 | LogReturnsRolling
11 | PeriodStats
12 | Returns
13 | SharpeRatio
14 | SharpeRatio_A
15 | SQN
16 | TimeReturn
17 | TradeAnalyzer
18 | Transactions
19 | VWR
20 |
21 | Pyfolio Integration - had trouble getting this running locally,
22 | however it is integrated into backtrader
23 | so it might be worth looking into more
24 |
25 | Single Stock Example: https://quantopian.github.io/pyfolio/notebooks/single_stock_example/
26 | '''
27 |
28 | # Example from Backtrader Analyzers docs, but modified
29 |
30 | from __future__ import (absolute_import, division, print_function,
31 | unicode_literals)
32 |
33 |
34 | import argparse
35 | import datetime
36 | import random
37 |
38 | import backtrader as bt
39 |
40 |
41 | class St(bt.Strategy):
42 | params = (
43 | ('printout', False),
44 | ('stake', 1000),
45 | )
46 |
47 | def __init__(self):
48 | pass
49 |
50 | def start(self):
51 | if self.p.printout:
52 | txtfields = list()
53 | txtfields.append('Len')
54 | txtfields.append('Datetime')
55 | txtfields.append('Open')
56 | txtfields.append('High')
57 | txtfields.append('Low')
58 | txtfields.append('Close')
59 | txtfields.append('Volume')
60 | txtfields.append('OpenInterest')
61 | print(','.join(txtfields))
62 |
63 | def next(self):
64 | if self.p.printout:
65 | # Print only 1st data ... is just a check that things are running
66 | txtfields = list()
67 | txtfields.append('%04d' % len(self))
68 | txtfields.append(self.data.datetime.datetime(0).isoformat())
69 | txtfields.append('%.2f' % self.data0.open[0])
70 | txtfields.append('%.2f' % self.data0.high[0])
71 | txtfields.append('%.2f' % self.data0.low[0])
72 | txtfields.append('%.2f' % self.data0.close[0])
73 | txtfields.append('%.2f' % self.data0.volume[0])
74 | txtfields.append('%.2f' % self.data0.openinterest[0])
75 | print(','.join(txtfields))
76 |
77 | # Data 0
78 | for data in self.datas:
79 | toss = random.randint(1, 10)
80 | curpos = self.getposition(data)
81 | if curpos.size:
82 | if toss > 5:
83 | size = curpos.size // 2
84 | self.sell(data=data, size=size)
85 | if self.p.printout:
86 | print('SELL {} @%{}'.format(size, data.close[0]))
87 |
88 | elif toss < 5:
89 | self.buy(data=data, size=self.p.stake)
90 | if self.p.printout:
91 | print('BUY {} @%{}'.format(self.p.stake, data.close[0]))
92 |
93 |
94 | def runstrat(args=None):
95 | args = parse_args(args)
96 | print(args)
97 | cerebro = bt.Cerebro()
98 | cerebro.broker.set_cash(args.cash)
99 |
100 | dkwargs = dict()
101 | if args.fromdate:
102 | fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
103 | dkwargs['fromdate'] = fromdate
104 |
105 | if args.todate:
106 | todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
107 | dkwargs['todate'] = todate
108 |
109 | data0 = bt.feeds.YahooFinanceCSVData(dataname=args.data0, **dkwargs)
110 | cerebro.adddata(data0, name='Data0')
111 |
112 | data1 = bt.feeds.YahooFinanceCSVData(dataname=args.data1, **dkwargs)
113 | cerebro.adddata(data1, name='Data1')
114 |
115 | data2 = bt.feeds.YahooFinanceCSVData(dataname=args.data2, **dkwargs)
116 | cerebro.adddata(data2, name='Data2')
117 |
118 | cerebro.addstrategy(St, printout=args.printout)
119 | if not args.no_pyfolio:
120 | cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')
121 |
122 | results = cerebro.run()
123 | if not args.no_pyfolio:
124 | strat = results[0]
125 | pyfoliozer = strat.analyzers.getbyname('pyfolio')
126 |
127 | returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items()
128 | if args.printout:
129 | print('-- RETURNS')
130 | print(returns)
131 | print('-- POSITIONS')
132 | print(positions)
133 | print('-- TRANSACTIONS')
134 | print(transactions)
135 | print('-- GROSS LEVERAGE')
136 | print(gross_lev)
137 |
138 | import pyfolio as pf
139 | pf.create_full_tear_sheet(
140 | returns,
141 | positions=positions,
142 | transactions=transactions,
143 | gross_lev=gross_lev,
144 | live_start_date='2005-05-01',
145 | round_trips=True)
146 |
147 | if args.plot:
148 | cerebro.plot(style=args.plot_style)
149 |
150 |
151 | def parse_args(args=None):
152 |
153 | parser = argparse.ArgumentParser(
154 | formatter_class=argparse.ArgumentDefaultsHelpFormatter,
155 | description='Sample for pivot point and cross plotting')
156 |
157 | parser.add_argument('--data0', required=False,
158 | default='AAPL.csv',
159 | help='Data to be read in')
160 |
161 | parser.add_argument('--data1', required=False,
162 | default='TSLA.csv',
163 | help='Data to be read in')
164 |
165 | parser.add_argument('--data2', required=False,
166 | default='PLTR.csv',
167 | help='Data to be read in')
168 |
169 | parser.add_argument('--fromdate', required=False,
170 | default='2005-01-01',
171 | help='Starting date in YYYY-MM-DD format')
172 |
173 | parser.add_argument('--todate', required=False,
174 | default='2006-12-31',
175 | help='Ending date in YYYY-MM-DD format')
176 |
177 | parser.add_argument('--printout', required=False, action='store_true',
178 | help=('Print data lines'))
179 |
180 | parser.add_argument('--cash', required=False, action='store',
181 | type=float, default=50000,
182 | help=('Cash to start with'))
183 |
184 | parser.add_argument('--plot', required=False, action='store_true',
185 | help=('Plot the result'))
186 |
187 | parser.add_argument('--plot-style', required=False, action='store',
188 | default='bar', choices=['bar', 'candle', 'line'],
189 | help=('Plot style'))
190 |
191 | parser.add_argument('--no-pyfolio', required=False, action='store_true',
192 | help=('Do not do pyfolio things'))
193 |
194 | import sys
195 | aargs = args if args is not None else sys.argv[1:]
196 | return parser.parse_args(aargs)
197 |
198 | runstrat([])
199 |
--------------------------------------------------------------------------------
/src/Strategy.py:
--------------------------------------------------------------------------------
1 | from abc import ABC, abstractmethod # Abstract class module for python.
2 | from DataHandler import DataHandler
3 | from ExecutionHandler import ExecutionHandler
4 | from backtrader import backtrader as bt
5 | from threading import Thread
6 | from multiprocessing import Process, Pipe
7 | from ENUMS import *
8 | from Factory import *
9 | import time
10 | import queue
11 |
12 | """
13 | overview:
14 | - Strategy Class: Strategy is a base class for all strategies in BrokerBot.
15 | It takes in a DataHandler and an ExecutionHandler, which it uses to communicate
16 | with the given broker. (Broker type and trade type are set by the StrategyHandler)
17 | It then runs its strategy, which is logged continuously by Cerebro. This allows for
18 | backtesting and analysis of the stretegy after the fact.
19 |
20 | Requires: api_key,secret_key,base_url,data_url,socket, base_url, ticker,
21 | DH_API,EH_API,
22 |
23 | Modifies: DH, EH,log,
24 | Effects: DH is initialized, EH is initialized, stream is listend to, Log is initialized.
25 | Returns: none
26 | TODO:
27 | - Create more strategies.
28 | - Refine backtrading setup and functions.
29 | -
30 | """
31 | class Strategy(ABC, bt.Strategy):
32 | @abstractmethod
33 | def __init__(self, dh: DataHandler, eh: ExecutionHandler, ticker: str, strat_search_conn):
34 | self.dh = dh
35 | self.eh = eh
36 | self.ticker = ticker
37 | self.dh_queue = None
38 | self.eh_conn = None
39 | self.queue = []
40 | self.strat_search_conn = strat_search_conn
41 | self.target_stocks = []
42 | self.dh_factory = DH_factory()
43 | @abstractmethod
44 | def start(self):
45 |
46 | #instantiate connections to the data handler and execution handler.
47 | st_dh_queue = queue.LifoQueue()
48 | st_eh_conn, eh_sh_conn = Pipe()
49 | self.set_eh_dh_conns(st_dh_queue, st_eh_conn)
50 | # Set queue in DH
51 | self.DataHandler.set_sh_queue(st_dh_queue)
52 |
53 | #Thread incoming data stream from the data handler.
54 | dh_stream_thread = Thread(
55 | target=self.DataHandler.start_streaming, args=([""],))
56 | dh_listen_thread = Thread(target=self.test_dh_queue, args=())
57 | dh_stream_thread.start()
58 | dh_listen_thread.start()
59 |
60 | searcher_thread = Thread(target=self.listen_for_searcher, args=())
61 | searcher_thread.start()
62 |
63 |
64 | # Initialize any technical indicators needed from the Lib.
65 | # Start strategy, pop stock from target stocks when needed
66 |
67 | @abstractmethod
68 | def listen_for_searcher(self):
69 | while True:
70 | target_stock = self.strat_search_conn.recv()
71 | if target_stock not in self.target_stocks:
72 | self.target_stocks.append(target_stock)
73 |
74 |
75 | @abstractmethod
76 | def next(self):
77 | #Buy Conditional
78 | #random buy thing for example.
79 | if self.position.size == 0:
80 | size = int(self.broker.getcash() / 1+ self.position.size)
81 | self.buy(size=size)
82 | # Sell Conditional
83 |
84 | pass
85 |
86 | @abstractmethod
87 | def run_strat(self):
88 | #just start streaming for example.
89 | self.start()
90 | self.ticker = self.queue[0]
91 | self.pop_queue()
92 |
93 | for i in range(0, 5): # stays on one stock for 5 minutes, before switching to next stock in priority queue
94 | self.dh.start_streaming(self.ticker)
95 | previous_time = int(time.time() - 60)
96 | current_time = int(time.time())
97 | df_price = self.df.get_bars(ticker, previous_time, current_time, "1Min", "2")
98 |
99 | #df_current = self.st_dh_queue.pop()
100 | #df_previous = self.st_dh_queue.pop()
101 |
102 | #previous_close = df_previous["close"]
103 | #previous_open = df_previous["open"]
104 | previous_close = df_price.iloc[1, 4]
105 | previous_open = df_price.iloc[1, 1]
106 | previous_candle = previous_close - previous_open
107 |
108 | #current_close = df.current["close"]
109 | #current_open = df.current["open"]
110 | current_close = df_price.iloc[0, 4]
111 | current_open = df_price.iloc[0, 1]
112 | current_candle = current_close - current_open
113 |
114 | # Bullish Engulfing Buy Condition
115 | if (previous_candle < 0 and current_candle > 0) and (current_open =< previous_open and current_close > previous_close):
116 | signal = 'buy'
117 | #self.eh.start_streaming(signal)
118 | #money_alloc = self.eh.money_alloc_pre(0.0025, 15)
119 | self.eh.create_order(self.ticker, 5, signal, 'market', 'gtc')
120 |
121 | # Bearish Engulfing Sell Condition
122 | if (previous_candle > 0 and current_candle < 0) and (current_open >= previous_open and current_close < previous_close):
123 | signal = 'sell'
124 | #self.eh.start_streaming(signal)
125 | self.eh.create_order(self.ticker, 5, signal, 'market', 'gtc')
126 |
127 | #time.sleep(60)
128 | #next_time = current_time + 60
129 | #next_time = round(current_time + 60, 0)
130 | next_time = current_time + 60
131 | while time.time() < next_time:
132 | #time.sleep(1)
133 |
134 | """
135 | if time.time() = next_time:
136 | continue
137 | else:
138 | time.sleep(1)
139 | """
140 |
141 | """
142 | Overview: sets the pipe connections
143 |
144 | Requires: none
145 | Modifies: none
146 | Effects: none
147 | Returns: none
148 | Throws: RunTimeError if any of the parameters are null
149 | """
150 | def set_eh_dh_conns(self, dh_q, eh_conn):
151 | if dh_q is None or eh_conn is None:
152 | raise RuntimeError('set_eh_dh_conns called with a null') from exc
153 | self.dh_queue = dh_q
154 | self.eh_conn = eh_conn
155 | """
156 | Overview: adds a stock to the queue
157 |
158 | Requires: none
159 | Modifies: self.queue
160 | Effects: appends "stock" to self.queue
161 | Returns: none
162 | Throws: none
163 | """
164 | def add_queue(self, stock):
165 | printf("Adding {} to queue".format(stock))
166 | self.queue.append(stock)
167 | """
168 | Overview: pops a stock from the queue
169 |
170 | Requires: queue is not empty
171 | Modifies: self.queue
172 | Effects: appends "stock" to self.queue
173 | Returns: none
174 | Throws: RuntimeError if queue is empty
175 | """
176 | def pop_queue(self, pos=0):
177 | if len(self.queue) == 0:
178 | raise RuntimeError('cannot pop element from an empty queue') from exc
179 | else:
180 | printf("Popping {} from queue".format(self.queue[pos]))
181 | self.queue.pop(pos)
--------------------------------------------------------------------------------
/client/src/style.css:
--------------------------------------------------------------------------------
1 |
2 | @import url('https://fonts.googleapis.com/css2?family=Titillium+Web:wght@300&display=swap');
3 |
4 | body {
5 | margin: 0;
6 | /*font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
7 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
8 | sans-serif;*/
9 | font-family: 'Titillium Web';
10 | -webkit-font-smoothing: antialiased;
11 | -moz-osx-font-smoothing: grayscale;
12 | }
13 |
14 | input, button{
15 | font-family: 'Titillium Web';
16 | }
17 |
18 | code {
19 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
20 | monospace;
21 | }
22 | /*---------------------------------------------------------------------------------*/
23 | /* General CSS */
24 | /*---------------------------------------------------------------------------------*/
25 | .center {
26 | justify-content: center;
27 | align-content: center;
28 | align-items: center;
29 | }
30 |
31 | .center-text {
32 | text-align: center;
33 | vertical-align: middle;
34 | }
35 |
36 | .page-wrapper {
37 | position: absolute;
38 | width: 100%;
39 | height: 100%;
40 | overflow: hidden;
41 | }
42 |
43 | /*---------------------------------------------------------------------------------*/
44 | /* Login Page CSS */
45 | /*---------------------------------------------------------------------------------*/
46 |
47 | .login-page-wrapper {
48 | position: relative;
49 | width: 135%;
50 | height: 100%;
51 | display: flex;
52 | flex-flow: row;
53 | transition: all 0.3s ease;
54 | overflow-x: hidden;
55 | }
56 |
57 | .register-wrapper {
58 | width: 35%;
59 | height: 100%;
60 | background: #DE8B57;
61 | /*background: #;*/
62 | }
63 |
64 |
65 | .login-content-wrapper {
66 | width: 65%;
67 | max-width: 60%;
68 | height: 100%;
69 | background: #ffffff;
70 | display: flex;
71 | flex-flow: column;
72 | }
73 | .login-wrapper {
74 | width: 35%;
75 | height: 100%;
76 | background: #669EC5;
77 | display: flex;
78 | flex-flow: column;
79 | /*background: #0089FA;*/
80 | }
81 |
82 | .login-content-header {
83 | width: 100%;
84 | height: 100px;
85 | display: flex;
86 | padding-bottom: 12%;
87 | }
88 |
89 | .login-content-header img{
90 | height: 40px;
91 | }
92 |
93 | .login-content-img-wrapper {
94 | width: 100%;
95 | height: auto;
96 | display: flex;
97 | padding-bottom: 20px;
98 | }
99 |
100 | .login-content-img-wrapper img {
101 | height: auto; /* 45vh */
102 | }
103 | .login-content-text-wrapper {
104 | width: 80%;
105 | height: auto;
106 | display: flex;
107 | flex-flow: column;
108 | text-align: center;
109 | }
110 |
111 | .login-context-title {
112 | display: flex;
113 | /*color: #0089FA;*/
114 | color: #669EC5;
115 | font-size: 24px;
116 | padding-bottom: 5px;
117 | }
118 |
119 | .login-context-subtitle {
120 | display: flex;
121 | flex: 1;
122 |
123 | font-size: 18px;
124 | }
125 | .login-content-footer {
126 | flex: 1;
127 | width: 100%;
128 | }
129 |
130 | .login-header{
131 | width: 100%;
132 | height: 50px;
133 | display: flex;
134 | padding-bottom: 20%;
135 | justify-content: space-between;
136 | padding-top: 10px;
137 | }
138 |
139 | .login-header-btn {
140 | width: auto;
141 | height: 50px;
142 | display: flex;
143 | padding: 0px 10px 0px 10px; /*Top Right Bottom Left*/
144 | }
145 |
146 | .login-header-btn button{
147 | width: auto;
148 | height: 34px;
149 | display: flex;
150 | border-radius: 17px;
151 | border: solid 1.5px #ffffff;
152 | background: none;
153 | color: #ffffff;
154 | padding: 5px 15px 5px 15px;
155 | }
156 |
157 | .login-page-form-title-wrapper{
158 | width: 100%;
159 | height: auto;
160 | display: flex;
161 | flex-flow: column;
162 | padding: 10px 0px 20px 0px; /*Top Right Bottom Left*/
163 | }
164 |
165 | .login-page-form-title{
166 | display: flex;
167 | /*color: #0089FA;*/
168 | color: #ffffff;
169 | font-weight: bold;
170 | font-size: 24px;
171 | height: 50%;
172 | }
173 |
174 | .login-page-form-subtitle {
175 | display: flex;
176 | flex: 1;
177 | color: #ffffff;
178 | font-size: 18px;
179 | }
180 |
181 | .login-body{
182 | width: 100%;
183 | height: auto;
184 | display: flex;
185 | flex-flow: column;
186 | }
187 |
188 | .login-footer{
189 | flex: 1;
190 | }
191 |
192 | .login-page-input-wrapper {
193 | width: 100%;
194 | height: auto;
195 | display: flex;
196 | flex-flow: column;
197 | justify-content: center;
198 | align-items: center;
199 | padding-bottom: 20px;
200 |
201 | }
202 |
203 | .login-page-text-input-title {
204 | width: 66%;
205 | height: auto;
206 | color: #ffffff;
207 |
208 | }
209 |
210 | .login-page-text-input-wrapper {
211 | width: 66%;
212 | height: auto;
213 | display: flex;
214 | flex-flow: column;
215 | justify-content: center;
216 | }
217 |
218 | .login-page-text-input-wrapper input {
219 | width: 100%;
220 | height: 30px;
221 | border-radius: 5px;
222 | border: none;
223 | color: #ffffff;
224 | font-size: 16px;
225 | padding-left: 40px;
226 | }
227 |
228 | .login-page-text-input-wrapper input:focus {
229 | outline: none;
230 |
231 | }
232 |
233 | .login-submit-btn-wrapper {
234 | width: 100%;
235 | height: 80px;
236 | display: flex;
237 |
238 | }
239 |
240 | .login-submit-btn-wrapper button {
241 | width: 66%;
242 | height: 40px;
243 | border-radius: 20px;
244 | border: none;
245 | background: #DE8B57;
246 | color: #ffffff;
247 | font-size: 18px;
248 | }
249 |
250 | .register-submit-btn-wrapper {
251 | width: 100%;
252 | height: 80px;
253 | display: flex;
254 |
255 | }
256 |
257 | .register-submit-btn-wrapper button {
258 | width: 66%;
259 | height: 40px;
260 | border-radius: 20px;
261 | border: none;
262 | background: #669EC5;
263 | color: #ffffff;
264 | font-size: 18px;
265 | }
266 |
267 | .forgot-password {
268 | padding-top: 5px;
269 | padding-left: 40px ;
270 | color: #ffffff;
271 | width: 75%;
272 | text-align: right;
273 | }
274 | .forgot-password button {
275 | color: #ffffff;
276 | font-size: 14px;
277 | background: none;
278 | border: none;
279 | }
280 | .forgot-password button:hover {
281 | text-decoration: underline;
282 | }
283 |
284 | .testing {
285 | padding-left: 10px;
286 | position: absolute;
287 | padding-top: 6px;
288 | pointer-events: none;
289 | }
290 |
291 | .icons-wrapper {
292 | width: 100%;
293 | height: 50px;
294 | display: flex;
295 | padding-top: 40px;
296 | }
297 | .icons-container {
298 | width: 66%;
299 | height: auto;
300 | display: flex;
301 | flex-flow: row;
302 | justify-content: center;
303 |
304 | }
305 | .icons-container img {
306 | width: 60px;
307 | }
--------------------------------------------------------------------------------
/src/BrokerBot.py:
--------------------------------------------------------------------------------
1 | # import DataHandler
2 | # import ExecutionHandler
3 | from PortfolioManager import *
4 | # from StrategyHandler import StrategyHandler
5 | # from DataHandler import AlpacaDataHandler
6 | from threading import Thread
7 | from queue import PriorityQueue
8 | from enum import Enum
9 | from ENUMS import *
10 | import time
11 | from multiprocessing import Process, Pipe
12 | import os
13 | from StrategyHandler import StrategyHandler
14 |
15 | """
16 | overview: (description of the class)
17 |
18 | TODO: (to do of the class as a whole more long term things)
19 | """
20 |
21 |
22 | class BrokerBot:
23 |
24 | """
25 | Abstract Function:
26 |
27 | Representation Invariant:
28 |
29 | Simple explanation:(if nesscary)
30 | """
31 |
32 | # ====================Creators====================
33 | '''
34 | Overview: constructs a BrokerBot instance.
35 |
36 | Requires: api_key: the api key for the api this instance uses.
37 | secret_key: the sectet key for the api this instance constructs
38 | base_url: the base url of the api.
39 | socket: the socket url for communication with the api
40 | Modifies: headers,market_open,api_key,secret_key,base_url,socket,
41 | account_url,order_url,strategy_handler_processes.
42 | Effects: headers is inilized and contains api_key, and secret_key
43 | market_open = true
44 | api_key stores the api key
45 | base_url stores the api's base url
46 | socket contian the api's socket port.
47 | account url stores a formatted url for the account(alpaca only)
48 | order url stores a formatted url for the orders(alpaca only)
49 | strategy_handler_processes is inilized to an empty list
50 | Returns: volume of the stock that was passed in
51 | Throws: RunTimeException if any parameter is null.
52 | TODO: How many tickers are we limited to per API request? Answer: 200
53 | sockets limited to 30
54 | '''
55 |
56 | #def __init__(self, api_key, secret_key, base_url, socket, search_conn):
57 | def __init__(self, api_key, secret_key, base_url, socket, search_conn):
58 | if api_key is None or secret_key is None or base_url is None or socket is None:
59 | raise RuntimeError('BrokerBot initalized with a null') from exc
60 |
61 | self.market_open = True
62 | self.api_key = api_key
63 | self.secret_key = secret_key
64 | self.base_url = base_url
65 | self.socket = socket
66 |
67 | self.headers = {}
68 | self.account_url = ""
69 | self.order_url = ""
70 |
71 | self.pm = PortfolioManager(api_key, secret_key, base_url, socket)
72 | self.input = self.pm.input
73 | self.set_vars()
74 |
75 | #self.searcher_conn = search_conn
76 | self.sh_pipe_conns = []
77 | self.sh_instances = []
78 | self.sh_processes = []
79 | # ====================Observers====================
80 |
81 | '''
82 | Overview: Updates handlers based on portfoliomanager values
83 | Requires: none
84 | Modifies: none
85 | Effects: none
86 | Returns: none
87 | Throws: none
88 | TODO: Add checking for if strategy or risk change and update
89 | handlers accordingly
90 | '''
91 | def update(self):
92 | if(self.pm.input != self.input):
93 | self.input = self.pm.input
94 | pass
95 | '''
96 | Overview: returns the account
97 |
98 | Requires: none
99 | Modifies: none
100 | Effects: none
101 | Returns: a joson containing the contents of the account.
102 | Throws: ???
103 | TODO: figure out what this Might throw
104 | '''
105 |
106 | def get_account(self):
107 | r = requests.get(self.account_url['alpaca'], headers['alpaca'])
108 | return json.loads(r.content)
109 | # ====================Producers====================
110 | # ====================Mutators====================
111 | '''
112 | Overview: sets market_open to false
113 |
114 | Requires: none
115 | Modifies: market_open
116 | Effects: market_open set to false
117 | Returns: none
118 | Throws: none
119 | TODO:
120 | '''
121 |
122 | def set_market_close(self):
123 | self.market_open = False
124 | '''
125 | Overview: sets variables based on portfolio manager inputs
126 |
127 | Requires: none
128 | Modifies: self.headers, self.account_url, self.order_url
129 | Effects: All three values updated based on PortfolioManager
130 | values of them
131 | Returns: none
132 | Throws: none
133 | TODO:
134 | '''
135 | def set_vars(self):
136 | self.headers = self.pm.headers
137 | self.account_url = self.pm.account_url
138 | self.order_url = self.pm.order_url
139 | '''
140 | Overview: infinite loop to update portfoliomanager values
141 |
142 | Requires: none
143 | Modifies: self.input
144 | Effects: self.input updates based on user input
145 | Returns: none
146 | Throws: none
147 | TODO:
148 | '''
149 | def get_commands(self):
150 | return {
151 | "changestrat": self.pm.change_strat,
152 | "changerisk": self.pm.change_risk,
153 | "getstrat": self.pm.get_strat,
154 | "totalreturn": self.pm.get_total_return,
155 | "todaysreturn": self.pm.get_todays_return,
156 | "deposit": self.pm.deposit,
157 | "withdraw": self.pm.withdraw,
158 | "balance": self.pm.get_balance,
159 | "liquidbalance": self.pm.get_current_liquid_cash,
160 | "getcurrstrat": self.pm.get_current_strat,
161 | "orderhistory": self.pm.order_history,
162 | "checkpositions": self.pm.check_positions
163 | }
164 |
165 |
166 | def listen_for_searcher(self):
167 | while True:
168 | target_stocks = self.searcher_conn.recv()
169 | for sh_conn in self.sh_pipe_conns:
170 | sh_conn.send(target_stocks)
171 |
172 |
173 | '''
174 | Overview: Start SH on own process via multiprocessing
175 |
176 | Requires: none
177 | Modifies: none
178 | Effects: none
179 | Returns: none
180 | Throws: RuntimeError on execution
181 |
182 | TODO: Specfification & figure out strategy logic/pipeline
183 | '''
184 |
185 | def run(self):
186 | # strategies = ["ST1", "ST2", "ST3"]
187 | strategies = ["ST1"]
188 |
189 | for strat in strategies:
190 | bb_sh_conn, sh_bb_conn = Pipe()
191 | self.sh_pipe_conns.append(bb_sh_conn)
192 | self.sh_instances.append(StrategyHandler(
193 | self.api_key, self.secret_key, self.base_url, self.socket, strat, self.input))
194 |
195 | for sh in self.sh_instances:
196 | self.sh_processes.append(Process(target=sh.run, args=()))
197 |
198 | for proc in self.sh_processes:
199 | proc.start()
200 |
201 | while True:
202 | if not self.market_open:
203 | for proc in sh_processes:
204 | proc.join()
205 | else:
206 | commands = self.get_commands()
207 | cmd_list = list(commands)
208 |
209 | user = input()
210 | if(user == 'q'):
211 | break
212 | if(user in cmd_list):
213 | commands.get(user)()
214 | pass
215 | else:
216 | print(f"Invalid Input - command options are {cmd_list}")
217 | self.update()
218 |
--------------------------------------------------------------------------------
/src/StrategyHandler.py:
--------------------------------------------------------------------------------
1 | from DataHandler import AlpacaDataHandler
2 | from ExecutionHandler import AlpacaExecutionHandler
3 | from threading import Thread
4 | import queue
5 | import copy
6 | from multiprocessing import Process, Pipe
7 |
8 |
9 | """
10 | Overview:
11 | class that handles running our different strategies, using DH & EH's that are
12 | customized to what ever api constructed with
13 | TODO:
14 | construct proper DH, and EH for whatever APi selected
15 | select from avaliable DH's ex binance DH, alpaca DH, .. ect.
16 | decide what fucntion/ stratey its running
17 | ex. longterm, short term, medium term... ect.
18 | instanciate member variables
19 | log(Data structure communicating with the DH)
20 | DH
21 | EH
22 | """
23 |
24 |
25 | class StrategyHandler:
26 | # ====================Creators====================
27 | """
28 | Overview: High-Risk Strategy will be implemented here. Below is just an example to give an idea.
29 |
30 | Requires: api_key,secret_key,base_url,data_url,socket, base_url, ticker,
31 | DH_API,EH_API,
32 |
33 | Modifies: DH, EH,log,
34 | Effects: DH is initialized, EH is initialized, stream is listend to, Log is initialized.
35 | Returns: none
36 | """
37 |
38 | def __init__(self, api_key, secret_key, base_url, socket, strategy, strat_input):
39 | self.api_key = api_key
40 | self.secret_key = secret_key
41 | self.base_url = base_url
42 | self.socket = socket
43 | self.strategy_category = strategy
44 | self.strategy_input = strat_input
45 |
46 | self.bb_conn = bb_conn
47 | self.dh_queue = None
48 | self.eh_conn = None
49 | self.target_stocks = []
50 | # ====================Observers====================
51 | """
52 | Overview: tests & prints if we revieved a message from the DH
53 |
54 | Requires: none
55 | Modifies: none
56 | Effects: none
57 | Returns: none
58 | Throws: none
59 | """
60 |
61 | def test_dh_queue(self):
62 | while True:
63 | data_frame = self.dh_queue.get()
64 | print(f"SH RECV: {data_frame}")
65 | # ====================Producers====================
66 | # ====================Mutators====================
67 |
68 | """
69 | Example Trading strategy
70 | def shortTerm(ticker):
71 | listen to stream of ticker\
72 | start adding to the message log
73 | while(true):
74 | add to the message log
75 | stat calculations for the strategy
76 | calculate trade signal
77 | EH.enterTrade(ticker, signal)
78 | while(order has not been fullfilled){
79 | // check if order fullfuill has been recieved
80 | // add to log
81 | //continue contiuous calculations
82 | }
83 | begin dynamic stop loss caluclations, by continuing the technical indicator calculations
84 | stoploss();
85 | """
86 |
87 | """
88 | Overview: High-Risk Strategy will be implemented here. Below is just an example to give an idea.
89 |
90 | Requires: bars is non-null
91 | Modifies: none
92 | Effects: none
93 | Returns: sell-trade decision if previous close is less than fibonacci value AND current open is less than fibonacci value, else returns none representing no decision is made
94 | """
95 | def HighRisk(symbol, bars):
96 | fib_values = self.fibonacci(symbol, bars)
97 | # elif current_price == fib_values[]
98 | for fib_val in fib_values:
99 | if previous_close < fib_val and current_open < fib_val:
100 | # by returning 'SHORT', this will tell execution handler to make a short trade
101 | decision = ["SHORT", None, None]
102 | return decision
103 | return None
104 |
105 | """
106 | Overview: Medium-Risk Strategy will be implemented here. Below is just an example to give an idea.
107 |
108 | Requires: bars is non-null
109 | Modifies: none
110 | Effects: none
111 | Returns: buy-trade decision, take-profit, and stop-loss if current volume is 1.5x greater than previous volume.
112 | """
113 | def MidRisk(bars):
114 | pass
115 |
116 | """
117 | Overview: Low-Risk Strategy will be implemented here. Below is just an example to give an idea.
118 |
119 | Requires: bars is non-null
120 | Modifies: none
121 | Effects: none
122 | Returns: buy-trade decision, take-profit, and stop-loss if current volume is 1.5x greater than previous volume.
123 | """
124 | def LowRisk(symbol, bars):
125 | vol_1 = self.volume(bars[0])
126 | # I think each bar should be tuple with open, close, low, high, current prices & volume
127 | vol_2 = self.volume(bars[1])
128 |
129 | if vol_1 > (vol_2 * 1.5):
130 | fib_values = self.fibonacci(symbol, bars)
131 | TP = fib_values[3] # take profit is the 3rd retracement
132 | SL = fib_values[2] # stop loss is the 2nd retracement
133 | decision = ["BUY", TP, SL]
134 | return decision
135 |
136 | """
137 | Overview: calculates the fibonacci values of 23.6%, 38.2%, 50%, 61.8%, and 78.6%
138 |
139 | Requires: bars is non-null
140 | Modifies: none
141 | Effects: none
142 | Returns: fibonacci values
143 | Throws: RunTimeException if bars or symbol is null
144 | """
145 | def fibonacci(symbol, bars):
146 | if symbol is None or bars is None:
147 | raise RuntimeError('fibonacci called with a null') from exc
148 | # first data-value in the bars array (most recent bar to current bar)
149 | first = bars[0]
150 | # last data-value in the bars array (most farthest bar to current bar)
151 | second = bars[len(candles) - 1]
152 | retracements = [0.236, 0.382, 0.5, 0.618, 0.786]
153 | fib_values = [(second - ((second - first) * retracement))
154 | for retracement in retracements]
155 | return fib_values
156 | """
157 | Overview: sets the pipe connections
158 |
159 | Requires: none
160 | Modifies: none
161 | Effects: none
162 | Returns: none
163 | Throws: RunTimeError if any of the parameters are null
164 | """
165 |
166 | def set_eh_dh_conns(self, dh_q, eh_conn):
167 | if dh_q is None or eh_conn is None:
168 | raise RuntimeError('set_eh_dh_conns called with a null') from exc
169 | self.dh_queue = dh_q
170 | self.eh_conn = eh_conn
171 |
172 |
173 | """
174 | Overview: Create DH + EH process and pipe connection points for both
175 |
176 | Requires: none
177 | Modifies: DataHandler,
178 | Effects: DataHandeler starts a stream on ticket "TSLA"
179 | Returns: none
180 | Throws: none
181 | TODO: figure out best way to pass these pipe connections points to DH and EH
182 | TODO: add logic in SH and EH for using pipe to communication with SH
183 | """
184 |
185 | def run(self):
186 | # TODO: add logic for starting proper strategies from a given categroy
187 | sample_strategies = ["strat1", "strat2", "strat3"]
188 | searcher_strat_conns = []
189 | # add switch statement for instantiating correct strategy class based on strat ^
190 | for start in sample_strategies:
191 | dh_params = [self.api_key, self.secret_key, self.base_url, self.socket]
192 | st_dh = self.dh_factory.construct_dh(self.strategy_input[2], dh_params)
193 | eh_params = [self.api_key, self.secret_key, self.base_url]
194 | st_eh = self.eh_factory.construct_eh(self.strategy_input[3], eh_params)
195 | search_strat, strat_search = Pipe()
196 |
197 | # replace with real class
198 | self.strategies.append(Strategy(st_dh, st_eh, "None", strat_search))
199 | searcher_strat_conns.append(search_strat)
200 |
201 | self.searcher = Searcher(self.api_key, self.secret_key, self.base_url, self.socket, searcher_strat_conns)
202 |
203 | searcher_proc = Process(target=self.searcher.search, args=())
204 | searcher_proc.start()
205 |
206 | strategy_processes = []
207 | for strat in self.strategies:
208 | strat_proc = Process(target=strat.start, args=())
209 | strat_proc.start()
210 | strategy_processes.append(strat_proc)
211 |
212 |
213 | dh_listen_thread = Thread(target=self.test_dh_queue, args=())
214 |
215 | dh_stream_thread.start()
216 | dh_listen_thread.start()
217 |
218 |
219 |
220 | prev_target_stocks = copy.deepcopy(self.target_stocks)
221 | # TODO: create logic for determining channel name based on strat
222 | channel_name = "T"
223 | # TODO: refine this once searcher routine more explictily defined
224 | while True:
225 | # if BB updates us with new target stocks from searcher, clean up DH process and start new one with updated target stocks
226 | new_target_stocks = self.bb_conn.recv()
227 | target_tickers = set(prev_target_stocks + new_target_stocks)
228 | self.DataHandler.listen(target_tickers, channel_name)
229 |
230 |
231 |
--------------------------------------------------------------------------------
/src/DataHandler.py:
--------------------------------------------------------------------------------
1 | import websocket
2 | import requests
3 | import json # For web connectivity
4 | from abc import ABC, abstractmethod # Abstract class module for python.
5 | import alpaca_trade_api as tradeapi
6 | from dataclasses import dataclass # Python structs module.
7 | import pandas as pd # For data storage and analysis.
8 | import ast, datetime # For on_message data handling
9 | """
10 | overview:
11 | - DataHandler Class: DataHandler is a class that takes in data from a given brokerage API,
12 | and sends it through to the StrategyHandler in a format that it can natively understand.
13 | in short, it is responsible for data transfer and formatting.
14 |
15 | - Standard Data Format (BBFrame): BBFrame is the standard data format used in the BrokerBot
16 | project. it consists of a Pandas dataframe in the following configuration:
17 | ================================================================
18 | | | Time | Open | High | Low | Close | Volume | <- Labelled columns
19 | | 01 | string | float | float | float | float | float |
20 | | 02 | string | float | float | float | float | float |
21 | ================================================================
22 | ^^
23 | auto-numbered rows (see pandas Dataframe for more info.)
24 |
25 | TODO:
26 | - More concretely define abstract methods for base class.
27 | - Support more API subclasses.
28 | -
29 | """
30 |
31 |
32 | class DataHandler(ABC):
33 | # ==================== Creators ====================
34 |
35 | # ==================== Observers ===================
36 | """
37 | requires: nothing.
38 | modifies: nothing.
39 | effects: nothing.
40 | returns: an account object for the API.
41 | """
42 | @abstractmethod
43 | def get_account(self):
44 | pass
45 | """
46 | requires: nothing.
47 | modifies: nothing.
48 | effects: nothing.
49 | returns: the socket object for the API.
50 | """
51 | @abstractmethod
52 | def get_socket(self):
53 | pass
54 |
55 | # ==================== Producers ===================
56 |
57 | """
58 | requires: ticker for given stock, start time for bar data, end time of bar data, and length of bar.
59 | modifies: nothing.
60 | effects: nothing.
61 | returns: a Pandas Dataframe containing the bars data in BrokerBot Standard Format (BBFrame).
62 | """
63 | @abstractmethod
64 | def get_bars(self, tickers, bar_timeframe, num_of_bars):
65 | pass
66 |
67 |
68 | # ==================== Mutators ====================
69 | """
70 | requires: The new account to set this one to.
71 | modifies: The current account object, replacing it with the argument account.
72 | effects: nothing.
73 | returns: nothing.
74 | """
75 | @abstractmethod
76 | def set_account(self, account):
77 | pass
78 |
79 |
80 | # ===================== Misc =======================
81 |
82 | @abstractmethod
83 | def on_open(self):
84 | pass
85 |
86 | @abstractmethod
87 | def on_message(self):
88 | pass
89 |
90 | @abstractmethod
91 | def on_close(self):
92 | pass
93 |
94 | @abstractmethod
95 | def on_error(self):
96 | pass
97 |
98 | @abstractmethod
99 | def listen(self):
100 | pass
101 |
102 | @abstractmethod
103 | def unlisten(self):
104 | pass
105 |
106 |
107 | class AlpacaDataHandler(DataHandler):
108 | def __init__(self,
109 | api_key,
110 | secret_key,
111 | base_url,
112 | socket="ws://data.alpaca.markets/stream"):
113 | self.api_key = api_key
114 | self.secret_key = secret_key
115 | self.headers = {
116 | "APCA-API-KEY-ID": api_key,
117 | "APCA-API-SECRET-KEY": secret_key
118 | }
119 | self.base_url = base_url
120 | self.account_url = "{}/v2/account".format(self.base_url)
121 | self.order_url = "{}/v2/orders".format(self.base_url)
122 | self.api = tradeapi.REST(self.headers["APCA-API-KEY-ID"],
123 | self.headers["APCA-API-SECRET-KEY"], base_url)
124 | self.api_account = self.api.get_account()
125 | self.ws = None
126 | self.socket = socket
127 | self.pending_tickers = []
128 | self.sh_queue = None
129 |
130 | def set_sh_queue(self, q):
131 | self.sh_queue = q
132 |
133 | def get_account(self):
134 | return self.api_account
135 |
136 | def set_account(self, account):
137 | self.acount = account
138 |
139 | def get_socket(self):
140 | return self.ws
141 |
142 |
143 | """
144 | requires: Strings for the ticker, start and end time in RFC-3339 format(e.g. 2021-03-11T00:00:00-05:00),
145 | timeframe (currently only '1Day', '1Hour', and '1Min'),
146 | and bar_limit, which limits the number of bars returned within that timeframe.
147 | modifies: nothing.
148 | effects: nothing.
149 | returns: A Pandas Dataframe containing the bars data.
150 | """
151 | def get_bars(self, ticker: str, start_time: str, end_time: str, bar_timeframe: str, bar_limit: str):
152 | url ='https://data.alpaca.markets/v2/stocks'+'/'+ticker+'/bars?adjustment=raw'+'&start='+start_time+'&end='+end_time+'&limit='+bar_limit+'&page_token='+'&timeframe='+bar_timeframe
153 | r = requests.get(url, headers=self.headers)
154 | df = pd.read_json(json.dumps(r.json()['bars']))
155 | df['oi'] = -1
156 | df.columns = ['Date', 'Open', 'High', 'Low', 'Close', 'Volume', 'Open Interest']
157 | print(df)
158 | return df
159 |
160 | # Socket Functions
161 |
162 | def on_open(self, ws):
163 | print("on open")
164 |
165 | # function called whenever a websocket is opened, authenticates with alpaca
166 |
167 | auth_data = {
168 | "action": "authenticate",
169 | "data": {
170 | "key_id": self.api_key,
171 | "secret_key": self.secret_key
172 | }
173 | }
174 | ws.send(json.dumps(auth_data))
175 | print("sent auth")
176 | listen_message = {
177 | "action": "listen",
178 | "data": {
179 | "streams": [f"AM.{self.pending_tickers.pop()}", "AM.GME"]
180 | }
181 | }
182 | # check pending tickers, sne initial listen message, wait for new tickers,
183 | ws.send(json.dumps(listen_message))
184 |
185 | """
186 | requires: Reference to the WebSocketApp and the message that was receieved.
187 | modifies: nothing.
188 | effects: Sends a DataFrame to SH via the pipe.
189 | returns: nothing.
190 | """
191 |
192 | def on_message(self, ws, message):
193 | print("received a message")
194 | print(message)
195 | # convert message to dictionary
196 | message = ast.literal_eval(message)
197 | # message is not an authorization or listening message, so it must be a minute bars message
198 | if message["stream"] != "authorization" and message["stream"] != "listening":
199 | timestamp = datetime.datetime.fromtimestamp(message["data"]["e"] / 1000)
200 | timestamp = timestamp.isoformat("T")
201 | op = message["data"]["o"]
202 | high = message["data"]["h"]
203 | low = message["data"]["l"]
204 | cl = message["data"]["c"]
205 | vol = message["data"]["v"]
206 | data = [[timestamp, op, high, low, cl, vol]]
207 | df = pd.DataFrame(data, columns=["Time", "Open", "High", "Low", "Close", "Volume"])
208 | self.sh_queue.put(df)
209 |
210 | def on_close(self, ws):
211 | print("closed connection")
212 |
213 | def on_error(self, ws, error):
214 | print(error)
215 |
216 | def start_streaming(self, tickers):
217 | self.pending_tickers += tickers
218 | print(self.socket)
219 | self.ws = websocket.WebSocketApp(
220 | self.socket,
221 | on_message=lambda ws, msg: self.on_message(self.ws, msg),
222 | on_close=lambda ws: self.on_close(self.ws),
223 | on_open=lambda ws: self.on_open(self.ws),
224 | on_error=lambda ws, error: self.on_error(self.ws, error))
225 |
226 | self.ws.run_forever()
227 | print("HELLO")
228 |
229 | def listen(self, tickers, channel_name):
230 | #
231 | # function that sends a listen message to alpaca for the streams inputed.
232 | #
233 | for x in range(len(tickers)):
234 | tickers[x] = channel_name + "." + tickers[x]
235 | print(tickers)
236 | listen_message = {"action": "listen", "data": {"streams": tickers}}
237 | self.ws.send(json.dumps(listen_message))
238 | #self.stream_conn.run(quote_callback, tickers)
239 |
240 | def unlisten(self, ticker, channel_name):
241 | #
242 | # function that unlistens for the streams inputed.
243 | # might need error checking if a stream that is not currently being listened to is asked to be unlistened.
244 | #
245 | for x in range(ticker):
246 | ticker[x] = channel_name + ticker[x]
247 | unlisten_message = {"action": "unlisten", "data": {"streams": ticker}}
248 | self.ws.send(json.dumps(unlisten_message))
249 |
--------------------------------------------------------------------------------
/client/src/Components/Pages/images/stocks.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Factory.py:
--------------------------------------------------------------------------------
1 | from DataHandler import DataHandler
2 | from ExecutionHandler import ExecutionHandler
3 | from Strategy import Strategy
4 |
5 | class DH_factory:
6 |
7 | # Overview: Class that creates and returns any DH object
8 |
9 | """
10 | Overview: Constructs and returns proper DH based on passed in enum
11 |
12 | Params: ENUM for DH_api
13 | params is list containg DH parameters
14 | Requires: none
15 | Modifies: none
16 | Effects: none
17 | Returns: Valid DH object based on parameter
18 | Throws: ValueError if parameter is invalid
19 | """
20 | def construct_dh(self, enum, params):
21 | if enum == 1:
22 | return self.dh_alpaca(params)
23 | elif enum == 2:
24 | return self.dh_binance(params)
25 | elif enum == 3:
26 | return self.dh_polygon(params)
27 | elif enum == 4:
28 | return self.dh_ibkr(params)
29 | elif enum == 5:
30 | return self.dh_alpha(params)
31 | else:
32 | raise ValueError("Invalid ENUM")
33 |
34 | """
35 | Overview: Constructs and returns alpaca DH based on params
36 |
37 | Params: params is a list of parameters for the alpaca api DH
38 | Requires: none
39 | Modifies: none
40 | Effects: none
41 | Returns: Valid alpaca DH object
42 | Throws: none
43 | """
44 | def dh_alpaca(self, params):
45 | dh = AlpacaDataHandler(params[0], params[1], params[2], params[3])
46 | return dh
47 |
48 | """
49 | Overview: Constructs and returns binance DH based on params
50 |
51 | Params: params is a list of parameters for the binance api DH
52 | Requires: none
53 | Modifies: none
54 | Effects: none
55 | Returns: Valid binance DH object
56 | Throws: none
57 | """
58 | def dh_binance(self, params):
59 | pass
60 |
61 | """
62 | Overview: Constructs and returns polygon DH based on params
63 |
64 | Params: params is a list of parameters for the polygon api DH
65 | Requires: none
66 | Modifies: none
67 | Effects: none
68 | Returns: Valid polygon DH object
69 | Throws: none
70 | """
71 | def dh_polygon(self, params):
72 | pass
73 |
74 | """
75 | Overview: Constructs and returns ibkr DH based on params
76 |
77 | Params: params is a list of parameters for the ibkr api DH
78 | Requires: none
79 | Modifies: none
80 | Effects: none
81 | Returns: Valid ibkr DH object
82 | Throws: none
83 | """
84 | def dh_ibkr(self, params):
85 | pass
86 |
87 | """
88 | Overview: Constructs and returns alpha DH based on params
89 |
90 | Params: params is a list of parameters for the alpha api DH
91 | Requires: none
92 | Modifies: none
93 | Effects: none
94 | Returns: Valid alpha DH object
95 | Throws: none
96 | """
97 | def dh_alpha(self, params):
98 | pass
99 |
100 | class EH_factory:
101 |
102 | # Overview: Class that creates and returns any EH object
103 |
104 | """
105 | Overview: Constructs and returns proper DH based on passed in enum
106 |
107 | Params: ENUM for EH_api
108 | params is list containg EH parameters
109 | Requires: none
110 | Modifies: none
111 | Effects: none
112 | Returns: Valid EH object based on parameter
113 | Throws: ValueError if parameter is invalid
114 | """
115 | def construct_eh(self, enum, params):
116 | if enum == 1:
117 | return self.eh_alpaca(params)
118 | elif enum == 2:
119 | return self.eh_binance(params)
120 | elif enum == 3:
121 | return self.eh_ibkr(params)
122 | elif enum == 4:
123 | return self.eh_alpha(params)
124 | else:
125 | raise ValueError("Invalid ENUM")
126 |
127 | """
128 | Overview: Constructs and returns alpaca EH based on params
129 |
130 | Params: params is a list of parameters for the alpaca api DH
131 | Requires: none
132 | Modifies: none
133 | Effects: none
134 | Returns: Valid alpaca DH object
135 | Throws: none
136 | """
137 | def eh_alpaca(self, params):
138 | eh = AlpacaExecutionHandler(params[0], params[1], params[2])
139 | return eh
140 |
141 | """
142 | Overview: Constructs and returns binance EH based on params
143 |
144 | Params: params is a list of parameters for the binance api EH
145 | Requires: none
146 | Modifies: none
147 | Effects: none
148 | Returns: Valid binance EH object
149 | Throws: none
150 | """
151 | def eh_binance(self, params):
152 | pass
153 |
154 |
155 | """
156 | Overview: Constructs and returns ibkr EH based on params
157 |
158 | Params: params is a list of parameters for the ibkr api EH
159 | Requires: none
160 | Modifies: none
161 | Effects: none
162 | Returns: Valid ibkr EH object
163 | Throws: none
164 | """
165 | def eh_ibkr(self, params):
166 | pass
167 |
168 | """
169 | Overview: Constructs and returns alpha EH based on params
170 |
171 | Params: params is a list of parameters for the alpha api EH
172 | Requires: none
173 | Modifies: none
174 | Effects: none
175 | Returns: Valid alpha EH object
176 | Throws: none
177 | """
178 | def eh_alpha(self, params):
179 | pass
180 |
181 | class Strategy_factory:
182 |
183 | # Overview: Class that creates and returns any Strategy object
184 |
185 | """
186 | Overview: Constructs and returns proper startegy based on passed in enum
187 |
188 | Params: ENUM for DH_api
189 | params is list containg DH parameters
190 | Requires: none
191 | Modifies: none
192 | Effects: none
193 | Returns: Valid DH object based on parameter
194 | Throws: ValueError if parameter is invalid
195 | """
196 | def construct_strat(self, enum, params):
197 | if enum == 1:
198 | return self.short_low_risk(params)
199 | elif enum == 2:
200 | return self.medium_low_risk(params)
201 | elif enum == 3:
202 | return self.long_low_risk(params)
203 | elif enum == 4:
204 | return self.medium_low_risk(params)
205 | elif enum == 5:
206 | return self.medium_medium_risk(params)
207 | elif enum == 6:
208 | return self.long_medium_risk(params)
209 | elif enum == 7:
210 | return self.short_high_risk(params)
211 | elif enum == 8:
212 | return self.medium_high_risk(params)
213 | elif enum == 9:
214 | return self.long_high_risk(params)
215 | else:
216 | raise ValueError("Invalid ENUM")
217 |
218 | """
219 | Overview: Constructs and returns short_low_risk strat based on parameters
220 |
221 | Params: params is a list of parameters for the strategy
222 | Requires: none
223 | Modifies: none
224 | Effects: none
225 | Returns: Valid short_low_risk strategy object
226 | Throws: none
227 | """
228 | def short_low_risk(self, params):
229 | strat = Strategy(0,params[0], params[1], params[2], params[3])
230 | return strat
231 |
232 | """
233 | Overview: Constructs and returns medium_low_risk strat based on parameters
234 |
235 | Params: params is a list of parameters for the strategy
236 | Requires: none
237 | Modifies: none
238 | Effects: none
239 | Returns: Valid medium_low_risk strategy object
240 | Throws: none
241 | """
242 | def medium_low_risk(self, params):
243 | strat = Strategy(1,params[0], params[1], params[2], params[3])
244 | return strat
245 |
246 | """
247 | Overview: Constructs and returns long_low_risk strat based on parameters
248 |
249 | Params: params is a list of parameters for the strategy
250 | Requires: none
251 | Modifies: none
252 | Effects: none
253 | Returns: Valid long_low_risk strategy object
254 | Throws: none
255 | """
256 | def long_low_risk(self, params):
257 | strat = Strategy(2,params[0], params[1], params[2], params[3])
258 | return strat
259 |
260 | """
261 | Overview: Constructs and returns short_medium_risk strat based on parameters
262 |
263 | Params: params is a list of parameters for the strategy
264 | Requires: none
265 | Modifies: none
266 | Effects: none
267 | Returns: Valid short_medium_risk strategy object
268 | Throws: none
269 | """
270 | def short_medium_risk(self, params):
271 | strat = Strategy(3,params[0], params[1], params[2], params[3])
272 | return strat
273 |
274 | """
275 | Overview: Constructs and returns medium_medium_risk strat based on parameters
276 |
277 | Params: params is a list of parameters for the strategy
278 | Requires: none
279 | Modifies: none
280 | Effects: none
281 | Returns: Valid medium_medium_risk strategy object
282 | Throws: none
283 | """
284 | def medium_medium_risk(self, params):
285 | strat = Strategy(4,params[0], params[1], params[2], params[3])
286 | return strat
287 |
288 | """
289 | Overview: Constructs and returns long_medium_risk strat based on parameters
290 |
291 | Params: params is a list of parameters for the strategy
292 | Requires: none
293 | Modifies: none
294 | Effects: none
295 | Returns: Valid long_medium_risk strategy object
296 | Throws: none
297 | """
298 | def long_medium_risk(self, params):
299 | strat = Strategy(5,params[0], params[1], params[2], params[3])
300 | return strat
301 |
302 | """
303 | Overview: Constructs and returns short_high_risk strat based on parameters
304 |
305 | Params: params is a list of parameters for the strategy
306 | Requires: none
307 | Modifies: none
308 | Effects: none
309 | Returns: Valid short_high_risk strategy object
310 | Throws: none
311 | """
312 | def short_high_risk(self, params):
313 | strat = Strategy(6,params[0], params[1], params[2], params[3])
314 | return strat
315 |
316 | """
317 | Overview: Constructs and returns medium_high_risk strat based on parameters
318 |
319 | Params: params is a list of parameters for the strategy
320 | Requires: none
321 | Modifies: none
322 | Effects: none
323 | Returns: Valid medium_high_risk strategy object
324 | Throws: none
325 | """
326 | def medium_high_risk(self, params):
327 | strat = Strategy(7,params[0], params[1], params[2], params[3])
328 | return strat
329 |
330 | """
331 | Overview: Constructs and returns long_high_risk strat based on parameters
332 |
333 | Params: params is a list of parameters for the strategy
334 | Requires: none
335 | Modifies: none
336 | Effects: none
337 | Returns: Valid long_high_risk strategy object
338 | Throws: none
339 | """
340 | def long_high_risk(self, params):
341 | strat = Strategy(8,params[0], params[1], params[2], params[3])
342 | return strat
--------------------------------------------------------------------------------
/src/PortfolioManager.py:
--------------------------------------------------------------------------------
1 | #Imports
2 | # from StrategyHandler import StrategyHandler
3 | # from DataHandler import AlpacaDataHandler
4 | # from threading import Thread
5 | # from queue import PriorityQueue
6 | from ENUMS import *
7 | import alpaca_trade_api as tradeapi
8 | import time
9 | import matplotlib.pyplot as plt
10 | import sys
11 | import itertools
12 | import requests
13 | import json
14 | import pandas as pd
15 |
16 | class PortfolioManager:
17 |
18 | # Overview: PortfolioManager acts as a basic UI for BrokerBot.
19 | # Allows for a user to input commands and outputs basic
20 | # information about BrokerBot operations
21 | # To Do: Create Constructor. I/O for api's, strategies, stop/loss, cap
22 | # Be able to interface with other classes
23 |
24 | # ====================Creators====================
25 | def __init__(self, api_key, secret_key, base_url, socket):
26 | self.api_key = api_key
27 | self.secret_key = secret_key
28 | self.base_url = base_url
29 | self.socket = socket
30 |
31 | self.headers = {}
32 | self.api = {}
33 | self.account_url = ""
34 | self.order_url = ""
35 | self.input = []
36 |
37 | self.balance = 0
38 | self.liquid = 0
39 | self.assets = 0
40 | self.active = 0
41 | self.watch_list = ""
42 | self.combos = self.calc_combos()
43 | self.strategies = []
44 |
45 | # inputs in the form [strategy, risk, DH_api, EH_api, stop_loss]
46 | self.initial_setup()
47 |
48 | # ====================Observers====================
49 | # Overview: Function to calculate all combinations of strategies
50 | #
51 | # Requires: none
52 | # Modifies: none
53 | # Effects: none
54 | # Returns: combinations calculated
55 | # Throws: none
56 | def calc_combos(self):
57 | strats = [1,2,3,4,5,6,7,8,9]
58 | risks = [1,2,3,4,5,6,7,8,9]
59 | dh_api = [1,2,3,4,5]
60 | eh_api = [1,2,3,4]
61 | stop_loss = [1,2]
62 | combined = [strats,risks,dh_api,eh_api,stop_loss]
63 | combos = list(itertools.product(*combined))
64 | return combos
65 |
66 | # Overview: Function to return self.input values
67 | #
68 | # Requires: none
69 | # Modifies: none
70 | # Effects: none
71 | # Returns: self.input
72 | # Throws: none
73 | def get_input(self):
74 | return self.input
75 |
76 | # Overview: Function to return all combinations of strategies
77 | #
78 | # Requires: none
79 | # Modifies: none
80 | # Effects: none
81 | # Returns: self.combos
82 | # Throws: none
83 | def strat_combs(self):
84 | #print(self.combos)
85 | return self.combos
86 |
87 |
88 | # Overview: Function that returns the user's total all time return
89 | #
90 | # Params: self (PortfolioManager Object)
91 | # Requires: None
92 | # Modifies: None
93 | # Effects: None
94 | # Returns: Returns the total all time return of the user's account
95 | def get_total_return(self):
96 | pass
97 |
98 | # Overview: Function that returns the user's return for the current day
99 | #
100 | # Params: self (PortfolioManager Object)
101 | # Requires: None
102 | # Modifies: None
103 | # Effects: None
104 | # Returns: Returns the return of the current day for this user's account
105 | def get_todays_return(self):
106 | pass
107 |
108 | # Overview: Function that returns current running strategy/strategies
109 | #
110 | # Params: self (PortfolioManager Object)
111 | # Requires: None
112 | # Modifies: None
113 | # Effects: None
114 | # Returns: Returns a list of length 0...n depending on the number of
115 | # strategies the user is currently using. This list contains
116 | # the current strategies.
117 | def get_strat(self):
118 | print(self.strategies)
119 | return self.strategies
120 |
121 | # Overview: Prints current strategy
122 | #
123 | # Requires: none
124 | # Modifies: none
125 | # Effects: none
126 | # Returns: self.input
127 | # Throws: none
128 | # TODO:
129 | def get_current_strat(self):
130 | print(self.input)
131 | return self.input
132 |
133 | # Overview: Function that computes a portfolio diversity score based
134 | # on the current holdings the user has.
135 | #
136 | # Params: self (PortfolioManager Object)
137 | # Requires: The user has at least one holding
138 | # Modifies: None
139 | # Effects: None
140 | # Returns: Returns a number between 0 and 100, 0 meaning the user only
141 | # has one stock and 100 meaning the user's portfolio is very
142 | # diverse. The score is computed based on strategies being
143 | # used the number and diversity of tickers being traded.
144 | def get_diversity_score(self):
145 | pass
146 |
147 | # Overview: Function that returns the user's remaining total of
148 | # liquid cash left in their account.
149 | #
150 | # Params: self (PortfolioManager Object)
151 | # Requires: None
152 | # Modifies: None
153 | # Effects: None
154 | # Returns: A float representing the liquid cash in the user's account.
155 | # TODO: Add functionality for other apis
156 | def get_current_liquid_cash(self):
157 | print("Liquid Balance: {:.2f}".format(self.liquid))
158 | return self.liquid
159 |
160 | # Overview: Function that returns the user's current total value they have
161 | # vested in stocks.
162 | #
163 | # Params: self (PortfolioManager Object)
164 | # Requires: None
165 | # Modifies: None
166 | # Effects: None
167 | # Returns: A float representing the value of all of the user's stocks.
168 | def get_current_total_stock_value(self):
169 | pass
170 |
171 | # Overview: Function that returns the user's current total value they have
172 | # vested in crypto.
173 | #
174 | # Params: self (PortfolioManager Object)
175 | # Requires: None
176 | # Modifies: None
177 | # Effects: None
178 | # Returns: A float representing the value of all of the user's crypto holdings.
179 | def get_current_total_crypto_value(self):
180 | pass
181 |
182 | # Overview: Function that displays the entire balance of a user
183 | #
184 | # Params: self (PortfolioManager Object)
185 | # Requires: None
186 | # Modifies: None
187 | # Effects: None
188 | # Returns: Returns a number reflecting entire balance of a user
189 | # TODO: Add functionality for other API's
190 | def get_balance(self):
191 | pass
192 |
193 | # Overview: Displays entire order history of a user's account
194 | #
195 | # Params: self (PortfolioManager Object)
196 | # Requires: None
197 | # Modifies: None
198 | # Effects: None
199 | # Returns: Creates chart displaying all orders from a user account
200 | def order_history(self):
201 | orders = self.api.list_orders(status='closed')
202 | columns = ["Symbol","Type","Date","Shares","Price per Share",
203 | "Notional","Amount","Status"]
204 | table = {i: [] for i in columns}
205 | for i in orders:
206 | table['Symbol'].append(i.symbol)
207 | table['Type'].append(i.side)
208 | table['Date'].append(str(i.created_at)[:10])
209 | table['Shares'].append(i.qty)
210 | table['Price per Share'].append(i.filled_avg_price)
211 | table['Notional'].append(i.notional)
212 | if(i.status == 'filled'):
213 | table['Amount'].append('{:.2f}'.format(float(i.filled_qty)*
214 | float(i.filled_avg_price)))
215 | else:
216 | table['Amount'].append(0)
217 | table['Status'].append(i.status)
218 | df = pd.DataFrame(table)
219 | print(df)
220 |
221 | # Overview: Displays all positions of a user's account
222 | #
223 | # Params: self (PortfolioManager Object)
224 | # Requires: None
225 | # Modifies: None
226 | # Effects: None
227 | # Returns: Creates chart displaying all positions from a user account
228 | def check_positions(self):
229 | pos = self.api.list_positions()
230 | col = ["Symbol","Avg Buy","Profit","Qty","Daily Change","Current Price",
231 | "Last Day Price","Market Value"]
232 | table = {i: [] for i in col}
233 | for i in pos:
234 | table['Symbol'].append(i.symbol)
235 | table['Avg Buy'].append("{:.2f}".format(float(i.avg_entry_price)))
236 | table['Qty'].append(i.qty)
237 | table['Daily Change'].append("{:.5f}".format(
238 | float(i.change_today)))
239 | table['Current Price'].append(i.current_price)
240 | table['Last Day Price'].append(i.lastday_price)
241 | table['Market Value'].append(i.market_value)
242 | table['Profit'].append("{:.2f}".format(float(i.qty)*
243 | (float(i.current_price)-float(i.avg_entry_price))))
244 | df = pd.DataFrame(table)
245 | print(df)
246 | # ====================Producers====================
247 | # ====================Mutators====================
248 | # Overview: Adds current strategy to self.strategies
249 | #
250 | # Requires: none
251 | # Modifies: self.strategies
252 | # Effects: self.strategies.append(self.input)
253 | # Returns: self.strategies
254 | # Throws: none
255 | # TODO:
256 | def add_strat(self):
257 | self.strategies.append(self.input.copy())
258 | return self.strategies
259 |
260 | # Overview: Sets the initial values of input
261 | #
262 | # Requires: none
263 | # Modifies: self.input
264 | # Effects: val of self.input changes based on user input
265 | # Returns: none
266 | # Throws: none
267 | # TODO: Add functionality for other api's
268 | def initial_setup(self):
269 | print("Enter the specified Strategy: (1-9)")
270 | strat = int(input())
271 | name = Strategies(strat).name
272 | print("Selected Strategy: {}".format(name))
273 | print("Enter the specified Risk: (1-9)")
274 | risk = int(input())
275 | name = Risk(risk).name
276 | print("Selected Risk: {}".format(name))
277 | print("Enter the specified DH_API: (1-5)")
278 | dh_api = int(input())
279 | name = DH_API(dh_api).name
280 | print("Selected DH_API: {}".format(name))
281 | print("Enter the specified EH_API: (1-4)")
282 | eh_api = int(input())
283 | name = EH_API(eh_api).name
284 | print("Selected EH_API: {}".format(name))
285 | print("Enter the specified Stop Loss: (1-2)")
286 | stop_loss = int(input())
287 | name = Stop_loss(stop_loss).name
288 | print("Selected Stop Loss: {}".format(name))
289 | self.input = [strat, risk, dh_api, eh_api, stop_loss]
290 | self.add_strat()
291 | if(self.input[3] == 1):
292 | self.set_alpaca()
293 |
294 | # Overview: Sets Alpaca API values if alpaca api is selected for EH
295 | #
296 | # Requires: none
297 | # Modifies: self.headers, self.account_url, self.order_url
298 | # Effects: self.headers takes the api key/secret key and formats to alpaca format
299 | # self.account_url takes base url and formats to alpaca format
300 | # self.order_url takes base url and formats to alpaca format
301 | # Returns: none
302 | # Throws: none
303 | def set_alpaca(self):
304 | self.headers = {
305 | "APCA-API-KEY-ID": self.api_key,
306 | "APCA-API-SECRET-KEY": self.secret_key
307 | }
308 | self.api = tradeapi.REST(self.headers["APCA-API-KEY-ID"],
309 | self.headers["APCA-API-SECRET-KEY"], self.base_url)
310 | self.account_url = "{}/v2/account".format(self.base_url)
311 | self.order_url = "{}/v2/orders".format(self.base_url)
312 | self.liquid = float(self.api.get_account().cash)
313 | self.balance = float(self.api.get_account().portfolio_value)
314 | self.assets = self.balance - self.liquid
315 |
316 | # Overview: Changes the strategy based on user input
317 | #
318 | # Requires: none
319 | # Modifies: self.input[0]
320 | # Effects: self.input[0] changes based on user input
321 | # Returns: none
322 | # Throws: none
323 | # TODO: Add functionality to stop one strategy and start new one
324 | def change_strat(self):
325 | print("Enter new Strategy to use: (1-9)")
326 | strat = int(input())
327 | name = Strategies(strat).name
328 | print("Selected Strategy: {}".format(name))
329 | self.input[0] = strat
330 | self.add_strat()
331 |
332 | # Overview: Changes the risk based on user input
333 | #
334 | # Requires: none
335 | # Modifies: self.input[1]
336 | # Effects: self.input[1] changes based on user input
337 | # Returns: none
338 | # Throws: none
339 | # TODO: Add functionality to change risk value used
340 | def change_risk(self):
341 | print("Enter new Risk to use: (1-9)")
342 | risk = int(input())
343 | name = Risk(risk).name
344 | print("Selected Risk: {}".format(name))
345 | self.input[1] = risk
346 | self.add_strat()
347 |
348 | # Overview: Adds a stock ticker or strategy to the user's watch list.
349 | #
350 | # Requires: none
351 | # Modifies: self.watch_list
352 | # Effects: adds a ticker to self.watch_list
353 | # Returns: none
354 | # Throws: none
355 | def add_to_watch_list(self, strat, ticker):
356 | pass
357 |
358 | # Overview: Removes a stock ticker or strategy from the user's watch list.
359 | #
360 | # Requires: none
361 | # Modifies: self.watch_list
362 | # Effects: removes a ticker from self.watch_list
363 | # Returns: none
364 | # Throws: none
365 | def remove_from_watch_list(self, strat, ticker):
366 | pass
367 |
368 |
369 | # Overview: Function that withdraws money from balance
370 | #
371 | # Params: None
372 | # Requires: None
373 | # Modifies: this.balance
374 | # Effects: this.balance -= minus
375 | # Returns: Returns a number reflecting new balance
376 | # Throws: Assertion if withdraw is greater than balance
377 | # TODO: Add functionality to actually withdraw to various API Accounts
378 | def withdraw(self):
379 | print("Current Liquid Balance: {:0.2f}".format(self.liquid))
380 | print("Enter a value to withdraw")
381 | user = int(input())
382 | while(user > self.liquid):
383 | print("Withdraw exceeds balance")
384 | user = int(input())
385 | self.liquid -= user
386 | print("New Liquid Balance: {:0.2f}".format(self.liquid))
387 | return self.liquid
388 |
389 | # Overview: Function that adds money to balance
390 | #
391 | # Params: self (PortfolioManager Object)
392 | # Requires: None
393 | # Modifies: this.balance
394 | # Effects: this.balance += plus
395 | # Returns: Returns a number reflecting entire balance of a user
396 | # TODO: Add functionality to actually deposit to various API Accounts
397 | def deposit(self):
398 | print("Current Balance: {:0.2f}".format(self.balance))
399 | print("Enter a value to deposit")
400 | user = int(input())
401 | self.liquid += user
402 | self.balance += user
403 | print("New Balance: {:0.2f}".format(self.balance))
404 | return self.liquid
405 |
406 |
--------------------------------------------------------------------------------
/src/ExecutionHandler.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import json
3 | import alpaca_trade_api as tradeapi
4 | from abc import ABC, abstractmethod
5 |
6 |
7 | class ExecutionHandler(ABC):
8 |
9 | """
10 | Overview: Abstract Class for the ExecutionHandler. Outlines essential
11 | function signatures.
12 | """
13 | # ====================Creators====================
14 |
15 | @abstractmethod
16 | def __init__(self):
17 | pass
18 | # ====================Observers====================
19 |
20 | @abstractmethod
21 | def get_account(self):
22 | pass
23 |
24 | @abstractmethod
25 | def get_orders(self):
26 | pass
27 |
28 | @abstractmethod
29 | def check_limitations(self):
30 | pass
31 |
32 | def cancel_order(self):
33 | pass
34 |
35 | def cancel_all_orders(self):
36 | pass
37 |
38 | def get_open_pos(self):
39 | pass
40 |
41 | @abstractmethod
42 | def close_pos(self):
43 | pass
44 |
45 | @abstractmethod
46 | def close_all_pos(self):
47 | pass
48 |
49 | @abstractmethod
50 | def bracket_order(self):
51 | pass
52 |
53 | @abstractmethod
54 | def dynamic_stop_loss(self):
55 | pass
56 |
57 | @abstractmethod
58 | def fill_order(self):
59 | pass
60 |
61 | # ====================Mutators====================
62 |
63 | @abstractmethod
64 | def create_order(self):
65 | pass
66 |
67 | def create_order_notional(self):
68 | pass
69 |
70 | @abstractmethod
71 | def trade_signal(self):
72 | pass
73 |
74 | @abstractmethod
75 | def money_alloc_pre(self):
76 | pass
77 |
78 | @abstractmethod
79 | def money_alloc_post(self):
80 | pass
81 |
82 | @abstractmethod
83 | def replace_order(self):
84 | pass
85 |
86 |
87 | class AlpacaExecutionHandler(ExecutionHandler):
88 |
89 | """
90 | Overview: ExecutionHandler class: ExecutionHandler is a class that takes in
91 | data from given brokerage API, receives signals from the
92 | StrategyHandler, and puts in buy/sell order requests through
93 | said API. StrategyHandler determines which stock to buy.
94 | ExecutionHandler decides how many shares to buy based on
95 | information passed in from the StrategyHandler. The symbol,
96 | quantity or notional, and current OrderID are stored as
97 | local variables.
98 | """
99 |
100 | # ====================Creators====================
101 |
102 | def __init__(self, API_key_id, API_secret_key, base_url):
103 | self.base_url = base_url
104 | self.account_url = "{}/v2/account".format(self.base_url)
105 | self.order_url = "{}/v2/orders".format(self.base_url)
106 | self.headers = {"APCA-API-KEY-ID": API_key_id,
107 | "APCA-API-SECRET-KEY": API_secret_key}
108 | self.api = tradeapi.REST(self.headers["APCA-API-KEY-ID"],
109 | self.headers["APCA-API-SECRET-KEY"], base_url)
110 | self.api_account = self.api.get_account()
111 | self.account_cash = self.api_account.cash
112 | self.position_url = "{}/v2/positions".format(self.base_url)
113 | self.symbol = None
114 | self.qty = 0
115 | self.orderID = None
116 | # ====================Observers====================
117 |
118 | # overview: returns the alpaca api account
119 | #
120 | # modifies: nothing.
121 | # effects: nothing.
122 | # returns: alpaca api account object
123 | def get_account(self):
124 | return self.api_account
125 |
126 | # overview: checks if alpaca account has ability to trade or not
127 | #
128 | # modifies: none
129 | # effects: none
130 | # returns: True if account is able to trade. False otherwise
131 | def check_limitations(self):
132 | if(self.api_account.trading_blocked or
133 | self.api_account.account_blocked or float(self.account_cash) <= 0):
134 | return False
135 | return True
136 |
137 | # overview: cancels an order based on specified orderid
138 | #
139 | # params: orderid = alpaca order id for a particular trade
140 | # modifies: none
141 | # effects: none
142 | # returns: json containing order details
143 | def cancel_order(self, orderid):
144 | delete_url = self.order_url + '/' + orderid
145 | r = requests.delete(delete_url, headers=self.headers)
146 | return json.loads(r.content)
147 |
148 | # overview: cancels all existing orders
149 | #
150 | # modifies: none
151 | # effects: none
152 | # returns: json containing order details
153 | def cancel_all_orders(self):
154 | r = requests.delete(order_url, headers=self.headers)
155 | return json.loads(r.content)
156 |
157 | # overview: get details of an open position based on specified symbol
158 | #
159 | # params: symbol = symbol to check positions
160 | # modifies: none
161 | # effects: none
162 | # returns: json containing order details
163 | def get_open_pos(self, symbol):
164 | open_pos_url = self.position_url + '/' + symbol
165 | r = requests.get(open_pos_url, headers=self.headers)
166 | return json.loads(r.content)
167 |
168 | # overview: closes a position based on specified symbol
169 | #
170 | # params: symbol = symbol to close
171 | # modifies: none
172 | # effects: none
173 | # returns: json containing order details
174 | def close_pos(self, symbol):
175 | close_pos_url = self.position_url + '/' + symbol
176 | r = requests.delete(close_pos_url, headers=self.headers)
177 | return json.loads(r.content)
178 |
179 | # overview: closes all positions
180 | #
181 | # params: none
182 | # modifies: none
183 | # effects: none
184 | # returns: json containing order details
185 | def close_all_pos(self):
186 | r = requests.delete(self.position_url, headers=self.headers)
187 | return json.loads(r.content)
188 |
189 | # overview: creates a quantity bracket order based on specified parameters
190 | #
191 | # params: symbol = stock symbol to identify asset to trade
192 | # qty = number of shares to trade
193 | # side = buy or sell
194 | # loss = specified stop loss price
195 | # profit = specified profit price
196 | # limit = specified limit price. None default
197 | # modifies: none
198 | # effects: none
199 | # returns: json containing order details
200 | def bracket_order(self, symbol, qty, side, loss, limit=None):
201 | stop_loss = {
202 | "stop_price": loss,
203 | "limit_price": limit
204 | }
205 | r = self.create_order(symbol, qty, side, "market", "gtc",
206 | "oto", None, stop_loss)
207 | return r
208 |
209 | # overview: dynamically adjusts the stop loss value of an order based on
210 | # incoming data. stop value should never decrease
211 | #
212 | # params: stop = stop loss number
213 | # limit = stop limit number. Default none
214 | # modifies: none
215 | # effects: none
216 | # returns: json containing order details
217 | def dynamic_stop_loss(self, stop, limit=None):
218 | self.get_orders()
219 | r = self.replace_order(self.orderID, self.qty, "gtc", limit, stop)
220 | return r
221 |
222 | # overview: loops until order is filled
223 | #
224 | # params: orderID is the order ID which identifies which order to wait
225 | # for fill
226 | # modifies: none
227 | # effects: none
228 | # returns: none
229 | def fill_order(self, orderID):
230 | order = self.order_url + '/' + orderID
231 | r = requests.get(order, headers=self.headers)
232 | status = json.loads(r.content)['status']
233 | while(status != "filled"):
234 | r = requests.get(order, headers=self.headers)
235 | status = json.loads(r.content)['status']
236 | return
237 |
238 | # ====================Producers====================
239 |
240 | # ====================Mutators====================
241 |
242 | # overview: Places a quantity order based on parameters and updates
243 | # variables accordingly.
244 | #
245 | # params: symbol = stock symbol to identify asset to trade
246 | # qty = number of shares to trade
247 | # side = buy or sell
248 | # type = market, limit,stop, stop_limit, trailing_stop
249 | # time_in_force = day, gtc, opg, cls, ioc, fok
250 | # limit_price = limit price. required if type is limit, stop_limit
251 | # stop_price = stop price. required of type is stop, stop_limit
252 | # trail_price = trail price. required if type is trailing_stop
253 | # trail_percent = trail percent. required if type is trailing_stop
254 | # extended_hours = Boolean. If true, order will be eligible to
255 | # execute in premarket/afterhours. Only works with
256 | # type limit and time_in_force day
257 | # client_order_id = unique identifier for order. Automatically
258 | # generated if not sent
259 | # take_profit = dictionary containing limit_price
260 | # stop_loss = dictionary containing stop_price and limit_price
261 | # modifies: self.orderID, self.symbol, self.qty
262 | # effects: self.orderID = alpaca generated order id
263 | # self.symbol = symbol
264 | # self.qty = qty
265 | # returns: json containing order details
266 | def create_order(self, symbol, qty, side, type, time_in_force,
267 | order_class=None, take_profit=None, stop_loss=None,
268 | limit_price=None, stop_price=None,
269 | trail_price=None, trail_percent=None,
270 | extended_hours=False, client_order_id=None):
271 | data = {
272 | "symbol": symbol,
273 | "qty": qty,
274 | "side": side,
275 | "type": type,
276 | "time_in_force": time_in_force,
277 | "limit_price": limit_price,
278 | "stop_price": stop_price,
279 | "trail_price": trail_price,
280 | "trail_percent": trail_percent,
281 | "extended_hours": extended_hours,
282 | "client_order_id": client_order_id,
283 | "order_class": order_class,
284 | "take_profit": take_profit,
285 | "stop_loss": stop_loss
286 | }
287 |
288 | r = requests.post(self.order_url, json=data, headers=self.headers)
289 | self.orderID = json.loads(r.content)['id']
290 | self.symbol = symbol
291 | self.qty = qty
292 | self.fill_order(self.orderID)
293 | return json.loads(r.content)
294 |
295 | # overview: Places a notional order based on parameters and updates
296 | # variables accordingly.
297 | #
298 | # params: symbol = stock symbol to identify asset to trade
299 | # notional = dollar amount to trade.
300 | # side = buy or sell
301 | # limit_price = limit price. required if type is limit, stop_limit
302 | # stop_price = stop price. required of type is stop or stop_limit
303 | # trail_price = trail price. required if type is trailing_stop
304 | # trail_percent = trail percent. required if type is trailing_stop
305 | # extended_hours = Boolean. If true, order will be eligible to
306 | # execute in premarket/afterhours. Only works with
307 | # type limit and time_in_force day
308 | # client_order_id = unique identifier for order. Automatically
309 | # generated if not sent
310 | # take_profit = dictionary containing limit_price
311 | # stop_loss = dictionary containing stop_price and limit_price
312 | # modifies: self.orderID, self.symbol, self.qty
313 | # effects: self.orderID = alpaca generated order id
314 | # self.symbol = symbol
315 | # self.qty = qty
316 | # returns: json containing order details
317 | def create_order_notional(self, symbol, notional, side, order_class=None,
318 | take_profit=None, stop_loss=None,
319 | limit_price=None, stop_price=None,
320 | trail_price=None, trail_percent=None,
321 | extended_hours=False, client_order_id=None):
322 | data = {
323 | "symbol": symbol,
324 | "notional": notional,
325 | "side": side,
326 | "type": 'market',
327 | "time_in_force": 'day',
328 | "limit_price": limit_price,
329 | "stop_price": stop_price,
330 | "trail_price": trail_price,
331 | "trail_percent": trail_percent,
332 | "extended_hours": extended_hours,
333 | "client_order_id": client_order_id,
334 | "order_class": order_class,
335 | "take_profit": take_profit,
336 | "stop_loss": stop_loss
337 | }
338 |
339 | r = requests.post(self.order_url, json=data, headers=self.headers)
340 | self.orderID = json.loads(r.content)['id']
341 | self.symbol = symbol
342 | self.fill_order(self.orderID)
343 | return json.loads(r.content)
344 |
345 | # overview: gets all active orders
346 | #
347 | # modifies: self.orderID
348 | # effects: self.orderID = most recent orderID if available
349 | # returns: json containing all orders
350 | def get_orders(self):
351 | r = requests.get(self.order_url, headers=self.headers)
352 | length = len(json.loads(r.content))
353 | self.orderID = json.loads(r.content)[length-1]['id']
354 | self.qty = json.loads(r.content)[length-1]['qty']
355 | return json.loads(r.content)
356 |
357 | # overview:
358 | #
359 | # modifies:
360 | # effects:
361 | # returns:
362 | def trade_signal(self):
363 | """
364 | Takes data from stategy handler and buys/sells stocks based on strategy
365 | """
366 | return
367 |
368 | # overview: determines number of shares to buy based on explicitly
369 | # specified cap value and risk value
370 | #
371 | # params: cap = percent of account balance to risk
372 | # risk = stop loss amount. Ex. If share is 100 and stop_loss = 95,
373 | # risk is 100-95=5
374 | # modifies: self.qty
375 | # effects: self.qty = number of shares calculated based on capped formula
376 | # returns: self.qty
377 | def money_alloc_pre(self, cap, risk):
378 | shares = (self.account.cash*cap)/risk
379 | shares = int(shares//1)
380 | self.qty = shares
381 | return self.qty
382 |
383 | # overview: determines number of shares to buy based on win rate and
384 | # win/loss ratio determined from historical data
385 | #
386 | # params: win = win rate.
387 | # ratio = win/loss rate
388 | # modifies: self.qty
389 | # effects: self.qty = number of shares calculated based on Kelly Criterion
390 | # returns: self.qty
391 | def money_alloc_post(self, win, ratio):
392 | k = win - ((1-win)/ratio)
393 | shares = self.account.cash*k
394 | shares = int(shares//1)
395 | self.qty = shares
396 | return self.qty
397 |
398 | # overview: replaces an order based on specified parameters
399 | #
400 | # params: orderid = alpaca order id for a particular trade
401 | # qty = new updated quantity if changed
402 | # time_in_force = day, gtc, opg, cls, ioc, fok
403 | # limit_price = limit price. required if type is limit, stop_limit
404 | # stop_price = stop price. required of type is stop or stop_limit
405 | # trail = new value of trail_price or trail_percent value
406 | # client_order_id = unique identifier for order. Automatically
407 | # generated if not sent
408 | # modifies: self.qty
409 | # effects: self.qty = updated quantity if changed
410 | # returns: json containing new order details
411 | def replace_order(self, orderid, qty, time_in_force, limit_price=None,
412 | stop_price=None, trail=None, client_order_id=None):
413 | replace_url = self.order_url + '/' + orderid
414 | data = {
415 | "qty": qty,
416 | "time_in_force": time_in_force,
417 | "limit_price": limit_price,
418 | "stop_price": stop_price,
419 | "trail": trail,
420 | "client_order_id": client_order_id,
421 | }
422 | r = requests.patch(replace_url, json=data, headers=self.headers)
423 | return json.loads(r.content)
424 |
--------------------------------------------------------------------------------
/test/data/AAPL.csv:
--------------------------------------------------------------------------------
1 | Date,Open,High,Low,Close,Adj Close,Volume
2 | 2020-04-20,69.487503,70.419998,69.212502,69.232498,68.699974,130015200
3 | 2020-04-21,69.070000,69.312500,66.357498,67.092499,66.576424,180991600
4 | 2020-04-22,68.402496,69.474998,68.050003,69.025002,68.494064,117057200
5 | 2020-04-23,68.967499,70.437500,68.717499,68.757500,68.228622,124814400
6 | 2020-04-24,69.300003,70.752502,69.250000,70.742500,70.198349,126508800
7 | 2020-04-27,70.449997,71.135002,69.987503,70.792503,70.247971,117087600
8 | 2020-04-28,71.269997,71.457497,69.550003,69.644997,69.109299,112004800
9 | 2020-04-29,71.182503,72.417503,70.972504,71.932503,71.379196,137280800
10 | 2020-04-30,72.489998,73.632500,72.087502,73.449997,72.885025,183064000
11 | 2020-05-01,71.562500,74.750000,71.462502,72.267502,71.711632,240616800
12 | 2020-05-04,72.292503,73.422501,71.580002,73.290001,72.726265,133568000
13 | 2020-05-05,73.764999,75.250000,73.614998,74.389999,73.817802,147751200
14 | 2020-05-06,75.114998,75.809998,74.717499,75.157501,74.579391,142333600
15 | 2020-05-07,75.805000,76.292503,75.492500,75.934998,75.350914,115215200
16 | 2020-05-08,76.410004,77.587502,76.072502,77.532501,77.144394,134048000
17 | 2020-05-11,77.025002,79.262497,76.809998,78.752502,78.358284,145946400
18 | 2020-05-12,79.457497,79.922501,77.727501,77.852501,77.462791,162301200
19 | 2020-05-13,78.037498,78.987503,75.802498,76.912498,76.527496,200622400
20 | 2020-05-14,76.127502,77.447502,75.382500,77.385002,76.997635,158929200
21 | 2020-05-15,75.087502,76.974998,75.052498,76.927498,76.542412,166348400
22 | 2020-05-18,78.292503,79.125000,77.580002,78.739998,78.345848,135372400
23 | 2020-05-19,78.757500,79.629997,78.252502,78.285004,77.893127,101729600
24 | 2020-05-20,79.169998,79.879997,79.129997,79.807503,79.408005,111504800
25 | 2020-05-21,79.665001,80.222504,78.967499,79.212502,78.815979,102688800
26 | 2020-05-22,78.942497,79.807503,78.837502,79.722504,79.323433,81803200
27 | 2020-05-26,80.875000,81.059998,79.125000,79.182503,78.786133,125522000
28 | 2020-05-27,79.035004,79.677498,78.272499,79.527496,79.129402,112945200
29 | 2020-05-28,79.192497,80.860001,78.907501,79.562500,79.164230,133560800
30 | 2020-05-29,79.812500,80.287498,79.117500,79.485001,79.087120,153598000
31 | 2020-06-01,79.437500,80.587502,79.302498,80.462502,80.059723,80791200
32 | 2020-06-02,80.187500,80.860001,79.732498,80.834999,80.430351,87642800
33 | 2020-06-03,81.165001,81.550003,80.574997,81.279999,80.873131,104491200
34 | 2020-06-04,81.097504,81.404999,80.195000,80.580002,80.176636,87560400
35 | 2020-06-05,80.837502,82.937500,80.807503,82.875000,82.460152,137250400
36 | 2020-06-08,82.562500,83.400002,81.830002,83.364998,82.947685,95654400
37 | 2020-06-09,83.035004,86.402496,83.002502,85.997498,85.567009,147712400
38 | 2020-06-10,86.974998,88.692497,86.522499,88.209999,87.768440,166651600
39 | 2020-06-11,87.327499,87.764999,83.870003,83.974998,83.554642,201662400
40 | 2020-06-12,86.180000,86.949997,83.555000,84.699997,84.276009,200146000
41 | 2020-06-15,83.312500,86.419998,83.144997,85.747498,85.318260,138808800
42 | 2020-06-16,87.864998,88.300003,86.180000,88.019997,87.579391,165428800
43 | 2020-06-17,88.787498,88.849998,87.772499,87.897499,87.457504,114406400
44 | 2020-06-18,87.852501,88.362503,87.305000,87.932503,87.492332,96820400
45 | 2020-06-19,88.660004,89.139999,86.287498,87.430000,86.992348,264476000
46 | 2020-06-22,87.834999,89.864998,87.787498,89.717499,89.268394,135445200
47 | 2020-06-23,91.000000,93.095001,90.567497,91.632500,91.173805,212155600
48 | 2020-06-24,91.250000,92.197502,89.629997,90.014999,89.564407,192623200
49 | 2020-06-25,90.175003,91.250000,89.392502,91.209999,90.753426,137522400
50 | 2020-06-26,91.102501,91.330002,88.254997,88.407501,87.964951,205256800
51 | 2020-06-29,88.312500,90.542503,87.820000,90.445000,89.992256,130646000
52 | 2020-06-30,90.019997,91.495003,90.000000,91.199997,90.743469,140223200
53 | 2020-07-01,91.279999,91.839996,90.977501,91.027496,90.571838,110737200
54 | 2020-07-02,91.962502,92.617500,90.910004,91.027496,90.571838,114041600
55 | 2020-07-06,92.500000,93.945000,92.467499,93.462502,92.994652,118655600
56 | 2020-07-07,93.852501,94.654999,93.057503,93.172501,92.706093,112424400
57 | 2020-07-08,94.180000,95.375000,94.089996,95.342499,94.865234,117092000
58 | 2020-07-09,96.262497,96.317497,94.672501,95.752502,95.273193,125642800
59 | 2020-07-10,95.334999,95.980003,94.705002,95.919998,95.439842,90257200
60 | 2020-07-13,97.264999,99.955002,95.257500,95.477501,94.999557,191649200
61 | 2020-07-14,94.839996,97.254997,93.877502,97.057503,96.571655,170989200
62 | 2020-07-15,98.989998,99.247498,96.489998,97.724998,97.235809,153198000
63 | 2020-07-16,96.562500,97.404999,95.904999,96.522499,96.039330,110577600
64 | 2020-07-17,96.987503,97.147499,95.839996,96.327499,95.845299,92186800
65 | 2020-07-20,96.417503,98.500000,96.062500,98.357498,97.865143,90318000
66 | 2020-07-21,99.172501,99.250000,96.742500,97.000000,96.514442,103646000
67 | 2020-07-22,96.692497,97.974998,96.602501,97.272499,96.785568,89001600
68 | 2020-07-23,96.997498,97.077499,92.010002,92.845001,92.380234,197004400
69 | 2020-07-24,90.987503,92.970001,89.144997,92.614998,92.151382,185438800
70 | 2020-07-27,93.709999,94.904999,93.480003,94.809998,94.335396,121214000
71 | 2020-07-28,94.367500,94.550003,93.247498,93.252502,92.785706,103625600
72 | 2020-07-29,93.750000,95.230003,93.712502,95.040001,94.564247,90329200
73 | 2020-07-30,94.187500,96.297501,93.767502,96.190002,95.708496,158130000
74 | 2020-07-31,102.885002,106.415001,100.824997,106.260002,105.728088,374336800
75 | 2020-08-03,108.199997,111.637497,107.892502,108.937500,108.392181,308151200
76 | 2020-08-04,109.132500,110.790001,108.387497,109.665001,109.116043,173071600
77 | 2020-08-05,109.377502,110.392502,108.897499,110.062500,109.511551,121992000
78 | 2020-08-06,110.404999,114.412498,109.797501,113.902496,113.332329,202428800
79 | 2020-08-07,113.205002,113.675003,110.292503,111.112503,110.755638,198045600
80 | 2020-08-10,112.599998,113.775002,110.000000,112.727501,112.365448,212403600
81 | 2020-08-11,111.970001,112.482498,109.107498,109.375000,109.023705,187902400
82 | 2020-08-12,110.497498,113.275002,110.297501,113.010002,112.647041,165944800
83 | 2020-08-13,114.430000,116.042503,113.927498,115.010002,114.640610,210082000
84 | 2020-08-14,114.830002,115.000000,113.044998,114.907501,114.538445,165565200
85 | 2020-08-17,116.062500,116.087502,113.962502,114.607498,114.239403,119561600
86 | 2020-08-18,114.352501,116.000000,114.007500,115.562500,115.191338,105633600
87 | 2020-08-19,115.982498,117.162498,115.610001,115.707497,115.335869,145538000
88 | 2020-08-20,115.750000,118.392502,115.732498,118.275002,117.895126,126907200
89 | 2020-08-21,119.262497,124.867500,119.250000,124.370003,123.970551,338054800
90 | 2020-08-24,128.697495,128.785004,123.937500,125.857498,125.453270,345937600
91 | 2020-08-25,124.697502,125.180000,123.052498,124.824997,124.424088,211495600
92 | 2020-08-26,126.180000,126.992500,125.082497,126.522499,126.116135,163022400
93 | 2020-08-27,127.142502,127.485001,123.832497,125.010002,124.608498,155552400
94 | 2020-08-28,126.012497,126.442497,124.577499,124.807503,124.406647,187630000
95 | 2020-08-31,127.580002,131.000000,126.000000,129.039993,128.625549,225702700
96 | 2020-09-01,132.759995,134.800003,130.529999,134.179993,133.749039,152470100
97 | 2020-09-02,137.589996,137.979996,127.000000,131.399994,130.977966,200119000
98 | 2020-09-03,126.910004,128.839996,120.500000,120.879997,120.491753,257599600
99 | 2020-09-04,120.070000,123.699997,110.889999,120.959999,120.571503,332607200
100 | 2020-09-08,113.949997,118.989998,112.680000,112.820000,112.457649,231366600
101 | 2020-09-09,117.260002,119.139999,115.260002,117.320000,116.943192,176940500
102 | 2020-09-10,120.360001,120.500000,112.500000,113.489998,113.125496,182274400
103 | 2020-09-11,114.570000,115.230003,110.000000,112.000000,111.640282,180860300
104 | 2020-09-14,114.720001,115.930000,112.800003,115.360001,114.989487,140150100
105 | 2020-09-15,118.330002,118.830002,113.610001,115.540001,115.168915,184642000
106 | 2020-09-16,115.230003,116.000000,112.040001,112.129997,111.769859,154679000
107 | 2020-09-17,109.720001,112.199997,108.709999,110.339996,109.985611,178011000
108 | 2020-09-18,110.400002,110.879997,106.089996,106.839996,106.496849,287104900
109 | 2020-09-21,104.540001,110.190002,103.099998,110.080002,109.726448,195713800
110 | 2020-09-22,112.680000,112.860001,109.160004,111.809998,111.450882,183055400
111 | 2020-09-23,111.620003,112.110001,106.769997,107.120003,106.775963,150718700
112 | 2020-09-24,105.169998,110.250000,105.000000,108.220001,107.872421,167743300
113 | 2020-09-25,108.430000,112.440002,107.669998,112.279999,111.919373,149981400
114 | 2020-09-28,115.010002,115.320000,112.779999,114.959999,114.590767,137672400
115 | 2020-09-29,114.550003,115.309998,113.570000,114.089996,113.723564,99382200
116 | 2020-09-30,113.790001,117.260002,113.620003,115.809998,115.438042,142675200
117 | 2020-10-01,117.639999,117.720001,115.830002,116.790001,116.414894,116120400
118 | 2020-10-02,112.889999,115.370003,112.220001,113.019997,112.657005,144712000
119 | 2020-10-05,113.910004,116.650002,113.550003,116.500000,116.125824,106243800
120 | 2020-10-06,115.699997,116.120003,112.250000,113.160004,112.796555,161498200
121 | 2020-10-07,114.620003,115.550003,114.129997,115.080002,114.710388,96849000
122 | 2020-10-08,116.250000,116.400002,114.589996,114.970001,114.600739,83477200
123 | 2020-10-09,115.279999,117.000000,114.919998,116.970001,116.594315,100506900
124 | 2020-10-12,120.059998,125.180000,119.279999,124.400002,124.000458,240226800
125 | 2020-10-13,125.269997,125.389999,119.650002,121.099998,120.711044,262330500
126 | 2020-10-14,121.000000,123.029999,119.620003,121.190002,120.800766,151062300
127 | 2020-10-15,118.720001,121.199997,118.150002,120.709999,120.322304,112559200
128 | 2020-10-16,121.279999,121.550003,118.809998,119.019997,118.637726,115393800
129 | 2020-10-19,119.959999,120.419998,115.660004,115.980003,115.607498,120639300
130 | 2020-10-20,116.199997,118.980003,115.629997,117.510002,117.132591,124423700
131 | 2020-10-21,116.669998,118.709999,116.449997,116.870003,116.494644,89946000
132 | 2020-10-22,117.449997,118.040001,114.589996,115.750000,115.378235,101988000
133 | 2020-10-23,116.389999,116.550003,114.279999,115.040001,114.670517,82572600
134 | 2020-10-26,114.010002,116.550003,112.879997,115.050003,114.680489,111850700
135 | 2020-10-27,115.489998,117.279999,114.540001,116.599998,116.225510,92276800
136 | 2020-10-28,115.050003,115.430000,111.099998,111.199997,110.842850,143937800
137 | 2020-10-29,112.370003,116.930000,112.199997,115.320000,114.949615,146129200
138 | 2020-10-30,111.059998,111.989998,107.720001,108.860001,108.510361,190272600
139 | 2020-11-02,109.110001,110.680000,107.320000,108.769997,108.420654,122866900
140 | 2020-11-03,109.660004,111.489998,108.730003,110.440002,110.085289,107624400
141 | 2020-11-04,114.139999,115.589996,112.349998,114.949997,114.580803,138235500
142 | 2020-11-05,117.949997,119.620003,116.870003,119.029999,118.647697,126387100
143 | 2020-11-06,118.320000,119.199997,116.129997,118.690002,118.512909,114457900
144 | 2020-11-09,120.500000,121.989998,116.050003,116.320000,116.146439,154515300
145 | 2020-11-10,115.550003,117.589996,114.129997,115.970001,115.796967,138023400
146 | 2020-11-11,117.190002,119.629997,116.440002,119.489998,119.311707,112295000
147 | 2020-11-12,119.620003,120.529999,118.570000,119.209999,119.032127,103162300
148 | 2020-11-13,119.440002,119.669998,117.870003,119.260002,119.082054,81581900
149 | 2020-11-16,118.919998,120.989998,118.150002,120.300003,120.120506,91183000
150 | 2020-11-17,119.550003,120.669998,118.959999,119.389999,119.211861,74271000
151 | 2020-11-18,118.610001,119.820000,118.000000,118.029999,117.853889,76322100
152 | 2020-11-19,117.589996,119.059998,116.809998,118.639999,118.462982,74113000
153 | 2020-11-20,118.639999,118.769997,117.290001,117.339996,117.164917,73604300
154 | 2020-11-23,117.180000,117.620003,113.750000,113.849998,113.680122,127959300
155 | 2020-11-24,113.910004,115.849998,112.589996,115.169998,114.998154,113874200
156 | 2020-11-25,115.550003,116.750000,115.169998,116.029999,115.856873,76499200
157 | 2020-11-27,116.570000,117.489998,116.220001,116.589996,116.416031,46691300
158 | 2020-11-30,116.970001,120.970001,116.809998,119.050003,118.872368,169410200
159 | 2020-12-01,121.010002,123.470001,120.010002,122.720001,122.536896,128166800
160 | 2020-12-02,122.019997,123.370003,120.889999,123.080002,122.896355,89004200
161 | 2020-12-03,123.519997,123.779999,122.209999,122.940002,122.756569,78967600
162 | 2020-12-04,122.599998,122.860001,121.519997,122.250000,122.067596,78260400
163 | 2020-12-07,122.309998,124.570000,122.250000,123.750000,123.565353,86712000
164 | 2020-12-08,124.370003,124.980003,123.089996,124.379997,124.194412,82225500
165 | 2020-12-09,124.529999,125.949997,121.000000,121.779999,121.598289,115089200
166 | 2020-12-10,120.500000,123.870003,120.150002,123.239998,123.056114,81312200
167 | 2020-12-11,122.430000,122.760002,120.550003,122.410004,122.227356,86939800
168 | 2020-12-14,122.599998,123.349998,121.540001,121.779999,121.598289,79184500
169 | 2020-12-15,124.339996,127.900002,124.129997,127.879997,127.689186,157572300
170 | 2020-12-16,127.410004,128.369995,126.559998,127.809998,127.619293,98208600
171 | 2020-12-17,128.899994,129.580002,128.039993,128.699997,128.507965,94359800
172 | 2020-12-18,128.960007,129.100006,126.120003,126.660004,126.471016,192541500
173 | 2020-12-21,125.019997,128.309998,123.449997,128.229996,128.038666,121251600
174 | 2020-12-22,131.610001,134.410004,129.649994,131.880005,131.683228,168904800
175 | 2020-12-23,132.160004,132.429993,130.779999,130.960007,130.764603,88223700
176 | 2020-12-24,131.320007,133.460007,131.100006,131.970001,131.773087,54930100
177 | 2020-12-28,133.990005,137.339996,133.509995,136.690002,136.486053,124486200
178 | 2020-12-29,138.050003,138.789993,134.339996,134.869995,134.668762,121047300
179 | 2020-12-30,135.580002,135.990005,133.399994,133.720001,133.520477,96452100
180 | 2020-12-31,134.080002,134.740005,131.720001,132.690002,132.492020,99116600
181 | 2021-01-04,133.520004,133.610001,126.760002,129.410004,129.216919,143301900
182 | 2021-01-05,128.889999,131.740005,128.429993,131.009995,130.814514,97664900
183 | 2021-01-06,127.720001,131.050003,126.379997,126.599998,126.411102,155088000
184 | 2021-01-07,128.360001,131.630005,127.860001,130.919998,130.724655,109578200
185 | 2021-01-08,132.429993,132.630005,130.229996,132.050003,131.852966,105158200
186 | 2021-01-11,129.190002,130.169998,128.500000,128.979996,128.787552,100620900
187 | 2021-01-12,128.500000,129.690002,126.860001,128.800003,128.607819,91951100
188 | 2021-01-13,128.759995,131.449997,128.490005,130.889999,130.694702,88636800
189 | 2021-01-14,130.800003,131.000000,128.759995,128.910004,128.717667,90221800
190 | 2021-01-15,128.779999,130.220001,127.000000,127.139999,126.950294,111598500
191 | 2021-01-19,127.779999,128.710007,126.940002,127.830002,127.639267,90757300
192 | 2021-01-20,128.660004,132.490005,128.550003,132.029999,131.832993,104319500
193 | 2021-01-21,133.800003,139.669998,133.589996,136.869995,136.665771,120529500
194 | 2021-01-22,136.279999,139.850006,135.020004,139.070007,138.862503,114459400
195 | 2021-01-25,143.070007,145.089996,136.539993,142.919998,142.706757,157611700
196 | 2021-01-26,143.600006,144.300003,141.369995,143.160004,142.946396,98390600
197 | 2021-01-27,143.429993,144.300003,140.410004,142.059998,141.848038,140843800
198 | 2021-01-28,139.520004,141.990005,136.699997,137.089996,136.885452,142621100
199 | 2021-01-29,135.830002,136.740005,130.210007,131.960007,131.763107,177180600
200 | 2021-02-01,133.750000,135.380005,130.929993,134.139999,133.939850,106239800
201 | 2021-02-02,135.729996,136.309998,134.610001,134.990005,134.788589,83305400
202 | 2021-02-03,135.759995,135.770004,133.610001,133.940002,133.740158,89880900
203 | 2021-02-04,136.300003,137.399994,134.589996,137.389999,137.184998,84183100
204 | 2021-02-05,137.350006,137.419998,135.860001,136.759995,136.759995,75524000
205 | 2021-02-08,136.029999,136.960007,134.919998,136.910004,136.910004,71297200
206 | 2021-02-09,136.619995,137.880005,135.850006,136.009995,136.009995,76774200
207 | 2021-02-10,136.479996,136.990005,134.399994,135.389999,135.389999,73046600
208 | 2021-02-11,135.899994,136.389999,133.770004,135.130005,135.130005,64154400
209 | 2021-02-12,134.350006,135.529999,133.690002,135.369995,135.369995,60029300
210 | 2021-02-16,135.490005,136.009995,132.789993,133.190002,133.190002,80576300
211 | 2021-02-17,131.250000,132.220001,129.470001,130.839996,130.839996,97918500
212 | 2021-02-18,129.199997,130.000000,127.410004,129.710007,129.710007,96856700
213 | 2021-02-19,130.240005,130.710007,128.800003,129.869995,129.869995,87668800
214 | 2021-02-22,128.009995,129.720001,125.599998,126.000000,126.000000,103916400
215 | 2021-02-23,123.760002,126.709999,118.389999,125.860001,125.860001,158273000
216 | 2021-02-24,124.940002,125.559998,122.230003,125.349998,125.349998,111039900
217 | 2021-02-25,124.680000,126.459999,120.540001,120.989998,120.989998,148199500
218 | 2021-02-26,122.589996,124.849998,121.199997,121.260002,121.260002,164320000
219 | 2021-03-01,123.750000,127.930000,122.790001,127.790001,127.790001,115998300
220 | 2021-03-02,128.410004,128.720001,125.010002,125.120003,125.120003,102015300
221 | 2021-03-03,124.809998,125.709999,121.839996,122.059998,122.059998,112430400
222 | 2021-03-04,121.750000,123.599998,118.620003,120.129997,120.129997,177275300
223 | 2021-03-05,120.980003,121.940002,117.570000,121.419998,121.419998,153590400
224 | 2021-03-08,120.930000,121.000000,116.209999,116.360001,116.360001,153918600
225 | 2021-03-09,119.029999,122.059998,118.790001,121.089996,121.089996,129159600
226 | 2021-03-10,121.690002,122.169998,119.449997,119.980003,119.980003,111760400
227 | 2021-03-11,122.540001,123.209999,121.260002,121.959999,121.959999,102753600
228 | 2021-03-12,120.400002,121.169998,119.160004,121.029999,121.029999,87963400
229 | 2021-03-15,121.410004,124.000000,120.419998,123.989998,123.989998,92403800
230 | 2021-03-16,125.699997,127.220001,124.720001,125.570000,125.570000,114740000
231 | 2021-03-17,124.050003,125.860001,122.339996,124.760002,124.760002,111437500
232 | 2021-03-18,122.879997,123.180000,120.320000,120.529999,120.529999,121229700
233 | 2021-03-19,119.900002,121.430000,119.680000,119.989998,119.989998,185023200
234 | 2021-03-22,120.330002,123.870003,120.260002,123.389999,123.389999,111912300
235 | 2021-03-23,123.330002,124.239998,122.139999,122.540001,122.540001,95467100
236 | 2021-03-24,122.820000,122.900002,120.070000,120.089996,120.089996,88530500
237 | 2021-03-25,119.540001,121.660004,119.000000,120.589996,120.589996,98844700
238 | 2021-03-26,120.349998,121.480003,118.919998,121.209999,121.209999,93958900
239 | 2021-03-29,121.650002,122.580002,120.730003,121.389999,121.389999,80819200
240 | 2021-03-30,120.110001,120.400002,118.860001,119.900002,119.900002,85671900
241 | 2021-03-31,121.650002,123.519997,121.150002,122.150002,122.150002,118323800
242 | 2021-04-01,123.660004,124.180000,122.489998,123.000000,123.000000,74957400
243 | 2021-04-05,123.870003,126.160004,123.070000,125.900002,125.900002,88651200
244 | 2021-04-06,126.500000,127.129997,125.650002,126.209999,126.209999,80171300
245 | 2021-04-07,125.830002,127.919998,125.139999,127.900002,127.900002,83466700
246 | 2021-04-08,128.949997,130.389999,128.520004,130.360001,130.360001,88844600
247 | 2021-04-09,129.800003,133.039993,129.470001,133.000000,133.000000,106513800
248 | 2021-04-12,132.520004,132.850006,130.630005,131.240005,131.240005,91420000
249 | 2021-04-13,132.440002,134.660004,131.929993,134.429993,134.429993,91266500
250 | 2021-04-14,134.940002,135.000000,131.660004,132.029999,132.029999,87222800
251 | 2021-04-15,133.820007,135.000000,133.639999,134.500000,134.500000,89347100
252 | 2021-04-16,134.300003,134.669998,133.279999,134.160004,134.160004,84818500
253 | 2021-04-19,133.509995,135.470001,133.339996,134.839996,134.839996,93996100
254 | 2021-04-20,135.020004,135.529999,132.281296,132.740005,132.740005,68181997
--------------------------------------------------------------------------------