diff --git a/README.md b/README.md index 16cffc7..70914c9 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@ -# Pairs-API v3 for trading tickers (single or pairs), deployed on Heroku & Dreamhost +# Flask-RESTful API for trading tickers (single or pairs), deployed on Heroku & Dreamhost -Version 3 of the Flask-RESTful API. - -(Latest Release: v3.2) +(Latest Release: v4.0) Built from the ground-up with Flask-RESTful & Flask-SQLAlchemy & Flask-JWT-Extended. Configured to be used with SQLite3 for local use. @@ -12,7 +10,18 @@ A working demo for the latest release is deployed in Heroku with PostgreSQL: https://api-pairs.herokuapp.com/ -# Watch Demo +# Additions to v3 + +- configuration file +- yfinance integration (for ticker price data) +- simple moving average(SMA) calculation for pairs +- slippage calculation +- email notifications for waiting/problematic orders +- demo improvements: + - watchlist page for pairs + - SMA distance for active positions + +# Watch Demo for V3 [![Watch on YouTube](https://ozdemirozcelik.github.io/utubelink.png)](https://www.youtube.com/watch?v=-jfJ6g-fZpI "Watch on YouTube") @@ -25,6 +34,7 @@ https://api-pairs.herokuapp.com/ - demo improvements: - new dashboard - list view for signals + - quick buy/sell/close/revert buttons - functionality to work with Interactive Brokers TWS API (Release V3.1) - Check my repository: [PAIRS-IBKR](https://github.com/ozdemirozcelik/pairs-ibkr) - keep account position and PNL details @@ -32,19 +42,22 @@ https://api-pairs.herokuapp.com/ # Use Cases -With Pairs-API v3 you can: +With Pairs-API you can: - catch webhooks from trading platforms or signal generators - list, save, update and delete tickers/pairs, order and price details with API calls - enable and disable tickers and pairs for active trading - use access tokens for authentication purposes with login system backend -- TODO: send real time orders to exchange (possibly via Interactive Brokers) +- send real time orders to exchange (possibly via Interactive Brokers) - see account positions and PNL details +- create a watchlist +- see pair price distance to SMA # Considerations Considering for the next version: -- add statistical analysis and visualization +- automatic selection of pairs by comparing cointegration test results +- add more statistical analysis and visualization - improve error handling, add unit tests - set up CI for the repository @@ -56,8 +69,13 @@ Considering for the next version: * Flask-JWT-Extended~=4.4.0 * flask-sqlalchemy~=2.5.1 * flask-session~=0.4.0 +* flask-mail~=0.9.1 * pyjwt~=2.4.0 * pytz~=2022.1 +* yfinance~=0.1.87 +* apscheduler~=3.9.1.post1 +* pandas~=1.1.5 +* numpy~=1.19.5 * uwsgi~=2.0.20 (for Heroku deployment only) * psycopg2~=2.9.3 (for Heroku Postgres deployment only) @@ -66,7 +84,7 @@ Considering for the next version: ### clone git repository: ```bash -$ git clone https://github.com/ozdemirozcelik/pairs-api-v3.git +$ git clone https://github.com/ozdemirozcelik/pairs-api.git ```` ### create and activate virtual environment: ````bash @@ -121,25 +139,23 @@ browse to "http://127.0.0.1:5000/" to see the dashboard. # Authorization ### webhooks -need a passphrase, by default it is set as 'webhook'; check signals.py: +need a passphrase, by default it is set as 'webhook'; check config.ini: ```python -PASSPHRASE = 'webhook' +# change this after installation +WEBHOOK_PASSPHRASE : webhook ``` ### default admin and a user -is created during database creation; check users.py:: +is created during database creation; check config.ini: ```python -@staticmethod -def default_users(): - # Add Default Users - if not UserModel.find_by_username("admin"): - admin = UserModel("admin", "123") - admin.insert() - if not UserModel.find_by_username("user1"): - user = UserModel("user1", "123") - user.insert() +# below should be edited via API after the first creation +# changes do not apply after installation +ADMIN_USERNAME : admin +ADMIN_PASSWORD: password +USER1_USERNAME: user1 +USER1_PASSWORD: password ``` ### resource authorization @@ -194,48 +210,51 @@ Check [Heroku deployment](#heroku-deployment) to learn for more about using your Resources defined with flask_restful are: ```python -api.add_resource(SignalWebhook, "/v3/webhook") -api.add_resource(SignalUpdateOrder, "/v3/signal/updateorder") -api.add_resource(SignalList, "/v3/signals/") -api.add_resource(SignalListStatus,"/v3/signals/status//",) -api.add_resource(SignalListTicker, "/v3/signals/ticker//") -api.add_resource(Signal, "/v3/signal/") +api.add_resource(SignalWebhook, "/v4/webhook") +api.add_resource(SignalUpdateOrder, "/v4/signal/updateorder") +api.add_resource(SignalList, "/v4/signals/") +api.add_resource(SignalListStatus,"/v4/signals/status//",) +api.add_resource(SignalListTicker, "/v4/signals/ticker//") +api.add_resource(Signal, "/v4/signal/") -api.add_resource(PairRegister, "/v3/regpair") -api.add_resource(PairList, "/v3/pairs/") -api.add_resource(Pair, "/v3/pair/") +api.add_resource(PairRegister, "/v4/regpair") +api.add_resource(PairList, "/v4/pairs/") +api.add_resource(Pair, "/v4/pair/") -api.add_resource(TickerRegister, "/v3/regticker") -api.add_resource(TickerUpdatePNL, "/v3/ticker/updatepnl") -api.add_resource(TickerList, "/v3/tickers/") -api.add_resource(Ticker, "/v3/ticker/") +api.add_resource(TickerRegister, "/v4/regticker") +api.add_resource(TickerUpdatePNL, "/v4/ticker/updatepnl") +api.add_resource(TickerList, "/v4/tickers/") +api.add_resource(Ticker, "/v4/ticker/") -api.add_resource(UserRegister, "/v3/reguser") -api.add_resource(UserList, "/v3/users/") -api.add_resource(User, "/v3/user/") -api.add_resource(UserLogin, "/v3/login") -api.add_resource(UserLogout, "/v3/logout") -api.add_resource(TokenRefresh, "/v3/refresh") +api.add_resource(UserRegister, "/v4/reguser") +api.add_resource(UserList, "/v4/users/") +api.add_resource(User, "/v4/user/") +api.add_resource(UserLogin, "/v4/login") +api.add_resource(UserLogout, "/v4/logout") +api.add_resource(TokenRefresh, "/v4/refresh") -api.add_resource(PNLRegister, "/v3/regpnl") -api.add_resource(PNLList, "/v3/pnl/") +api.add_resource(PNLRegister, "/v4/regpnl") +api.add_resource(PNLList, "/v4/pnl/") ``` # Request & Response Examples -Please check the [POSTMAN collection](local/pairs_api%20v3.postman_collection.json) to test all resources. +Please check the [POSTMAN collection](local/pairs_api%20v4.postman_collection.json) for all resources. ### POST request to register a single ticker: ```python -'http://api-pairs-v3.herokuapp.com/v3/regticker' +'http://api-pairs.herokuapp.com/v4/regticker' ``` Request Body: ```json { "symbol": "AAPL", - "prixch": "SMART", - "secxch": "NASDAQ", - "active": 1 + "sectype": "STK", + "xch": "SMART", + "prixch": "NASDAQ", + "currency": "USD", + "order_type": "RELATIVE", + "active": 0 } ``` @@ -246,17 +265,20 @@ Response: } ``` -### PUT request to update a single ticker: +### PUT request to update a single ticker. '-1' to add to the watchlist: ```python -'http://api-pairs-v3.herokuapp.com/v3/regticker' +'http://api-pairs.herokuapp.com/v4/regticker' ``` Request Body: ```json { "symbol": "AAPL", - "prixch": "SMART", - "secxch": "NASDAQ", - "active": 1 + "sectype": "STK", + "xch": "SMART", + "prixch": "NASDAQ", + "currency": "USD", + "order_type": "RELATIVE", + "active": -1 } ``` @@ -264,36 +286,51 @@ Response: ```json { "symbol": "AAPL", - "prixch": "ISLAND", - "secxch": "BYX", - "active": 0 + "sectype": "STK", + "xch": "SMART", + "prixch": "NASDAQ", + "currency": "USD", + "order_type": "RELATIVE", + "active": -1 } ``` ### GET request to get all tickers: ```python -'http://api-pairs-v3.herokuapp.com/v3/tickers/0' +'http://api-pairs.herokuapp.com/v4/tickers/0' ``` ### GET request to receive certain number of tickers (for exp: 50): ```python -'http://api-pairs-v3.herokuapp.com/v3/tickers/2' +'http://api-pairs.herokuapp.com/v4/tickers/2' ``` Response: ```json { "tickers": [ { - "symbol": "AAPL", - "prixch": "SMART", - "secxch": "NASDAQ", - "active": 1 + "symbol": "NEM", + "sectype": "STK", + "xch": "SMART", + "prixch": "NYSE", + "currency": "USD", + "order_type": "RELATIVE", + "active": 0, + "active_pos": -131.0, + "active_pnl": -117.0, + "active_cost": 46.021 }, { - "symbol": "DRH", - "prixch": "SMART", - "secxch": "SMART", - "active": 1 + "symbol": "SLB", + "sectype": "STK", + "xch": "SMART", + "prixch": "NYSE", + "currency": "USD", + "order_type": "RELATIVE", + "active": 0, + "active_pos": 0.0, + "active_pnl": 0.0, + "active_cost": 0.0 } ] } @@ -301,21 +338,48 @@ Response: ### GET request to get details of a certain ticker: ```python -'http://api-pairs-v3.herokuapp.com/v3/ticker/AAPL' +'http://api-pairs-v4.herokuapp.com/v4/ticker/NOVA' +``` +Request Body: +```json +{ + "passphrase": "webhook", + "symbol": "NEM", + "active_pos": -131.0, + "active_pnl": -117.0, + "active_cost": 46.021 +} ``` Response: ```json { - "symbol": "AAPL", - "prixch": "SMART", - "secxch": "NASDAQ", - "active": 1 + "symbol": "NEM", + "sectype": "STK", + "xch": "SMART", + "prixch": "NYSE", + "currency": "USD", + "order_type": "RELATIVE", + "active": 0, + "active_pos": -131.0, + "active_pnl": -117.0, + "active_cost": 46.021 } ``` ### DELETE request for a certain ticker: ```python -'http://api-pairs-v3.herokuapp.com/v3/ticker/AAPL' +'http://api-pairs.herokuapp.com/v4/ticker/AAPL' +``` +Response: +```json +{ + "message": "Item deleted" +} +``` + +### PUT request to update PNL records: +```python +'http://api-pairs.herokuapp.com/v4/ticker/updatepnl' ``` Response: ```json @@ -326,15 +390,16 @@ Response: ### POST request to register a pair: ```python -'http://api-pairs-v3.herokuapp.com/v3/regpair' +'http://api-pairs.herokuapp.com/v4/regpair' ``` Request Body: ```json { + "name": "MA-V", "ticker1": "MA", "ticker2": "V", - "hedge": 1.3, - "status": 0 + "hedge": 1.6, + "contracts": 36 } ``` @@ -347,7 +412,7 @@ Response: ### PUT request to update a pair: ```python -'http://api-pairs-v3.herokuapp.com/v3/regpair' +'http://api-pairs.herokuapp.com/v4/regpair' ``` Request Body: ```json @@ -355,8 +420,8 @@ Request Body: "name": "MA-V", "ticker1": "MA", "ticker2": "V", - "hedge": 1.4, - "status": 1 + "hedge": 1.6, + "contracts": 40 } ``` @@ -366,15 +431,20 @@ Response: "name": "MA-V", "ticker1": "MA", "ticker2": "V", - "hedge": 1.4, - "status": 1, - "notes": null + "hedge": 1.6, + "status": 0, + "notes": null, + "contracts": 40, + "act_price": 0.0, + "sma": 0.0, + "sma_dist": 0.0, + "std": 0.0 } ``` ### POST request to register a webhook signal: ```python -'http://api-pairs-v3.herokuapp.com/v3/webhook' +'http://api-pairs.herokuapp.com/v4/webhook' ``` Request Body: ```json @@ -402,7 +472,7 @@ Response: ### GET request to get a list of signals with certain trade status ```python -'http://api-pairs-v3.herokuapp.com/v3/signals/status/waiting/2' +'http://api-pairs.herokuapp.com/v4/signals/status/waiting/1' ``` Response: ```json @@ -419,51 +489,27 @@ Response: "mar_pos_size": 100, "pre_mar_pos": "flat", "pre_mar_pos_size": 0, - "order_comment": "Enter Long(...)", - "order_status": "waiting", - "ticker_type": "pair", - "stk_ticker1": "NMFC", - "stk_ticker2": "ROIC", - "hedge_param": 0.72, - "order_id1": null, - "order_id2": null, - "stk_price1": null, - "stk_price2": null, - "fill_price": null, - "slip": null, - "error_msg": null - }, - { - "rowid": 27, - "timestamp": "2022-05-26 21:16:49", - "ticker": "NMFC-0.72*NASDAQ:ROIC", - "order_action": "buy", - "order_contracts": 100, - "order_price": -0.01, - "mar_pos": "long", - "mar_pos_size": 100, - "pre_mar_pos": "flat", - "pre_mar_pos_size": 0, - "order_comment": "Enter Long(...)", + "order_comment": "Enter Long(manual)", "order_status": "waiting", "ticker_type": "pair", - "stk_ticker1": "NMFC", - "stk_ticker2": "ROIC", + "ticker1": "NMFC", + "ticker2": "ROIC", "hedge_param": 0.72, "order_id1": null, "order_id2": null, - "stk_price1": null, - "stk_price2": null, + "price1": null, + "price2": null, "fill_price": null, "slip": null, - "error_msg": null + "error_msg": null, + "status_msg": "passive ticker" } ] } ``` ### PUT request to update order price and status by order id ```python -'http://api-pairs-v3.herokuapp.com/v3/signal/updateorder' +'http://api-pairs.herokuapp.com/v4/signal/updateorder' ``` ```` @@ -512,7 +558,7 @@ Response: ### POST request to login with a user ```python -'http://api-pairs-v3.herokuapp.com/v3/login' +'http://api-pairs.herokuapp.com/v4/login' ``` Request Body (Token to expire in 30 min, default is 10 min): ```json @@ -533,7 +579,7 @@ Response: # Status Codes -Pairs-API v3 returns the following status codes: +Pairs-API returns the following status codes: | Status Code | Description | | :--- |:------------------------| @@ -550,7 +596,7 @@ Download and install [Heroku CLI](https://devcenter.heroku.com/articles/heroku-c Clone repository, login to Heroku, add git remote and push: ```` -$ git clone https://github.com/ozdemirozcelik/pairs-api-v3.git +$ git clone https://github.com/ozdemirozcelik/pairs-api.git $ heroku login $ heroku git:remote -a [your-heroku-app-name] $ git push heroku main @@ -580,7 +626,7 @@ Please follow the instructions here: You can use below template for TradingView to send a POST request as soon as an alert is triggered. -webhook URL should be: '{URL_OF_YOUR_API}/v3/webhook' +webhook URL should be: '{URL_OF_YOUR_API}/v4/webhook' (local\webhook.json) ````json diff --git a/app.py b/app.py index 999d09f..876153d 100644 --- a/app.py +++ b/app.py @@ -188,36 +188,36 @@ def my_revoked_token_callback(jwt_header, jwt_payload): # Resource definitions (Start) -api.add_resource(SignalWebhook, "/v3/webhook") -api.add_resource(SignalUpdateOrder, "/v3/signal/updateorder") -api.add_resource(SignalList, "/v3/signals/") +api.add_resource(SignalWebhook, "/v4/webhook") +api.add_resource(SignalUpdateOrder, "/v4/signal/updateorder") +api.add_resource(SignalList, "/v4/signals/") api.add_resource( SignalListStatus, - "/v3/signals/status//", + "/v4/signals/status//", ) api.add_resource( - SignalListTicker, "/v3/signals/ticker//" + SignalListTicker, "/v4/signals/ticker//" ) -api.add_resource(Signal, "/v3/signal/") - -api.add_resource(PairRegister, "/v3/regpair") -api.add_resource(PairList, "/v3/pairs/") -api.add_resource(Pair, "/v3/pair/") - -api.add_resource(TickerRegister, "/v3/regticker") -api.add_resource(TickerUpdatePNL, "/v3/ticker/updatepnl") -api.add_resource(TickerList, "/v3/tickers/") -api.add_resource(Ticker, "/v3/ticker/") - -api.add_resource(UserRegister, "/v3/reguser") -api.add_resource(UserList, "/v3/users/") -api.add_resource(User, "/v3/user/") -api.add_resource(UserLogin, "/v3/login") -api.add_resource(UserLogout, "/v3/logout") -api.add_resource(TokenRefresh, "/v3/refresh") - -api.add_resource(PNLRegister, "/v3/regpnl") -api.add_resource(PNLList, "/v3/pnl/") +api.add_resource(Signal, "/v4/signal/") + +api.add_resource(PairRegister, "/v4/regpair") +api.add_resource(PairList, "/v4/pairs/") +api.add_resource(Pair, "/v4/pair/") + +api.add_resource(TickerRegister, "/v4/regticker") +api.add_resource(TickerUpdatePNL, "/v4/ticker/updatepnl") +api.add_resource(TickerList, "/v4/tickers/") +api.add_resource(Ticker, "/v4/ticker/") + +api.add_resource(UserRegister, "/v4/reguser") +api.add_resource(UserList, "/v4/users/") +api.add_resource(User, "/v4/user/") +api.add_resource(UserLogin, "/v4/login") +api.add_resource(UserLogout, "/v4/logout") +api.add_resource(TokenRefresh, "/v4/refresh") + +api.add_resource(PNLRegister, "/v4/regpnl") +api.add_resource(PNLList, "/v4/pnl/") # Resource definitions (End) diff --git a/config.ini b/config.ini index d544a2b..0f08ca6 100644 --- a/config.ini +++ b/config.ini @@ -41,6 +41,6 @@ MAIL_BODY : waiting orders-possible server problem ENABLE_SMA_CALC = True # calculate 20D moving average in every x minutes # data is from yahoo finance, check for rate limitations: https://pypi.org/project/yfinance/ -SMA_CALC_PERIOD = 5 +SMA_CALC_PERIOD = 20 diff --git a/local/pairs_api v4.postman_collection.json b/local/pairs_api v4.postman_collection.json new file mode 100644 index 0000000..fa08944 --- /dev/null +++ b/local/pairs_api v4.postman_collection.json @@ -0,0 +1,1004 @@ +{ + "info": { + "_postman_id": "8514209e-cb47-450e-ae99-09e33b7f6c54", + "name": "pairs_api v4", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "20044007" + }, + "item": [ + { + "name": "tickers", + "item": [ + { + "name": "register (a ticker)", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"symbol\": \"NOVA\",\r\n \"sectype\": \"STK\",\r\n \"xch\": \"SMART\",\r\n \"prixch\": \"NASDAQ\",\r\n \"currency\":\"USD\",\r\n \"active\": 0\r\n\r\n}" + }, + "url": { + "raw": "{{pairs_api_v4}}/regticker", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "regticker" + ] + } + }, + "response": [] + }, + { + "name": "update(a ticker)", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"symbol\": \"NEM\",\r\n \"sectype\": \"STK\",\r\n \"xch\": \"SMART\",\r\n \"prixch\": \"NYSE\",\r\n \"currency\": \"USD\",\r\n \"order_type\": \"RELATIVE\",\r\n \"active\": 0\r\n}" + }, + "url": { + "raw": "{{pairs_api_v4}}/regticker", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "regticker" + ] + } + }, + "response": [] + }, + { + "name": "update(PNL)", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"passphrase\": \"webhook\",\r\n \"symbol\": \"NEM\",\r\n \"active_pos\": -131.0,\r\n \"active_pnl\": -117.0,\r\n \"active_cost\": 46.021\r\n\r\n}" + }, + "url": { + "raw": "{{pairs_api_v4}}/ticker/updatepnl", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "ticker", + "updatepnl" + ] + } + }, + "response": [] + }, + { + "name": "tickers (# of items)", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text", + "disabled": true + } + ], + "url": { + "raw": "{{pairs_api_v4}}/tickers/0", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "tickers", + "0" + ] + } + }, + "response": [] + }, + { + "name": "ticker (details)", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text" + } + ], + "url": { + "raw": "{{pairs_api_v4}}/ticker/NOVA", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "ticker", + "NOVA" + ] + } + }, + "response": [] + }, + { + "name": "ticker (delete)", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text" + } + ], + "url": { + "raw": "{{pairs_api_v4}}/ticker/CCC", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "ticker", + "CCC" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "account", + "item": [ + { + "name": "register (PNL)", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"passphrase\":\"webhook\",\r\n \"AvailableFunds\": 31000,\r\n \"BuyingPower\": 103295,\r\n \"GrossPositionValue\": 103809,\r\n \"MaintMarginReq\": 31600,\r\n \"NetLiquidation\": 62588\r\n\r\n\r\n}" + }, + "url": { + "raw": "{{pairs_api_v4}}/regpnl", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "regpnl" + ] + } + }, + "response": [] + }, + { + "name": "update(PNL)", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"passphrase\":\"webhook\",\r\n \"rowid\":10,\r\n \"timestamp\":\"2022-06-16 23:55:36\",\r\n \"AvailableFunds\": 32000,\r\n \"BuyingPower\": 103295,\r\n \"DailyPnL\": 268.9,\r\n \"GrossPositionValue\": 103809,\r\n \"MaintMarginReq\": 31600,\r\n \"NetLiquidation\": 62588,\r\n \"RealizedPnL\": 30,\r\n \"UnrealizedPnL\": -1000\r\n\r\n}" + }, + "url": { + "raw": "{{pairs_api_v4}}/regpnl", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "regpnl" + ] + } + }, + "response": [] + }, + { + "name": "pnl (# of items)", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text", + "disabled": true + } + ], + "url": { + "raw": "{{pairs_api_v4}}/pnl/0", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "pnl", + "0" + ] + } + }, + "response": [] + }, + { + "name": "pnl(delete)", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text" + } + ], + "url": { + "raw": "{{pairs_api_v4}}/pnl/2", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "pnl", + "2" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "pairs", + "item": [ + { + "name": "register (a pair)", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"MA-V\",\r\n \"ticker1\": \"MA\",\r\n \"ticker2\": \"V\",\r\n \"hedge\": 1.6,\r\n \"contracts\": 36\r\n}" + }, + "url": { + "raw": "{{pairs_api_v4}}/regpair", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "regpair" + ] + } + }, + "response": [] + }, + { + "name": "update (a pair)", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"MA-V\",\r\n \"ticker1\": \"MA\",\r\n \"ticker2\": \"V\",\r\n \"hedge\": 1.6,\r\n \"contracts\": 40\r\n}" + }, + "url": { + "raw": "{{pairs_api_v4}}/regpair", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "regpair" + ] + } + }, + "response": [] + }, + { + "name": "pairs (# of items)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{pairs_api_v4}}/pairs/0", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "pairs", + "0" + ] + } + }, + "response": [] + }, + { + "name": "pair (details)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{pairs_api_v4}}/pair/MA-V", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "pair", + "MA-V" + ] + } + }, + "response": [] + }, + { + "name": "pair (delete)", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text" + } + ], + "url": { + "raw": "{{pairs_api_v4}}/pair/XXX-ZZZ", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "pair", + "XXX-ZZZ" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "signals", + "item": [ + { + "name": "register (a signal)", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"passphrase\": \"webhook\",\r\n \"ticker\": \"NOVA\",\r\n \"order_action\": \"buy\",\r\n \"order_contracts\": \"100\",\r\n \"order_price\": \"400.2\",\r\n \"mar_pos\": \"long\",\r\n \"mar_pos_size\": \"100\",\r\n \"pre_mar_pos\": \"flat\",\r\n \"pre_mar_pos_size\": \"0\",\r\n \"order_comment\": \" Enter Long\",\r\n \"order_status\": \"waiting\"\r\n}\r\n" + }, + "url": { + "raw": "{{pairs_api_v4}}/webhook", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "webhook" + ] + } + }, + "response": [] + }, + { + "name": "update (a signal)", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"rowid\": 87,\r\n \"timestamp\": \"2023-01-05 00:38:07\",\r\n \"ticker\": \"NOVA\",\r\n \"order_action\": \"buy\",\r\n \"order_contracts\": 100,\r\n \"order_price\": 400.2,\r\n \"mar_pos\": \"long\",\r\n \"mar_pos_size\": 100,\r\n \"pre_mar_pos\": \"flat\",\r\n \"pre_mar_pos_size\": 0,\r\n \"order_comment\": \" Enter Long\",\r\n \"order_status\": \"canceled\",\r\n \"ticker_type\": \"single\",\r\n \"ticker1\": \"NOVA\",\r\n \"ticker2\": null,\r\n \"hedge_param\": null,\r\n \"order_id1\": null,\r\n \"order_id2\": null,\r\n \"price1\": null,\r\n \"price2\": null,\r\n \"fill_price\": null,\r\n \"slip\": null,\r\n \"error_msg\": null,\r\n \"status_msg\": \"passive ticker\"\r\n}" + }, + "url": { + "raw": "{{pairs_api_v4}}/webhook", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "webhook" + ] + } + }, + "response": [] + }, + { + "name": "update (by order_id)", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"passphrase\": \"webhook\",\r\n \"symbol\": \"ROIC\",\r\n \"order_id\": 8,\r\n \"price\": 15.55,\r\n \"filled_qty\": 100\r\n}" + }, + "url": { + "raw": "{{pairs_api_v4}}/signal/updateorder", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "signal", + "updateorder" + ] + } + }, + "response": [] + }, + { + "name": "update (by order_id)(cancel)", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"passphrase\": \"webhook\",\r\n \"symbol\": \"ROIC\",\r\n \"order_id\": 7,\r\n \"price\": -1,\r\n \"filled_qty\": -1,\r\n \"cancel\":true\r\n}" + }, + "url": { + "raw": "{{pairs_api_v4}}/signal/updateorder", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "signal", + "updateorder" + ] + } + }, + "response": [] + }, + { + "name": "signals (# of items)", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text", + "disabled": true + } + ], + "url": { + "raw": "{{pairs_api_v4}}/signals/0", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "signals", + "0" + ] + } + }, + "response": [] + }, + { + "name": "list of ticker", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text", + "disabled": true + } + ], + "url": { + "raw": "{{pairs_api_v4}}/signals/ticker/CARR-GE/2", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "signals", + "ticker", + "CARR-GE", + "2" + ] + } + }, + "response": [] + }, + { + "name": "list of status", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text", + "disabled": true + } + ], + "url": { + "raw": "{{pairs_api_v4}}/signals/status/waiting/0", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "signals", + "status", + "waiting", + "0" + ] + } + }, + "response": [] + }, + { + "name": "signal details)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{pairs_api_v4}}/signal/43", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "signal", + "43" + ] + } + }, + "response": [] + }, + { + "name": "signal (delete)", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text" + } + ], + "url": { + "raw": "{{pairs_api_v4}}/signal/10", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "signal", + "10" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "users", + "item": [ + { + "name": "login(with a user)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();\r", + "pm.test(\"access_token not empty\", function () {\r", + " pm.expect(jsonData.access_token).not.eql(undefined);\r", + "});\r", + "\r", + "pm.test(\"refresh token not empty\", function () {\r", + " pm.expect(jsonData.refresh_token).not.eql(undefined);\r", + "});\r", + "// set access token as environement variable\r", + "if (jsonData.access_token !== undefined) {\r", + " postman.setEnvironmentVariable(\"access_token\", jsonData.access_token);\r", + "} else {\r", + " postman.setEnvironmentVariable(\"access_token\", null);\r", + "}\r", + "// set refresh token as environement variable\r", + "if (jsonData.refresh_token !== undefined) {\r", + " postman.setEnvironmentVariable(\"refresh_token\", jsonData.refresh_token);\r", + "} else {\r", + " postman.setEnvironmentVariable(\"refresh_token\", null);\r", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "", + "value": "", + "type": "text", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"username\": \"admin\",\r\n \"password\": \"password\",\r\n \"expire\":20\r\n\r\n}" + }, + "url": { + "raw": "{{pairs_api_v4}}/login", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "login" + ] + } + }, + "response": [] + }, + { + "name": "logout", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();\r", + "pm.test(\"access_token not empty\", function () {\r", + " pm.expect(jsonData.access_token).not.eql(undefined);\r", + "});\r", + "\r", + "pm.test(\"refresh token not empty\", function () {\r", + " pm.expect(jsonData.refresh_token).not.eql(undefined);\r", + "});\r", + "// set access token as environement variable\r", + "if (jsonData.access_token !== undefined) {\r", + " postman.setEnvironmentVariable(\"access_token\", jsonData.access_token);\r", + "} else {\r", + " postman.setEnvironmentVariable(\"access_token\", null);\r", + "}\r", + "// set refresh token as environement variable\r", + "if (jsonData.refresh_token !== undefined) {\r", + " postman.setEnvironmentVariable(\"refresh_token\", jsonData.refresh_token);\r", + "} else {\r", + " postman.setEnvironmentVariable(\"refresh_token\", null);\r", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{pairs_api_v4}}/logout", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "logout" + ] + } + }, + "response": [] + }, + { + "name": "refresh (token)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();\r", + "pm.test(\"access_token not empty\", function () {\r", + " pm.expect(jsonData.access_token).not.eql(undefined);\r", + "});\r", + "\r", + "pm.test(\"refresh token not empty\", function () {\r", + " pm.expect(jsonData.refresh_token).not.eql(undefined);\r", + "});\r", + "// set access token as environement variable\r", + "if (jsonData.access_token !== undefined) {\r", + " postman.setEnvironmentVariable(\"access_token\", jsonData.access_token);\r", + "} else {\r", + " postman.setEnvironmentVariable(\"access_token\", null);\r", + "}\r", + "// set refresh token as environement variable\r", + "if (jsonData.refresh_token !== undefined) {\r", + " postman.setEnvironmentVariable(\"refresh_token\", jsonData.refresh_token);\r", + "} else {\r", + " postman.setEnvironmentVariable(\"refresh_token\", null);\r", + "}" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{refresh_token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{pairs_api_v4}}/refresh", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "refresh" + ] + } + }, + "response": [] + }, + { + "name": "register (a user)", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"username\": \"user2\",\r\n \"password\": \"123\"\r\n\r\n}" + }, + "url": { + "raw": "{{pairs_api_v4}}/reguser", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "reguser" + ] + } + }, + "response": [] + }, + { + "name": "update(a user)", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"username\": \"user2\",\r\n \"password\": \"123\"\r\n\r\n}" + }, + "url": { + "raw": "{{pairs_api_v4}}/reguser", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "reguser" + ] + } + }, + "response": [] + }, + { + "name": "users (# of users)", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text" + } + ], + "url": { + "raw": "{{pairs_api_v4}}/users/0", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "users", + "0" + ] + } + }, + "response": [] + }, + { + "name": "user (details)", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text" + } + ], + "url": { + "raw": "{{pairs_api_v4}}/user/user2", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "user", + "user2" + ] + } + }, + "response": [] + }, + { + "name": "user (delete)", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{access_token}}", + "type": "text" + } + ], + "url": { + "raw": "{{pairs_api_v4}}/user/user", + "host": [ + "{{pairs_api_v4}}" + ], + "path": [ + "user", + "user" + ] + } + }, + "response": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 2d25438..5203418 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ flask-mail~=0.9.1 pyjwt~=2.4.0 pytz~=2022.1 yfinance~=0.1.87 -apsscheduler~=3.9.1.post1 +apscheduler~=3.9.1.post1 pandas~=1.1.5 numpy~=1.19.5 uwsgi~=2.0.20 diff --git a/static/list.js b/static/list.js index aac4ae9..953fa76 100644 --- a/static/list.js +++ b/static/list.js @@ -1,6 +1,6 @@ // define API end points for getting list of pairs and tickers -const api_url_list_all_ticker= server_url + 'v3/tickers/0'; -const api_url_list_all_pair= server_url + 'v3/pairs/0'; +const api_url_list_all_ticker= server_url + 'v4/tickers/0'; +const api_url_list_all_pair= server_url + 'v4/pairs/0'; var ticker_webhook = document.getElementById("ticker_webhook") diff --git a/static/pairs.js b/static/pairs.js index a9e37f7..46ef392 100644 --- a/static/pairs.js +++ b/static/pairs.js @@ -2,11 +2,11 @@ // test get, put, delete requests for the pairs // define api constants for the pairs: -const api_url_get_pair= server_url +'v3/pair/'; -const api_url_get_all_pairs= server_url +'v3/pairs/0';// "0" for all pairs. -const api_url_post_put_pair= server_url +'v3/regpair'; +const api_url_get_pair= server_url +'v4/pair/'; +const api_url_get_all_pairs= server_url +'v4/pairs/0';// "0" for all pairs. +const api_url_post_put_pair= server_url +'v4/regpair'; // define other api constants (defining as a separate constant to be used as a standalone script): -const api_url_get_all_tickers= server_url +'v3/tickers/0'; +const api_url_get_all_tickers= server_url +'v4/tickers/0'; // form data to be collected in these variables diff --git a/static/signals.js b/static/signals.js index a9ed009..bb7ab5c 100644 --- a/static/signals.js +++ b/static/signals.js @@ -2,12 +2,12 @@ // test get, put, delete requests for the signals // define api constants for the signals: -const api_url_get_signal= server_url + 'v3/signal/'; -const api_url_get_all_signals= server_url + 'v3/signals/100'; // "0" for all signals. Better to define a limit such as 100. -const api_url_post_put_signal= server_url + 'v3/webhook'; +const api_url_get_signal= server_url + 'v4/signal/'; +const api_url_get_all_signals= server_url + 'v4/signals/100'; // "0" for all signals. Better to define a limit such as 100. +const api_url_post_put_signal= server_url + 'v4/webhook'; // define other api constants (defining as a separate constant to be used as a standalone script): -const api_url_get_all_ticker= server_url + 'v3/tickers/0'; -const api_url_get_all_pair= server_url + 'v3/pairs/0'; +const api_url_get_all_ticker= server_url + 'v4/tickers/0'; +const api_url_get_all_pair= server_url + 'v4/pairs/0'; // form data to be collected in these variables diff --git a/static/tickers.js b/static/tickers.js index 6e0ad69..e80b7d0 100644 --- a/static/tickers.js +++ b/static/tickers.js @@ -2,9 +2,9 @@ // test get, put, delete requests for the tickers // define api constants for the tickers: -const api_url_get= server_url +'v3/ticker/'; -const api_url_get_all= server_url +'v3/tickers/0';// "0" for all tickers -const api_url_post_put= server_url +'v3/regticker'; +const api_url_get= server_url +'v4/ticker/'; +const api_url_get_all= server_url +'v4/tickers/0';// "0" for all tickers +const api_url_post_put= server_url +'v4/regticker'; // form data to be collected in these variables: var formJSON; diff --git a/static/users.js b/static/users.js index 0191f8c..3c0506a 100644 --- a/static/users.js +++ b/static/users.js @@ -1,6 +1,6 @@ // define api constants for the users: -const api_url_post_login= server_url +'v3/login'; -const api_url_post_logout= server_url +'v3/logout'; +const api_url_post_login= server_url +'v4/login'; +const api_url_post_logout= server_url +'v4/logout'; // define token defaults var token_data; diff --git a/templates/base.html b/templates/base.html index c891e56..c8cdab1 100644 --- a/templates/base.html +++ b/templates/base.html @@ -52,7 +52,9 @@

Signals

{% block watchlist %} {% endblock %} -
+ + {% block legendwatch %} + {% endblock %}