diff --git a/.gitignore b/.gitignore index 27c75e0..a5fc406 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ my_example.py Chart.lock charts/ test-config.yaml +app.log diff --git a/brokers/base_broker.py b/brokers/base_broker.py index 35ef5b2..e829b70 100644 --- a/brokers/base_broker.py +++ b/brokers/base_broker.py @@ -2,8 +2,9 @@ from sqlalchemy.orm import sessionmaker from sqlalchemy.sql import and_ from database.db_manager import DBManager -from database.models import Trade, AccountInfo, Balance, Position +from database.models import Trade, AccountInfo, Position from datetime import datetime +from utils.logger import logger # Import the logger class BaseBroker(ABC): def __init__(self, api_key, secret_key, broker_name, engine, prevent_day_trading=False): @@ -13,7 +14,8 @@ def __init__(self, api_key, secret_key, broker_name, engine, prevent_day_trading self.db_manager = DBManager(engine) self.Session = sessionmaker(bind=engine) self.account_id = None - self.prevent_day_trading = False + self.prevent_day_trading = prevent_day_trading + logger.info('Initialized BaseBroker', extra={'broker_name': self.broker_name}) @abstractmethod def connect(self): @@ -44,114 +46,154 @@ def get_current_price(self, symbol): pass def get_account_info(self): - account_info = self._get_account_info() - self.db_manager.add_account_info(AccountInfo(broker=self.broker_name, value=account_info['value'])) - return account_info + try: + account_info = self._get_account_info() + self.db_manager.add_account_info(AccountInfo(broker=self.broker_name, value=account_info['value'])) + logger.info('Account information retrieved', extra={'account_info': account_info}) + return account_info + except Exception as e: + logger.error('Failed to get account information', extra={'error': str(e)}) + return None def has_bought_today(self, symbol): today = datetime.now().date() - with self.Session() as session: - trades = session.query(Trade).filter( - and_( - Trade.symbol == symbol, - Trade.broker == self.broker_name, - Trade.order_type == 'buy', - Trade.timestamp >= today - ) - ).all() - return len(trades) > 0 + try: + with self.Session() as session: + trades = session.query(Trade).filter( + and_( + Trade.symbol == symbol, + Trade.broker == self.broker_name, + Trade.order_type == 'buy', + Trade.timestamp >= today + ) + ).all() + logger.info('Checked for trades today', extra={'symbol': symbol, 'trade_count': len(trades)}) + return len(trades) > 0 + except Exception as e: + logger.error('Failed to check if bought today', extra={'error': str(e)}) + return False def update_positions(self, session, trade): - position = session.query(Position).filter_by(symbol=trade.symbol, broker=self.broker_name, strategy=trade.strategy).first() - - if trade.order_type == 'buy': - if position: - position.quantity += trade.quantity - position.latest_price = trade.executed_price - position.timestamp = datetime.now() - else: - position = Position( - broker=self.broker_name, - strategy=trade.strategy, - symbol=trade.symbol, - quantity=trade.quantity, - latest_price=trade.executed_price, - ) - session.add(position) - elif trade.order_type == 'sell': - if position: - position.quantity -= trade.quantity - position.latest_price = trade.executed_price - if position.quantity < 0: - raise ValueError("Sell quantity exceeds current position quantity.") - - session.commit() + try: + position = session.query(Position).filter_by(symbol=trade.symbol, broker=self.broker_name, strategy=trade.strategy).first() + + if trade.order_type == 'buy': + if position: + position.quantity += trade.quantity + position.latest_price = trade.executed_price + position.timestamp = datetime.now() + else: + position = Position( + broker=self.broker_name, + strategy=trade.strategy, + symbol=trade.symbol, + quantity=trade.quantity, + latest_price=trade.executed_price, + ) + session.add(position) + elif trade.order_type == 'sell': + if position: + position.quantity -= trade.quantity + position.latest_price = trade.executed_price + if position.quantity < 0: + logger.error('Sell quantity exceeds current position quantity', extra={'trade': trade}) + position.quantity = 0 # Set to 0 to handle the error gracefully + + session.commit() + logger.info('Position updated', extra={'position': position}) + except Exception as e: + logger.error('Failed to update positions', extra={'error': str(e)}) def place_order(self, symbol, quantity, order_type, strategy, price=None): - # Check for day trading + logger.info('Placing order', extra={'symbol': symbol, 'quantity': quantity, 'order_type': order_type, 'strategy': strategy}) + if self.prevent_day_trading and order_type == 'sell': if self.has_bought_today(symbol): - raise ValueError("Day trading is not allowed. Cannot sell positions opened today.") - - response = self._place_order(symbol, quantity, order_type, price) - - trade = Trade( - symbol=symbol, - quantity=quantity, - # TODO: remove redundant price - price=response['filled_price'], - executed_price=response['filled_price'], - order_type=order_type, - status='filled', - timestamp=datetime.now(), - broker=self.broker_name, - strategy=strategy, - profit_loss=0, - success='yes' - ) - - with self.Session() as session: - session.add(trade) - session.commit() - - # Update positions - self.update_positions(session, trade) - - return response + logger.error('Day trading is not allowed. Cannot sell positions opened today.', extra={'symbol': symbol}) + return None + + try: + response = self._place_order(symbol, quantity, order_type, price) + logger.info('Order placed successfully', extra={'response': response}) + + trade = Trade( + symbol=symbol, + quantity=quantity, + price=response.get('filled_price', price), + executed_price=response.get('filled_price', price), + order_type=order_type, + status='filled', + timestamp=datetime.now(), + broker=self.broker_name, + strategy=strategy, + profit_loss=0, + success='yes' + ) + + with self.Session() as session: + session.add(trade) + session.commit() + self.update_positions(session, trade) + + return response + except Exception as e: + logger.error('Failed to place order', extra={'error': str(e)}) + return None def get_order_status(self, order_id): - order_status = self._get_order_status(order_id) - with self.Session() as session: - trade = session.query(Trade).filter_by(id=order_id).first() - if trade: - self.update_trade(session, trade.id, order_status) - return order_status + logger.info('Retrieving order status', extra={'order_id': order_id}) + try: + order_status = self._get_order_status(order_id) + with self.Session() as session: + trade = session.query(Trade).filter_by(id=order_id).first() + if trade: + self.update_trade(session, trade.id, order_status) + logger.info('Order status retrieved', extra={'order_status': order_status}) + return order_status + except Exception as e: + logger.error('Failed to get order status', extra={'error': str(e)}) + return None def cancel_order(self, order_id): - cancel_status = self._cancel_order(order_id) - with self.Session() as session: - trade = session.query(Trade).filter_by(id=order_id).first() - if trade: - self.update_trade(session, trade.id, cancel_status) - return cancel_status + logger.info('Cancelling order', extra={'order_id': order_id}) + try: + cancel_status = self._cancel_order(order_id) + with self.Session() as session: + trade = session.query(Trade).filter_by(id=order_id).first() + if trade: + self.update_trade(session, trade.id, cancel_status) + logger.info('Order cancelled successfully', extra={'cancel_status': cancel_status}) + return cancel_status + except Exception as e: + logger.error('Failed to cancel order', extra={'error': str(e)}) + return None def get_options_chain(self, symbol, expiration_date): - return self._get_options_chain(symbol, expiration_date) + logger.info('Retrieving options chain', extra={'symbol': symbol, 'expiration_date': expiration_date}) + try: + options_chain = self._get_options_chain(symbol, expiration_date) + logger.info('Options chain retrieved', extra={'options_chain': options_chain}) + return options_chain + except Exception as e: + logger.error('Failed to retrieve options chain', extra={'error': str(e)}) + return None def update_trade(self, session, trade_id, order_info): - trade = session.query(Trade).filter_by(id=trade_id).first() - if not trade: - return - - executed_price = order_info.get('filled_price', trade.price) # Match the correct key - if executed_price is None: - executed_price = trade.price # Ensure we have a valid executed price - - trade.executed_price = executed_price - profit_loss = self.db_manager.calculate_profit_loss(trade) - success = "success" if profit_loss > 0 else "failure" - - trade.executed_price = executed_price - trade.success = success - trade.profit_loss = profit_loss - session.commit() + try: + trade = session.query(Trade).filter_by(id=trade_id).first() + if not trade: + logger.error('Trade not found for update', extra={'trade_id': trade_id}) + return + + executed_price = order_info.get('filled_price', trade.price) + trade.executed_price = executed_price + profit_loss = self.db_manager.calculate_profit_loss(trade) + success = "success" if profit_loss > 0 else "failure" + + trade.executed_price = executed_price + trade.success = success + trade.profit_loss = profit_loss + session.commit() + logger.info('Trade updated', extra={'trade': trade}) + except Exception as e: + logger.error('Failed to update trade', extra={'error': str(e)}) diff --git a/brokers/tradier_broker.py b/brokers/tradier_broker.py index 7f2b2fb..0bbef70 100644 --- a/brokers/tradier_broker.py +++ b/brokers/tradier_broker.py @@ -1,6 +1,7 @@ import requests import time from brokers.base_broker import BaseBroker +from utils.logger import logger # Import the logger class TradierBroker(BaseBroker): def __init__(self, api_key, secret_key, engine, **kwargs): @@ -12,138 +13,159 @@ def __init__(self, api_key, secret_key, engine, **kwargs): } self.order_timeout = 1 self.auto_cancel_orders = True + logger.info('Initialized TradierBroker', extra={'api_key': api_key, 'base_url': self.base_url}) - # TODO: remove def connect(self): + logger.info('Connecting to Tradier API') + # Placeholder for actual connection logic pass def _get_account_info(self): - # Implement account information retrieval - response = requests.get("https://api.tradier.com/v1/user/profile", headers=self.headers) - if response.status_code == 401: - raise ValueError("It seems we are having trouble authenticating to Tradier") - account_info = response.json() - account_id = account_info['profile']['account']['account_number'] - self.account_id = account_id - - # Get the balance info for the account - url = f'{self.base_url}/accounts/{self.account_id}/balances' - response = requests.get(url, headers=self.headers) - if response.status_code != 200: - raise Exception(f"Failed to get account info: {response.text}") - - account_info = response.json().get('balances') - if not account_info: - raise Exception("Invalid account info response") - - if account_info.get('cash'): - self.account_type = 'cash' - buying_power = account_info['cash']['cash_available'] - account_value = account_info['total_equity'] - if account_info.get('margin'): - self.account_type = 'margin' - buying_power = account_info['margin']['stock_buying_power'] - account_value = account_info['total_equity'] - if account_info.get('pdt'): - self.account_type = 'pdt' - buying_power = account_info['pdt']['stock_buying_power'] - - return { - 'account_number': account_info['account_number'], - 'account_type': self.account_type, - 'buying_power': buying_power, - 'value': account_value - } + logger.info('Retrieving account information') + try: + response = requests.get("https://api.tradier.com/v1/user/profile", headers=self.headers) + response.raise_for_status() + account_info = response.json() + account_id = account_info['profile']['account']['account_number'] + self.account_id = account_id + logger.info('Account info retrieved', extra={'account_id': self.account_id}) + + url = f'{self.base_url}/accounts/{self.account_id}/balances' + response = requests.get(url, headers=self.headers) + response.raise_for_status() + account_info = response.json().get('balances') + + if not account_info: + logger.error("Invalid account info response") + + if account_info.get('cash'): + self.account_type = 'cash' + buying_power = account_info['cash']['cash_available'] + account_value = account_info['total_equity'] + if account_info.get('margin'): + self.account_type = 'margin' + buying_power = account_info['margin']['stock_buying_power'] + account_value = account_info['total_equity'] + if account_info.get('pdt'): + self.account_type = 'pdt' + buying_power = account_info['pdt']['stock_buying_power'] + + logger.info('Account balances retrieved', extra={'account_type': self.account_type, 'buying_power': buying_power, 'value': account_value}) + return { + 'account_number': account_info['account_number'], + 'account_type': self.account_type, + 'buying_power': buying_power, + 'value': account_value + } + except requests.RequestException as e: + logger.error('Failed to retrieve account information', extra={'error': str(e)}) def get_positions(self): + logger.info('Retrieving positions') url = f"{self.base_url}/accounts/{self.account_id}/positions" - response = requests.get(url, headers=self.headers) - - if response.status_code == 200: + try: + response = requests.get(url, headers=self.headers) + response.raise_for_status() positions_data = response.json()['positions']['position'] - # Singular dict response + if type(positions_data) != list: positions_data = [positions_data] positions = {p['symbol']: p for p in positions_data} + logger.info('Positions retrieved', extra={'positions': positions}) return positions - else: - response.raise_for_status() + except requests.RequestException as e: + logger.error('Failed to retrieve positions', extra={'error': str(e)}) def _place_order(self, symbol, quantity, order_type, price=None): - # Retrieve the current quote to get the bid/ask prices - quote_url = f"https://api.tradier.com/v1/markets/quotes?symbols={symbol}" - quote_response = requests.get(quote_url, headers=self.headers) - if quote_response.status_code != 200: - raise Exception(f"Failed to get quote: {quote_response.text}") - - quote = quote_response.json()['quotes']['quote'] - bid = quote['bid'] - ask = quote['ask'] - - # Use the median of the bid/ask spread as the limit price if none is provided - if price is None: - price = round((bid + ask) / 2, 2) - - # Prepare order data - order_data = { - "class": "equity", - "symbol": symbol, - "quantity": quantity, - "side": order_type, # 'buy' or 'sell' - "type": "limit", # Using limit order type - "duration": "day", # Immediate or Cancel - "price": price - } - - # Make the API call to place the order - response = requests.post(f"https://api.tradier.com/v1/accounts/{self.account_id}/orders", data=order_data, headers=self.headers) - - # Check for success or raise an exception - if response.status_code > 400: - print(f"Failed to place order: {response.text}") - return {} - - order_id = response.json()['order']['id'] - if self.auto_cancel_orders: - # Wait for a short period to check if the order gets filled - time.sleep(self.order_timeout) - - # Check the order status - order_status_url = f"https://api.tradier.com/v1/accounts/{self.account_id}/orders/{order_id}" - status_response = requests.get(order_status_url, headers=self.headers) - if status_response.status_code != 200: - raise Exception(f"Failed to get order status: {status_response.text}") - - order_status = status_response.json()['order']['status'] - - # try to Cancel the order if it's not filled (might have gotten filled by now) - if order_status != 'filled': - cancel_url = f"https://api.tradier.com/v1/accounts/{self.account_id}/orders/{order_id}/cancel" - cancel_response = requests.put(cancel_url, headers=self.headers) + logger.info('Placing order', extra={'symbol': symbol, 'quantity': quantity, 'order_type': order_type, 'price': price}) + try: + quote_url = f"https://api.tradier.com/v1/markets/quotes?symbols={symbol}" + quote_response = requests.get(quote_url, headers=self.headers) + quote_response.raise_for_status() + quote = quote_response.json()['quotes']['quote'] + bid = quote['bid'] + ask = quote['ask'] + + if price is None: + price = round((bid + ask) / 2, 2) + + order_data = { + "class": "equity", + "symbol": symbol, + "quantity": quantity, + "side": order_type, + "type": "limit", + "duration": "day", + "price": price + } + + response = requests.post(f"{self.base_url}/accounts/{self.account_id}/orders", data=order_data, headers=self.headers) + response.raise_for_status() - # Return the response in JSON format - data = response.json() - if data.get('filled_price') is None: - data['filled_price'] = price - return data + order_id = response.json()['order']['id'] + logger.info('Order placed', extra={'order_id': order_id}) + + if self.auto_cancel_orders: + time.sleep(self.order_timeout) + order_status_url = f"{self.base_url}/accounts/{self.account_id}/orders/{order_id}" + status_response = requests.get(order_status_url, headers=self.headers) + status_response.raise_for_status() + order_status = status_response.json()['order']['status'] + + if order_status != 'filled': + cancel_url = f"{self.base_url}/accounts/{self.account_id}/orders/{order_id}/cancel" + cancel_response = requests.put(cancel_url, headers=self.headers) + cancel_response.raise_for_status() + logger.info('Order cancelled', extra={'order_id': order_id}) + + data = response.json() + if data.get('filled_price') is None: + data['filled_price'] = price + logger.info('Order execution complete', extra={'order_data': data}) + return data + except requests.RequestException as e: + logger.error('Failed to place order', extra={'error': str(e)}) def _get_order_status(self, order_id): - # Implement order status retrieval - response = requests.get(f"https://api.tradier.com/v1/accounts/orders/{order_id}", headers=self.headers) - return response.json() + logger.info('Retrieving order status', extra={'order_id': order_id}) + try: + response = requests.get(f"{self.base_url}/accounts/orders/{order_id}", headers=self.headers) + response.raise_for_status() + order_status = response.json() + logger.info('Order status retrieved', extra={'order_status': order_status}) + return order_status + except requests.RequestException as e: + logger.error('Failed to retrieve order status', extra={'error': str(e)}) def _cancel_order(self, order_id): - # Implement order cancellation - response = requests.delete(f"https://api.tradier.com/v1/accounts/orders/{order_id}", headers=self.headers) - return response.json() + logger.info('Cancelling order', extra={'order_id': order_id}) + try: + response = requests.delete(f"{self.base_url}/accounts/orders/{order_id}", headers=self.headers) + response.raise_for_status() + cancellation_response = response.json() + logger.info('Order cancelled successfully', extra={'cancellation_response': cancellation_response}) + return cancellation_response + except requests.RequestException as e: + logger.error('Failed to cancel order', extra={'error': str(e)}) def _get_options_chain(self, symbol, expiration_date): - # Implement options chain retrieval - response = requests.get(f"https://api.tradier.com/v1/markets/options/chains?symbol={symbol}&expiration={expiration_date}", headers=self.headers) - return response.json() + logger.info('Retrieving options chain', extra={'symbol': symbol, 'expiration_date': expiration_date}) + try: + response = requests.get(f"{self.base_url}/markets/options/chains?symbol={symbol}&expiration={expiration_date}", headers=self.headers) + response.raise_for_status() + options_chain = response.json() + logger.info('Options chain retrieved', extra={'options_chain': options_chain}) + return options_chain + except requests.RequestException as e: + logger.error('Failed to retrieve options chain', extra={'error': str(e)}) def get_current_price(self, symbol): - # Implement current price retrieval - response = requests.get(f"https://api.tradier.com/v1/markets/quotes?symbols={symbol}", headers=self.headers) - last_price = response.json().get('quotes').get('quote').get('last') - return last_price + logger.info('Retrieving current price', extra={'symbol': symbol}) + try: + response = requests.get(f"{self.base_url}/markets/quotes?symbols={symbol}", headers=self.headers) + response.raise_for_status() + last_price = response.json().get('quotes').get('quote').get('last') + logger.info('Current price retrieved', extra={'symbol': symbol, 'last_price': last_price}) + return last_price + except requests.RequestException as e: + logger.error('Failed to retrieve current price', extra={'error': str(e)}) diff --git a/database/db_manager.py b/database/db_manager.py index 148066e..ca76f83 100644 --- a/database/db_manager.py +++ b/database/db_manager.py @@ -1,75 +1,88 @@ import json -from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from .models import Base, Trade, AccountInfo - -DATABASE_URL = "sqlite:///trades.db" - -engine = create_engine(DATABASE_URL) -Session = sessionmaker(bind=engine) +from utils.logger import logger class DBManager: def __init__(self, engine): self.Session = sessionmaker(bind=engine) + logger.info('DBManager initialized', extra={'database_url': engine.url}) def add_account_info(self, account_info): - with self.Session() as session: + session = self.Session() + try: + logger.info('Adding account info', extra={'account_info': account_info}) existing_info = session.query(AccountInfo).filter_by(broker=account_info.broker).first() if existing_info: existing_info.value = account_info.value + logger.info('Updated existing account info', extra={'account_info': account_info}) else: session.add(account_info) - session.commit() - - def add_account_info(self, account_info): - session = self.Session() - try: - existing_info = session.query(AccountInfo).first() - if existing_info: - session.delete(existing_info) - session.commit() - session.add(account_info) + logger.info('Added new account info', extra={'account_info': account_info}) session.commit() except Exception as e: session.rollback() - raise e + logger.error('Failed to add account info', extra={'error': str(e)}) finally: session.close() def get_trade(self, trade_id): session = self.Session() try: - return session.query(Trade).filter_by(id=trade_id).first() + logger.info('Retrieving trade', extra={'trade_id': trade_id}) + trade = session.query(Trade).filter_by(id=trade_id).first() + logger.info('Trade retrieved', extra={'trade': trade}) + return trade + except Exception as e: + logger.error('Failed to retrieve trade', extra={'error': str(e)}) + return None finally: session.close() def get_all_trades(self): session = self.Session() try: - return session.query(Trade).all() + logger.info('Retrieving all trades') + trades = session.query(Trade).all() + logger.info('All trades retrieved', extra={'trade_count': len(trades)}) + return trades + except Exception as e: + logger.error('Failed to retrieve all trades', extra={'error': str(e)}) + return [] finally: session.close() def calculate_profit_loss(self, trade): - current_price = trade.executed_price - if current_price is None: - raise ValueError("Executed price is None, cannot calculate profit/loss") - if trade.order_type.lower() == 'buy': - return (current_price - trade.price) * trade.quantity - elif trade.order_type.lower() == 'sell': - return (trade.price - current_price) * trade.quantity + try: + logger.info('Calculating profit/loss', extra={'trade': trade}) + current_price = trade.executed_price + if current_price is None: + logger.error('Executed price is None, cannot calculate profit/loss', extra={'trade': trade}) + return None + + if trade.order_type.lower() == 'buy': + profit_loss = (current_price - trade.price) * trade.quantity + elif trade.order_type.lower() == 'sell': + profit_loss = (trade.price - current_price) * trade.quantity + logger.info('Profit/loss calculated', extra={'trade': trade, 'profit_loss': profit_loss}) + return profit_loss + except Exception as e: + logger.error('Failed to calculate profit/loss', extra={'error': str(e)}) + return None def update_trade_status(self, trade_id, executed_price, success, profit_loss): session = self.Session() try: + logger.info('Updating trade status', extra={'trade_id': trade_id, 'executed_price': executed_price, 'success': success, 'profit_loss': profit_loss}) trade = session.query(Trade).filter_by(id=trade_id).first() if trade: trade.executed_price = executed_price trade.success = success trade.profit_loss = profit_loss session.commit() + logger.info('Trade status updated', extra={'trade': trade}) except Exception as e: session.rollback() - raise e + logger.error('Failed to update trade status', extra={'error': str(e)}) finally: session.close() diff --git a/main.py b/main.py index c1822dc..bb0f6c7 100644 --- a/main.py +++ b/main.py @@ -5,59 +5,112 @@ from ui.app import create_app from utils.config import parse_config, initialize_brokers, initialize_strategies from sqlalchemy import create_engine +from utils.logger import logger # Import the logger def start_trading_system(config_path): + logger.info('Starting the trading system', extra={'config_path': config_path}) + # Parse the configuration file - config = parse_config(config_path) - + try: + config = parse_config(config_path) + logger.info('Configuration parsed successfully') + except Exception as e: + logger.error('Failed to parse configuration', extra={'error': str(e)}) + return + # Initialize the brokers - brokers = initialize_brokers(config) + try: + brokers = initialize_brokers(config) + logger.info('Brokers initialized successfully') + except Exception as e: + logger.error('Failed to initialize brokers', extra={'error': str(e)}) + return + # Setup the database engine if 'database' in config and 'url' in config['database']: engine = create_engine(config['database']['url']) else: engine = create_engine('sqlite:///default_trading_system.db') - + logger.info('Database engine created', extra={'db_url': engine.url}) + # Initialize the database - init_db(engine) - + try: + init_db(engine) + logger.info('Database initialized successfully') + except Exception as e: + logger.error('Failed to initialize database', extra={'error': str(e)}) + return + # Connect to each broker - for broker in brokers.values(): - broker.connect() - + for broker_name, broker in brokers.items(): + try: + broker.connect() + logger.info(f'Connected to broker {broker_name}') + except Exception as e: + logger.error(f'Failed to connect to broker {broker_name}', extra={'error': str(e)}) + # Initialize the strategies - strategies = initialize_strategies(brokers, config) - + try: + strategies = initialize_strategies(brokers, config) + logger.info('Strategies initialized successfully') + except Exception as e: + logger.error('Failed to initialize strategies', extra={'error': str(e)}) + return + # Execute the strategies loop rebalance_intervals = [timedelta(minutes=s.rebalance_interval_minutes) for s in strategies] last_rebalances = [datetime.min for _ in strategies] - + logger.info('Entering the strategies execution loop') + while True: now = datetime.now() for i, strategy in enumerate(strategies): if now - last_rebalances[i] >= rebalance_intervals[i]: - strategy.rebalance() - last_rebalances[i] = now + try: + strategy.rebalance() + last_rebalances[i] = now + logger.info(f'Strategy {i} rebalanced successfully', extra={'time': now}) + except Exception as e: + logger.error(f'Error during rebalancing strategy {i}', extra={'error': str(e)}) time.sleep(60) # Check every minute def start_api_server(config_path=None, local_testing=False): + logger.info('Starting API server', extra={'config_path': config_path, 'local_testing': local_testing}) + if config_path is None: config = {} else: - config = parse_config(config_path) + try: + config = parse_config(config_path) + logger.info('Configuration parsed successfully for API server') + except Exception as e: + logger.error('Failed to parse configuration for API server', extra={'error': str(e)}) + return + # Setup the database engine if local_testing: engine = create_engine('sqlite:///trading.db') elif 'database' in config and 'url' in config['database']: engine = create_engine(config['database']['url']) else: engine = create_engine('sqlite:///default_trading_system.db') + logger.info('Database engine created for API server', extra={'db_url': engine.url}) # Initialize the database - init_db(engine) + try: + init_db(engine) + logger.info('Database initialized successfully for API server') + except Exception as e: + logger.error('Failed to initialize database for API server', extra={'error': str(e)}) + return - app = create_app(engine) - app.run(host="0.0.0.0", port=8000, debug=True) + # Create and run the app + try: + app = create_app(engine) + logger.info('API server created successfully') + app.run(host="0.0.0.0", port=8000, debug=True) + except Exception as e: + logger.error('Failed to start API server', extra={'error': str(e)}) def main(): parser = argparse.ArgumentParser(description="Run trading strategies or start API server based on YAML configuration.") diff --git a/requirements.txt b/requirements.txt index 964473c..db272b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ pytest sqlalchemy pyyaml flask +python-json-logger diff --git a/utils/logger.py b/utils/logger.py index e69de29..f228a94 100644 --- a/utils/logger.py +++ b/utils/logger.py @@ -0,0 +1,28 @@ +# utils/json_logger.py +import logging +import os +from pythonjsonlogger import jsonlogger + +class JsonLogger: + def __init__(self, log_file='app.log'): + self.logger = logging.getLogger(__name__) + self.logger.setLevel(logging.DEBUG) + + # Create handlers + file_handler = logging.FileHandler(log_file) + console_handler = logging.StreamHandler() + + # Create formatters and add it to handlers + formatter = jsonlogger.JsonFormatter('%(asctime)s %(name)s %(levelname)s %(message)s') + file_handler.setFormatter(formatter) + console_handler.setFormatter(formatter) + + # Add handlers to the logger + self.logger.addHandler(file_handler) + self.logger.addHandler(console_handler) + + def get_logger(self): + return self.logger + +# Create a logger instance +logger = JsonLogger().get_logger()