brighter-trading/src/app.py

323 lines
11 KiB
Python

import json
import logging
from flask import Flask, render_template, request, redirect, jsonify, session, flash
from flask_cors import CORS
from flask_sock import Sock
from email_validator import validate_email, EmailNotValidError
# Handles all updates and requests for locally stored data.
import config
from BrighterTrades import BrighterTrades
# Set up logging
logging.basicConfig(level=logging.DEBUG)
# Create a BrighterTrades object. This the main application that maintains access to the server, local storage,
# and manages objects that process trade data.
brighter_trades = BrighterTrades()
# Create a Flask object named app that serves the html.
app = Flask(__name__)
# Create a socket in order to receive requests.
sock = Sock(app)
# Set server configuration globals.
CORS_HEADERS = 'Content-Type'
# Set the app directly with the globals.
app.config.from_object(__name__)
app.secret_key = '1_BAD_secrete_KEY_is_not_2'
# Enable cross-origin resource sharing for specific endpoints
cors = CORS(app, supports_credentials=True,
resources={
r"/api/history": {"origins": ["http://127.0.0.1:5000", "http://localhost:5000"]},
r"/api/indicator_init": {"origins": ["http://127.0.0.1:5000", "http://localhost:5000"]}
},
allow_headers=['Content-Type']) # Change from headers to allow_headers
@app.after_request
def add_cors_headers(response):
response.headers['Access-Control-Allow-Credentials'] = 'true'
return response
@app.route('/')
# @cross_origin(supports_credentials=True)
def index():
"""
Fetches data from brighter_trades and inject it into an HTML template.
Renders the html template and serves the web application.
"""
# Clear the session to simulate a new visitor
session.clear()
try:
# Log the user in.
user_name = brighter_trades.config.users.load_or_create_user(username=session.get('user'))
except ValueError as e:
if str(e) != 'GuestLimitExceeded!':
raise
else:
flash('The system is too busy for another user.')
return
# Ensure client session is assigned a user_name.
session['user'] = user_name
print('[SERVING INDEX] (USERNAME):', user_name)
default_exchange = 'binance'
default_keys = None # No keys needed for public market data
# Ensure that a valid connection with an exchange exists
result = brighter_trades.connect_user_to_exchange(user_name=user_name,
default_exchange=default_exchange,
default_keys=default_keys)
if not result:
raise ValueError("Couldn't connect to the default exchange.")
# A dict of data required to build the html of the user app.
# Dynamic content like options and titles and balances to display.
rendered_data = brighter_trades.get_rendered_data(user_name)
# Jsom initialization data to be passed into the web application.
js_data = brighter_trades.get_js_init_data(user_name)
# serve the landing page.
return render_template('index.html',
title=rendered_data['title'],
user_name=user_name,
my_balances=rendered_data['my_balances'],
symbols=rendered_data['symbols'],
intervals=rendered_data['intervals'],
interval_state=rendered_data['chart_interval'],
exchanges=rendered_data['available_exchanges'],
cond_exchanges=rendered_data['connected_exchanges'],
confd_exchanges=rendered_data['configured_exchanges'],
selected_exchange=rendered_data['selected_exchange'],
indicator_types=rendered_data['indicator_types'],
indicator_list=rendered_data['indicator_list'],
checked=rendered_data['enabled_indicators'],
ma_vals=rendered_data['ma_vals'],
js_data=js_data,
active_trades=rendered_data['active_trades'],
open_orders=rendered_data['open_orders'])
@sock.route('/ws')
def ws(socket_conn):
"""
Open a websocket to handle two-way communication with UI without browser refreshes.
"""
def json_msg_received(msg_obj):
"""
Handle incoming json msg's.
"""
# Validate input
if 'message_type' not in msg_obj:
return
msg_type, msg_data = msg_obj['message_type'], msg_obj['data']
print(f'\n[MSG_RECEIVED]\nMSG Type:{msg_type}\n{msg_data}')
resp = brighter_trades.process_incoming_message(msg_type=msg_type, msg_data=msg_data)
socket_conn.send(json.dumps(resp))
return
# The rendered page connects to the exchange_interface and relays the candle data back here
# this socket also handles data and processing requests
while True:
msg = socket_conn.receive()
if msg:
# If in json format the message gets converted into a dictionary
# otherwise it is handled as a status signal from the client
try:
json_msg = json.loads(msg)
json_msg_received(json_msg)
except json.JSONDecodeError:
print(f'Msg received from client: {msg}')
@app.route('/settings', methods=['POST'])
def settings():
"""
This route is used to change settings that require a browser refresh.
Options are:
Data source options for the chart display:
timeframe: For setting the time frame the of the viewport.
trading_pair: For setting the market displayed in the viewport.
exchange_name: For setting the exchange_name displayed in the viewport.
Controls of the indicators:
new_indicator: Creates a new indicator and stores it.
edit_indicator: Edits the properties of specific indicators.
toggle_indicator: Enables or disables display of indicators in the viewport.
:return: None - Redirects to index.
"""
# Request the user_name from the client session.
if not (user_name := session.get('user')):
return redirect('/')
# Return if the user is not logged in.
if not brighter_trades.get_user_info(user_name=user_name, info='Is logged in?'):
return redirect('/')
# Get the setting that the web application wants to change.
if not (setting := request.form.get('setting')):
return redirect('/')
# Change the setting.
brighter_trades.adjust_setting(user_name=user_name, setting=setting, params=request.form)
return redirect('/')
@app.route('/api/history', methods=['POST', 'GET'])
def history():
"""
Fetches the candle history of a specific trading pair and timeframe.
Currently, set to last 1000 candles.
:return: json - price history
"""
try:
data = request.get_json()
if not data:
raise ValueError("No JSON data received")
username = data.get('user_name')
# Return if the user is not logged in.
if not username:
# redirect('/')
return jsonify({'error': 'User not logged in.'}), 401
print('[RECEIVED PRICE HISTORY REQUEST] - user', username)
# Get a dictionary of chart attributes from the user data indexed by user_name.
chart_view = brighter_trades.get_user_info(user_name=username, info='Chart View')
payload = brighter_trades.get_market_info(
info='Candle History', num_records=1000, chart_view=chart_view, user_name=username)
print('[SENDING PRICE HISTORY TO CLIENT]', payload.tail(2))
return jsonify(payload.to_dict('records')), 200
except Exception as e:
print(f'Error in history endpoint: {e}')
return jsonify({'error': str(e)}), 500
# @app.route('/saved_data')
# def saved_data():
# """Returns the saved settings for the indicators"""
# # Request the user_name from the client session.
# if not (user_name := session.get('user')):
# return redirect('/')
#
# # Return if the user is not logged in.
# if not brighter_trades.get_user_info(user_name=user_name, info='Is logged in?'):
# return redirect('/')
#
# return jsonify(brighter_trades.indicators.indicator_list)
@app.route('/signup')
def signup():
return render_template('sign_up.html', title='title')
@app.route('/signout')
def signout():
if not (user_name := session.get('user')):
return redirect('/')
if brighter_trades.log_user_in_out(user_name=user_name, cmd='logout'):
# If the user was logged out successfully delete the session var.
del session['user']
return redirect('/')
@app.route('/login', methods=['POST'])
def login():
# Get the user_name and password from the form data
username = request.form.get('user_name')
password = request.form.get('password')
# Validate the input
if not username or not password:
flash('Please provide both user_name and password.')
return redirect('/')
# Attempt to log in the user
success = brighter_trades.log_user_in_out(user_name=username, cmd='login', password=password)
if success:
# Store the user_name in the session
session['user'] = username
flash('Login successful!')
else:
flash('Invalid user_name or password.')
return redirect('/')
@app.route('/signup_submit', methods=['POST'])
def signup_submit():
email = request.form.get('email')
username = request.form.get('user_name')
password = request.form.get('password')
# Validate email format
try:
validate_email(email)
except EmailNotValidError as e:
flash(message=f"Invalid email format: {e}")
return None
# Validate user_name and password
if not username or not password:
flash(message="Missing user_name or password")
return None
# Create a new user
success = brighter_trades.create_new_user(email=email, username=username, password=password)
if success:
session['user'] = username
return redirect('/')
else:
flash(message="An error has occurred during the signup process.")
return None
@app.route('/api/indicator_init', methods=['POST', 'GET'])
def indicator_init():
"""
Initializes the indicators and returns the data for a given symbol and timeframe.
"""
data = request.get_json()
username = data.get('user_name')
if not username:
return jsonify({'error': 'Invalid user name.'}), 400
# Check if the user is logged in
if not brighter_trades.get_user_info(user_name=username, info='Is logged in?'):
return jsonify({'error': 'User is not logged in.'}), 401
# Get the chart view for the user
chart_view = brighter_trades.get_user_info(user_name=username, info='Chart View')
# Get the indicator data
source = {'user_name': username, 'market': chart_view}
data = brighter_trades.get_indicator_data(user_name=username, source=source, start_ts=None, num_results=1000)
# indicators={'EMA 5': {'visible': true, 'type': 'EMA', 'color': 'red' },'vol': {'visible': true, 'type': 'Volume'},'New Indicator': {'visible': true, 'type': 'nothing'}}
return jsonify(data), 200
if __name__ == '__main__':
app.run(debug=False, use_reloader=False)