Fix chart loading and stabilize local runtime
This commit is contained in:
parent
ee51ab1d8c
commit
f1a184b131
|
|
@ -0,0 +1,24 @@
|
|||
# Keep Claude Code focused on project source and tests.
|
||||
# Exclude large local artifacts that can cause indexing slowdowns/freezes.
|
||||
|
||||
.git/
|
||||
.idea/
|
||||
.pytest_cache/
|
||||
__pycache__/
|
||||
flask_session/
|
||||
venv/
|
||||
.venv/
|
||||
|
||||
# Local project data and databases
|
||||
data/
|
||||
BrighterTrading.db
|
||||
|
||||
# Symlink to external docs repo (outside this project)
|
||||
docs/
|
||||
docs
|
||||
|
||||
# Local Claude settings
|
||||
.claude/
|
||||
|
||||
# Large vendored Blockly source checkout
|
||||
src/static/blockly/
|
||||
|
|
@ -14,10 +14,12 @@ data/*.db
|
|||
# Ignore configuration files
|
||||
config/
|
||||
config.py
|
||||
src/config.py
|
||||
config.yml
|
||||
|
||||
# Ignore virtual environments
|
||||
venv/
|
||||
.venv/
|
||||
ENV/
|
||||
env/
|
||||
|
||||
|
|
@ -32,3 +34,13 @@ __pycache__/
|
|||
# Ignore system files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Ignore local AI/tooling settings
|
||||
.claude/
|
||||
|
||||
# Ignore local symlinked docs from centralized docs repo
|
||||
docs/
|
||||
docs
|
||||
|
||||
# Ignore local Blockly vendor checkout (large source + node_modules)
|
||||
src/static/blockly/
|
||||
|
|
|
|||
|
|
@ -1471,7 +1471,16 @@ class ServerInteractions(DatabaseInteractions):
|
|||
raise ValueError('Not a valid Target!')
|
||||
|
||||
for fetch_method in resources:
|
||||
try:
|
||||
result = fetch_method(**kwargs)
|
||||
except Exception as e:
|
||||
logger.error(f"Fetch step '{fetch_method.__name__}' failed: {e}")
|
||||
if not combined_data.empty:
|
||||
logger.warning(
|
||||
"Returning partial candle history from cache/database because a downstream fetch failed."
|
||||
)
|
||||
return combined_data.reset_index(drop=True)
|
||||
continue
|
||||
|
||||
if result is not None and not result.empty:
|
||||
# Drop the 'id' column if it exists in the result
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ class Database:
|
|||
:param table_name: The name of the table.
|
||||
:return: DataFrame containing all rows.
|
||||
"""
|
||||
sql_query = f"SELECT * FROM {table_name}"
|
||||
sql_query = f'SELECT * FROM "{table_name}"'
|
||||
with SQLite(self.db_file) as conn:
|
||||
return pd.read_sql(sql_query, conn)
|
||||
|
||||
|
|
|
|||
|
|
@ -57,15 +57,20 @@ class Exchange:
|
|||
'apiKey': self.api_key,
|
||||
'secret': self.api_key_secret,
|
||||
'enableRateLimit': True,
|
||||
'verbose': False
|
||||
'verbose': False,
|
||||
'options': {'warnOnFetchOpenOrdersWithoutSymbol': False}
|
||||
})
|
||||
else:
|
||||
return exchange_class({
|
||||
'enableRateLimit': True,
|
||||
'verbose': False
|
||||
'verbose': False,
|
||||
'options': {'warnOnFetchOpenOrdersWithoutSymbol': False}
|
||||
})
|
||||
|
||||
def _check_authentication(self):
|
||||
if not (self.api_key and self.api_key_secret):
|
||||
self.configured = False
|
||||
return
|
||||
try:
|
||||
self.client.fetch_open_orders() # Much faster than fetch_balance
|
||||
self.configured = True
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import logging
|
||||
import sqlite3
|
||||
from typing import List, Any, Dict, TYPE_CHECKING
|
||||
import pandas as pd
|
||||
import ccxt
|
||||
import config
|
||||
from Exchange import Exchange
|
||||
from DataCache_v3 import DataCache
|
||||
|
||||
|
|
@ -27,12 +29,47 @@ class ExchangeInterface:
|
|||
eviction_policy='deny',
|
||||
columns=['user', 'name', 'reference', 'balances']
|
||||
)
|
||||
self.exchange_data_db_columns = self._ensure_exchange_data_schema()
|
||||
|
||||
self.available_exchanges = self.get_ccxt_exchanges()
|
||||
|
||||
self.default_ex_name = 'binance'
|
||||
self.default_exchange = None
|
||||
|
||||
@staticmethod
|
||||
def _ensure_exchange_data_schema() -> set[str]:
|
||||
"""
|
||||
Ensure the on-disk exchange_data table has columns required by current code.
|
||||
This handles older migrated databases that stored `saved_state` instead of
|
||||
`reference`.
|
||||
"""
|
||||
try:
|
||||
with sqlite3.connect(config.DB_FILE) as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute("PRAGMA table_info(exchange_data)")
|
||||
table_info = cur.fetchall()
|
||||
if not table_info:
|
||||
return set()
|
||||
|
||||
cols = {row[1] for row in table_info}
|
||||
|
||||
if 'reference' not in cols:
|
||||
cur.execute("ALTER TABLE exchange_data ADD COLUMN reference BLOB")
|
||||
if 'saved_state' in cols:
|
||||
cur.execute(
|
||||
"UPDATE exchange_data SET reference = saved_state WHERE reference IS NULL"
|
||||
)
|
||||
|
||||
if 'balances' not in cols:
|
||||
cur.execute("ALTER TABLE exchange_data ADD COLUMN balances BLOB")
|
||||
|
||||
conn.commit()
|
||||
cur.execute("PRAGMA table_info(exchange_data)")
|
||||
return {row[1] for row in cur.fetchall()}
|
||||
except Exception as e:
|
||||
logger.warning("exchange_data schema check skipped: %s", e)
|
||||
return set()
|
||||
|
||||
def connect_default_exchange(self):
|
||||
if self.default_exchange is not None:
|
||||
return
|
||||
|
|
@ -86,9 +123,18 @@ class ExchangeInterface:
|
|||
:param exchange: The Exchange object to add.
|
||||
"""
|
||||
try:
|
||||
row = pd.DataFrame([{
|
||||
'user': user_name, 'name': exchange.name,
|
||||
'reference': exchange, 'balances': exchange.balances}])
|
||||
row_data = {
|
||||
'user': user_name,
|
||||
'name': exchange.name,
|
||||
'reference': exchange,
|
||||
'balances': exchange.balances
|
||||
}
|
||||
# Backwards compatibility for migrated databases that still enforce
|
||||
# a NOT NULL legacy `saved_state` column.
|
||||
if 'saved_state' in self.exchange_data_db_columns:
|
||||
row_data['saved_state'] = exchange
|
||||
|
||||
row = pd.DataFrame([row_data])
|
||||
|
||||
self.cache_manager.serialized_datacache_insert(cache_name='exchange_data', data=row)
|
||||
except Exception as e:
|
||||
|
|
|
|||
10
src/Users.py
10
src/Users.py
|
|
@ -2,7 +2,7 @@ import copy
|
|||
import datetime as dt
|
||||
import json
|
||||
import random
|
||||
from passlib.hash import bcrypt
|
||||
from passlib.hash import bcrypt, pbkdf2_sha256
|
||||
import pandas as pd
|
||||
from typing import Any
|
||||
from DataCache_v3 import DataCache
|
||||
|
|
@ -174,11 +174,15 @@ class UserAccountManagement(BaseUser):
|
|||
if not hashed_pass:
|
||||
return False
|
||||
|
||||
# Verify the password using bcrypt
|
||||
# Verify the password using the legacy or current hash format.
|
||||
# Some existing records (e.g., default guest) use pbkdf2_sha256 while
|
||||
# newer accounts are created with bcrypt.
|
||||
try:
|
||||
return bcrypt.verify(password, hashed_pass)
|
||||
except ValueError:
|
||||
# Handle any issues with the verification process
|
||||
try:
|
||||
return pbkdf2_sha256.verify(password, hashed_pass)
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def log_in_user(self, username: str, password: str) -> bool:
|
||||
|
|
|
|||
12
src/app.py
12
src/app.py
|
|
@ -6,6 +6,7 @@ eventlet.monkey_patch() # noqa: E402
|
|||
|
||||
# Standard library imports
|
||||
import logging # noqa: E402
|
||||
import os # noqa: E402
|
||||
# import json # noqa: E402
|
||||
# import datetime as dt # noqa: E402
|
||||
|
||||
|
|
@ -19,7 +20,11 @@ from email_validator import validate_email, EmailNotValidError # noqa: E402
|
|||
from BrighterTrades import BrighterTrades # noqa: E402
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
log_level_name = os.getenv('BRIGHTER_LOG_LEVEL', 'INFO').upper()
|
||||
log_level = getattr(logging, log_level_name, logging.INFO)
|
||||
logging.basicConfig(level=log_level)
|
||||
logging.getLogger('ccxt.base.exchange').setLevel(logging.WARNING)
|
||||
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
||||
|
||||
# Create a Flask object named app that serves the html.
|
||||
app = Flask(__name__)
|
||||
|
|
@ -68,7 +73,7 @@ def index():
|
|||
raise
|
||||
else:
|
||||
flash('The system is too busy for another user.')
|
||||
return
|
||||
return redirect('/signup')
|
||||
|
||||
# Ensure client session is assigned a user_name.
|
||||
session['user'] = user_name
|
||||
|
|
@ -341,5 +346,4 @@ def indicator_init():
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
socketio.run(app, host='127.0.0.1', port=5000, debug=False, use_reloader=False)
|
||||
|
||||
socketio.run(app, host='127.0.0.1', port=5002, debug=False, use_reloader=False)
|
||||
|
|
|
|||
|
|
@ -57,6 +57,16 @@ class Candles:
|
|||
# Fetch records older than start_datetime.
|
||||
candles = self.data.get_records_since(start_datetime=start_datetime,
|
||||
ex_details=[asset, timeframe, exchange, user_name])
|
||||
|
||||
# If there are no recent records, fall back to the latest local candles in the market table.
|
||||
if candles.empty:
|
||||
try:
|
||||
table_name = self.data._make_key([asset, timeframe, exchange, user_name])
|
||||
if self.data.db.table_exists(table_name):
|
||||
candles = self.data.db.get_all_rows(table_name).sort_values(by='time').reset_index(drop=True)
|
||||
except Exception as e:
|
||||
print(f"candles[64]: Failed local fallback load for {asset} {timeframe} {exchange}: {e}")
|
||||
|
||||
if len(candles.index) < num_candles:
|
||||
timesince = dt.datetime.now(pytz.UTC) - start_datetime
|
||||
minutes_since = int(timesince.total_seconds() / 60)
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
BINANCE_API_KEY = 'rkp1Xflb5nnwt6jys0PG27KXcqwn0q9lKCLryKcSp4mKW2UOlkPRuAHPg45rQVgj'
|
||||
BINANCE_API_SECRET = 'DiFhhYhF64nkPe5f3V7TRJX2bSVA7ZQZlozSdX7O7uYmBMdK985eA6Kp2B2zKvbK'
|
||||
ALPACA_API_KEY = 'PKE4RD999SJ8L53OUI8O'
|
||||
ALPACA_API_SECRET = 'buwlMoSSfZWGih8Er30quQt4d7brsBWdJXD1KB7C'
|
||||
DB_FILE = "C:/Users/Rob/PycharmProjects/BrighterTrading/data/BrighterTrading.db"
|
||||
|
|
@ -19,6 +19,7 @@ class Comms {
|
|||
|
||||
// Save the userName
|
||||
this.userName = userName;
|
||||
this.baseUrl = window.location.origin;
|
||||
|
||||
// Initialize the socket
|
||||
this._initializeSocket();
|
||||
|
|
@ -29,7 +30,7 @@ class Comms {
|
|||
*/
|
||||
_initializeSocket() {
|
||||
// Initialize Socket.IO client with query parameter
|
||||
this.socket = io('http://127.0.0.1:5000', {
|
||||
this.socket = io(this.baseUrl, {
|
||||
query: { 'user_name': this.userName },
|
||||
transports: ['websocket'], // Optional: Force WebSocket transport
|
||||
autoConnect: true,
|
||||
|
|
@ -164,7 +165,7 @@ class Comms {
|
|||
*/
|
||||
async getPriceHistory(userName) {
|
||||
try {
|
||||
const response = await fetch('http://127.0.0.1:5000/api/history', {
|
||||
const response = await fetch(`${this.baseUrl}/api/history`, {
|
||||
credentials: 'include',
|
||||
mode: 'cors',
|
||||
method: 'POST',
|
||||
|
|
@ -194,7 +195,7 @@ class Comms {
|
|||
*/
|
||||
async getIndicatorData(userName) {
|
||||
try {
|
||||
const response = await fetch('http://127.0.0.1:5000/api/indicator_init', { // Changed to use same host
|
||||
const response = await fetch(`${this.baseUrl}/api/indicator_init`, {
|
||||
credentials: 'same-origin',
|
||||
mode: 'cors',
|
||||
method: 'POST',
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class Data {
|
|||
// The assets being traded.
|
||||
this.trading_pair = bt_data.trading_pair;
|
||||
// The time period of the charts.
|
||||
this.interval = bt_data.interval;
|
||||
this.interval = bt_data.timeframe || bt_data.interval || '5m';
|
||||
// All the indicators available.
|
||||
this.indicators = bt_data.indicators;
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
<script src="{{ url_for('static', filename='exchanges.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='user.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='Alerts.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='strategies.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='Strategies.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='data.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='indicators.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='charts.js') }}"></script>
|
||||
|
|
|
|||
Loading…
Reference in New Issue