diff --git a/.gitignore b/.gitignore index f78c89a9..8ad17ff7 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,5 @@ airflow_home .env env node_modules/* -Honey.txt \ No newline at end of file +Honey.txt +.venv/ \ No newline at end of file diff --git a/chess_piece/king.py b/chess_piece/king.py index eba9a66e..08f04165 100644 --- a/chess_piece/king.py +++ b/chess_piece/king.py @@ -37,7 +37,7 @@ def hive_master_root_db(info='\pollen\pollen\db'): main_root = hive_master_root() # os.getcwd() load_dotenv(os.path.join(main_root, ".env")) -pg_migration = os.getenv('pg_migration') +pg_migration = os.getenv('pg_migration', 'False').lower() == 'true' def get_ip_address(): @@ -430,6 +430,11 @@ def PickleData(pickle_file, data_to_store, write_temp=False, console=True): if not pickle_file.endswith('.pkl'): pickle_file = pickle_file + ".pkl" if pickle_file: + # Ensure the directory exists + directory = os.path.dirname(pickle_file) + if directory and not os.path.exists(directory): + os.makedirs(directory, exist_ok=True) + if write_temp: root, name = os.path.split(pickle_file) pickle_file_temp = os.path.join(root, ("temp" + name)) @@ -745,4 +750,3 @@ def send_email( return True #### #### if __name__ == '__main__' ### - diff --git a/chess_piece/queen_bee.py b/chess_piece/queen_bee.py index d5470d35..58f0afa2 100644 --- a/chess_piece/queen_bee.py +++ b/chess_piece/queen_bee.py @@ -50,9 +50,12 @@ return_Ticker_Universe, refresh_broker_account_portolfio, ) -from chess_piece.queen_mind import refresh_chess_board__revrec, weight_team_keys, kings_order_rules +from chess_piece.queen_mind import refresh_chess_board__revrec, weight_team_keys from chess_piece.pollen_db import PollenDatabase, PollenJsonEncoder, PollenJsonDecoder -from chess_utils.robinhood_crypto_utils import CryptoAPITrading +try: + from chess_utils.robinhood_crypto_utils import CryptoAPITrading +except ImportError: + CryptoAPITrading = None import copy from tqdm import tqdm @@ -581,17 +584,9 @@ def fix_crypto_ticker(QUEEN, ticker, idx): # order manage return ticker -def update_origin_order_cost_basis_current(QUEEN, queen_order_idx): - av_qty = float(QUEEN['queen_orders'].at[queen_order_idx, 'qty_available']) - avg_price_fill = float(QUEEN['queen_orders'].at[queen_order_idx, 'filled_avg_price']) - if av_qty > 0: - QUEEN['queen_orders'].at[queen_order_idx, 'cost_basis_current'] = av_qty * avg_price_fill - - return QUEEN['queen_orders'].loc[queen_order_idx].to_dict() - def update_latest_queen_order_status(QUEEN, order_status, queen_order_idx): # updates qty and cost basis from Alpaca # WORKERBEE fix what if any columns neseccary to bring in? Maybe use run_order columns from createQueenBeeOrder? & handle Robinhood/others? - + broker_cols = broker_orders_fields() for order_key, order_value in order_status.items(): QUEEN['queen_orders'].at[queen_order_idx, order_key] = order_value # if order_key in broker_cols: @@ -601,8 +596,11 @@ def update_latest_queen_order_status(QUEEN, order_status, queen_order_idx): # up if order_status['filled_avg_price'] is not None: QUEEN['queen_orders'].at[queen_order_idx, 'filled_avg_price'] = float(order_status['filled_avg_price']) QUEEN['queen_orders'].at[queen_order_idx, 'cost_basis'] = float(order_status['filled_qty']) * float(order_status['filled_avg_price']) - update_origin_order_cost_basis_current(QUEEN, queen_order_idx) - + av_qty = float(QUEEN['queen_orders'].at[queen_order_idx, 'qty_available']) + avg_price_fill = float(QUEEN['queen_orders'].at[queen_order_idx, 'filled_avg_price']) + if av_qty > 0: + QUEEN['queen_orders'].at[queen_order_idx, 'cost_basis_current'] = av_qty * avg_price_fill + return QUEEN['queen_orders'].loc[queen_order_idx].to_dict() @@ -632,8 +630,12 @@ def update_runCLOSE__queen_order_honey(QUEEN, queen_order, origin_order, queen_o def update_origin_orders_profits(QUEEN, queen_order, origin_order, origin_order_idx): # updated origin Trade orders profits # origin order + # origin_order_cost_basis__qorder = float(queen_order['filled_qty']) * float(origin_order['filled_avg_price']) origin_order_cost_basis__qorder = origin_order.get('cost_basis') - + origin_filled_qty = float(queen_order['filled_qty']) + queen_order_cost_basis = origin_filled_qty * float(queen_order['filled_avg_price']) + # queen_order_cost_basis__to_origin_order = queen_order_cost_basis - origin_order_cost_basis__qorder + # closing_orders_cost_basis origin_closing_orders_df = return_closing_orders_df(QUEEN=QUEEN, exit_order_link=queen_order['exit_order_link']) @@ -841,11 +843,11 @@ def alpaca_queen_order_state(QUEEN, order_status, queen_order, queen_order_idx, res = update_origin_orders_profits(QUEEN=QUEEN, queen_order=queen_order, origin_order=origin_order, origin_order_idx=origin_order_idx) closing_filled = res['closing_filled'] profit_loss = res['profit_loss'] - update_queen_order_profits(QUEEN=QUEEN, ticker=queen_order.get('ticker'), queen_order=queen_order, queen_order_idx=queen_order_idx, priceinfo=priceinfo) - + # Qty Available update_origin_order_qty_available(QUEEN=QUEEN, run_order_idx=origin_order_idx, RUNNING_CLOSE_Orders=RUNNING_CLOSE_Orders, RUNNING_Orders=RUNNING_Orders) - update_origin_order_cost_basis_current(QUEEN=QUEEN, queen_order_idx=origin_order_idx) + # Check to complete Queen Order check_origin_order_status(QUEEN=QUEEN, origin_order=origin_order, origin_idx=origin_order_idx, closing_filled=closing_filled) + elif alp_order_status in partially_filled: if order_status['side'] == 'buy': @@ -869,7 +871,6 @@ def alpaca_queen_order_state(QUEEN, order_status, queen_order, queen_order_idx, update_origin_orders_profits(QUEEN=QUEEN, queen_order=queen_order, origin_order=origin_order, origin_order_idx=origin_order_idx) update_queen_order_profits(QUEEN=QUEEN, ticker=queen_order.get('ticker'), queen_order=queen_order, queen_order_idx=queen_order_idx, priceinfo=priceinfo) update_origin_order_qty_available(QUEEN=QUEEN, run_order_idx=origin_order_idx, RUNNING_CLOSE_Orders=RUNNING_CLOSE_Orders, RUNNING_Orders=RUNNING_Orders) - update_origin_order_cost_basis_current(QUEEN=QUEEN, queen_order_idx=origin_order_idx) else: print("Critical Error New Order Side") @@ -1029,20 +1030,21 @@ def reconcile_broker_orders_with_queen_orders(BROKER, api, QUEEN, active_queen_o def update_queen_order(QUEEN, update_package, upsert_to_main_server=upsert_to_main_server): # update_package client_order id and field updates {client_order_id: {'queen_order_status': 'running'}} + table_name = 'queen_orders' if QUEEN['prod'] else 'queen_orders_sandbox' try: + save = False for c_order_id, package in update_package['queen_order_updates'].items(): - save = False for field_, new_value in package.items(): try: QUEEN['queen_orders'].at[c_order_id, field_] = new_value + # postgresql migration + save_queen_order(QUEEN, prod=QUEEN['prod'], client_order_id=c_order_id, upsert_to_main_server=upsert_to_main_server) save = True except Exception as e: print(e, 'failed to update QueenOrder') logging.critical({'msg': 'failed to update queen orders', 'error': e, 'other': (field_, new_value)}) - if save: - save_queen_order(QUEEN, prod=QUEEN['prod'], client_order_id=c_order_id, upsert_to_main_server=upsert_to_main_server) - # if save: # Obsolete with move to save per order - # god_save_the_queen(QUEEN, save_q=True, save_qo=True, upsert_to_main_server=upsert_to_main_server, console=True) + if save: + god_save_the_queen(QUEEN, save_q=True, save_qo=True, upsert_to_main_server=upsert_to_main_server) except Exception as e: print_line_of_error() logging.critical({'error': e, 'msg': 'update queen order', 'update_package': update_package}) @@ -1050,29 +1052,22 @@ def update_queen_order(QUEEN, update_package, upsert_to_main_server=upsert_to_ma def update_queen_order_order_rules(QUEEN, update_package, upsert_to_main_server=upsert_to_main_server): try: - latest_kors = kings_order_rules() + table_name = 'queen_orders' if QUEEN['prod'] else 'queen_orders_sandbox' + save = False for c_order_id, package in update_package['update_order_rules'].items(): - save = False for field_, new_value in package.items(): try: - if field_ in latest_kors: - QUEEN['queen_orders'].at[c_order_id, 'order_rules'].update({field_: new_value}) - save = True - elif field_ in QUEEN['queen_orders'].loc[c_order_id]: - QUEEN['queen_orders'].at[c_order_id, field_] = new_value - save = True - else: - print(f"Field {field_} not found in order_rules or queen_orders for {c_order_id}") - logging.warning(f"Field {field_} not found in order_rules or queen_orders for {c_order_id}") - # logging.info((f'{field_} updated to {new_value}')) + QUEEN['queen_orders'].at[c_order_id, 'order_rules'].update({field_: new_value}) + save = True + logging.info((f'{field_} updated to {new_value}')) + + # postgresql migration + save_queen_order(QUEEN, prod=QUEEN['prod'], client_order_id=c_order_id, upsert_to_main_server=upsert_to_main_server) except Exception as e: print(e, 'failed to update QueenOrder') logging.critical({'msg': 'failed to update queen orders', 'error': e, 'other': (field_, new_value)}) - - if save: - save_queen_order(QUEEN, prod=QUEEN['prod'], client_order_id=c_order_id, upsert_to_main_server=upsert_to_main_server) - # if save: - # god_save_the_queen(QUEEN, save_q=True, save_qo=True, upsert_to_main_server=upsert_to_main_server) + if save: + god_save_the_queen(QUEEN, save_q=True, save_qo=True, upsert_to_main_server=upsert_to_main_server) except Exception as e: print_line_of_error() logging.critical({'error': e, 'msg': 'update queen order', 'update_package': update_package}) @@ -1437,6 +1432,7 @@ def ready_buy_star_timeduration_delay(star_time): print(msg) wave_amo = star_total_borrow_remaining + # ticker_current_ask = STORY_bee[ticker_time_frame]['story'].get('current_ask') ticker_current_ask = ticker_priceinfo['current_ask'] if not ticker_current_ask: pass # snapshot always called on execute @@ -2060,17 +2056,15 @@ def check_revrec( ticker_time_frame, makers_middle_price, close_order_today, - app_request, - min_allocation_field='ticker_total_budget' + min_allocation='ticker_total_budget' ): try: symbol = ticker_time_frame.split("_")[0] - # WORKERBEE Need a higher defensive check on selling below total budget // issue I think is when multiple sells happen at once ignore_allocation_budget = order_rules.get('ignore_allocation_budget', False) current_long_value = 'star_buys_at_play' if ignore_allocation_budget else 'star_buys_at_play_allocation' - if close_order_today or app_request: + if close_order_today: logging.info(f"{ticker_time_frame} CLOSE Order TODAY") return sell_qty @@ -2078,7 +2072,7 @@ def check_revrec( print(f"{ticker_time_frame} MISSING in RevRec") return sell_qty - min_allocation = revrec['storygauge'].loc[symbol].get(min_allocation_field, 0) + min_allocation = revrec['storygauge'].loc[symbol].get(min_allocation, 0) if min_allocation == 0: print(f'{ticker_time_frame} no Min Allocation Sell ALL') return sell_qty @@ -2111,11 +2105,11 @@ def check_revrec( # Defensive: never negative if adjust_qty != sell_qty: - print("SELL QTY ADJUSTMENT", ticker_time_frame, " adjusted sell qty: ", adjust_qty, " sell qty: ", sell_qty) + print("SELL QTY ADJUSTMENT", ticker_time_frame, " sell qty: ", sell_qty, " adjusted sell qty: ", adjust_qty) if adjust_qty <= 0: print(ticker_time_frame, "NOT Allowed to SELL Adjusted QTY == ", adjust_qty, " : Sell Qty ==", sell_qty) return 0 - + return float(adjust_qty) @@ -2289,6 +2283,7 @@ def waterfall_sellout_chain(client_order_id, limit_price = priceinfo['maker_middle'] if order_type == 'limit' else False """ Stop Loss """ + if sell_out: # can be None or 0==None if honey <= sell_out: # print(f"{ticker_time_frame} selling out due STOP LOSS {client_order_id}") @@ -2339,20 +2334,19 @@ def waterfall_sellout_chain(client_order_id, if sell_order and sell_reasons: - if 'sell_reason' in run_order.keys(): - current_reasons = QUEEN['queen_orders'].at[client_order_id, 'sell_reason'] - if str(current_reasons) != str(sell_reasons): - # print(ticker_time_frame, ": Sell Reason Changed", current_reasons, "to", sell_reasons) - QUEEN['queen_orders'].at[client_order_id, 'sell_reason'] = sell_reasons - # save_order = True - if 'queen_wants_to_sell_qty' in run_order.keys(): - current_sell_qty = QUEEN['queen_orders'].at[client_order_id, 'queen_wants_to_sell_qty'] - if sell_qty != current_sell_qty: - # print(ticker_time_frame, "QUEEN WANTS TO SELL QTY CHANGED: from ", current_sell_qty, "TO NOW SELL", sell_qty) - QUEEN['queen_orders'].at[client_order_id, 'queen_wants_to_sell_qty'] = sell_qty - # save_order = True - - if not close_order_today and not app_request: # based on user input, profit, sellout, and sell_trigbee_trigger... + # if 'sell_reason' in run_order.keys(): + # current_reasons = QUEEN['queen_orders'].at[client_order_id, 'sell_reason'] + # if str(current_reasons) != str(sell_reasons): + # print(ticker_time_frame, ": Sell Reason Changed", current_reasons, "to", sell_reasons) + # QUEEN['queen_orders'].at[client_order_id, 'sell_reason'] = sell_reasons + # save_order = True + # if 'queen_wants_to_sell_qty' in run_order.keys(): + # current_sell_qty = QUEEN['queen_orders'].at[client_order_id, 'queen_wants_to_sell_qty'] + # if sell_qty != current_sell_qty: + # print(ticker_time_frame, "QUEEN WANTS TO SELL QTY CHANGED: from ", current_sell_qty, "TO NOW SELL", sell_qty) + # QUEEN['queen_orders'].at[client_order_id, 'queen_wants_to_sell_qty'] = sell_qty + # save_order = True + if not close_order_today: # based on user input, profit, sellout, and sell_trigbee_trigger... """ AUTO PILOT """ auto_pilot_df = QUEEN_KING['king_controls_queen']['ticker_autopilot'] if symbol in auto_pilot_df.index: @@ -2373,8 +2367,7 @@ def waterfall_sellout_chain(client_order_id, ticker_time_frame=ticker_time_frame, makers_middle_price=makers_middle_price, close_order_today=close_order_today, - app_request=app_request, - min_allocation_field='ticker_total_budget', + min_allocation='ticker_total_budget', ) if sell_qty > 0: @@ -2398,18 +2391,18 @@ def waterfall_sellout_chain(client_order_id, return {'sell_order': False, 'save_order': save_order} else: pass - if 'sell_reason' in run_order.keys(): - current_reasons = QUEEN['queen_orders'].at[client_order_id, 'sell_reason'] - if str(current_reasons) != str([]): - # print(ticker_time_frame, ": Sell Reason Changed", current_reasons, "to", sell_reasons) - QUEEN['queen_orders'].at[client_order_id, 'sell_reason'] = sell_reasons - # save_order = True - if 'queen_wants_to_sell_qty' in run_order.keys(): - current_sell_qty = QUEEN['queen_orders'].at[client_order_id, 'queen_wants_to_sell_qty'] - if current_sell_qty != 0: - # print(ticker_time_frame, "No Sell Reasons Reset Sell Qty to 0 from: ", current_sell_qty) - QUEEN['queen_orders'].at[client_order_id, 'queen_wants_to_sell_qty'] = 0 - # save_order = True + # if 'sell_reason' in run_order.keys(): + # current_reasons = QUEEN['queen_orders'].at[client_order_id, 'sell_reason'] + # if str(current_reasons) != str([]): + # print(ticker_time_frame, ": Sell Reason Changed", current_reasons, "to", sell_reasons) + # QUEEN['queen_orders'].at[client_order_id, 'sell_reason'] = sell_reasons + # save_order = True + # if 'queen_wants_to_sell_qty' in run_order.keys(): + # current_sell_qty = QUEEN['queen_orders'].at[client_order_id, 'queen_wants_to_sell_qty'] + # if current_sell_qty != 0: + # print(ticker_time_frame, "No Sell Reasons Reset Sell Qty to 0 from: ", current_sell_qty) + # QUEEN['queen_orders'].at[client_order_id, 'queen_wants_to_sell_qty'] = 0 + # save_order = True return {'sell_order': False, 'save_order': save_order} except Exception as e: @@ -2474,10 +2467,10 @@ def stop_queen_order_from_kingbishop(QUEEN, QUEEN_KING, run_order): if str(run_order['order_trig_sell_stop']).lower() == 'true': ### consider remaining qty return True elif run_order['queen_order_state'] not in RUNNING_Orders: - print(ttf, run_order['client_order_id'], ": QUEEN ORDER STATE NOT RUNNING") + print(ttf, ": QUEEN ORDER STATE NOT RUNNING") return True elif float(run_order['qty_available']) == 0: - print(ttf, run_order['client_order_id'], ": QUEEN ORDER QTY AVAILABLE IS ZERO 0") + print(ttf, ": QUEEN ORDER QTY AVAILABLE IS ZERO 0") QUEEN['queen_orders'].at[client_order_id, 'queen_order_state'] = 'completed' save_queen_order(QUEEN, prod=QUEEN['prod'], client_order_id=client_order_id, upsert_to_main_server=upsert_to_main_server) return True @@ -2618,7 +2611,7 @@ def long_short_queenorders(df_active, QUEEN, col_metric='cost_basis_current'): if not order_status: adhoc_handle_queen_order_broker_failure(order_status, QUEEN, idx) continue - run_order = update_latest_queen_order_status(QUEEN=QUEEN, order_status=order_status, queen_order_idx=idx) + run_order = update_latest_queen_order_status(QUEEN, order_status, queen_order_idx=idx) save_order = True save_broker_order = True update_broker_order_status(BROKER, order_status) @@ -3149,4 +3142,4 @@ def createParser(): Believe in you, Believe in God, Believe -""" +""" \ No newline at end of file diff --git a/chess_piece/queen_hive.py b/chess_piece/queen_hive.py index f9a0b088..150bb07f 100644 --- a/chess_piece/queen_hive.py +++ b/chess_piece/queen_hive.py @@ -49,7 +49,7 @@ load_dotenv(os.path.join(main_root, ".env")) db_root = os.path.join(main_root, "db") -pg_migration = os.getenv('pg_migration') +pg_migration = os.getenv('pg_migration', 'False').lower() == 'true' server = os.getenv('server') """# Dates """ @@ -168,13 +168,51 @@ def init_logging(queens_chess_piece, prod, loglevel='info', db_root=''): def kingdom__grace_to_find_a_Queen(prod=True): + try: + if pg_migration: + table_name = 'db' if prod else 'db_sandbox' + print(f"Retrieving KING from table: {table_name}") + KING = PollenDatabase.retrieve_data(table_name, 'KING') + # create list for userdb + else: + KING = ReadPickleData(master_swarm_KING(prod)) + except Exception as e: + print(f"Error loading KING data: {e}") + KING = None + + # Handle case where KING is None or doesn't have expected structure + if KING is None or not isinstance(KING, dict): + print("Warning: KING data is None or invalid, initializing default structure") + KING = init_KING() # Use the proper initialization function + # Save the newly initialized KING data + if pg_migration: + table_name = 'db' if prod else 'db_sandbox' + PollenDatabase.upsert_data(table_name, 'KING', KING) + print(f"KING data saved to table: {table_name}") + elif 'users' not in KING: + print("Warning: KING missing 'users' key, initializing") + KING['users'] = {'not_allowed': []} + + # Ensure all required keys are present + required_keys = ['star_times', 'alpaca_symbols_df', 'alpaca_symbols_dict', 'active_order_state_list'] + missing_keys = [key for key in required_keys if key not in KING] + + if missing_keys: + print(f"Warning: KING missing keys: {missing_keys}, reinitializing...") + # Reinitialize KING with proper structure + KING = init_KING() + # Save the newly initialized KING data + if pg_migration: + table_name = 'db' if prod else 'db_sandbox' + PollenDatabase.upsert_data(table_name, 'KING', KING) + print(f"KING data saved to table: {table_name}") + + # Ensure KING data is properly saved and retrieved if pg_migration: table_name = 'db' if prod else 'db_sandbox' - KING = PollenDatabase.retrieve_data(table_name, 'KING') - # create list for userdb - else: - KING = ReadPickleData(master_swarm_KING(prod)) - + # Force save the KING data to ensure it's persisted + PollenDatabase.upsert_data(table_name, 'KING', KING) + print(f"KING data ensured in table: {table_name}") if 'not_allowed' not in KING['users'].keys(): KING['users']['not_allowed'] = [] @@ -259,28 +297,46 @@ def return_alpaca_api_keys(prod): # """ Keys """ ### NEEDS TO BE FIXED TO PULL USERS API CREDS UNLESS USER IS PART OF MAIN.FUND.Account try: if prod: + api_key_id = os.environ.get("APCA_API_KEY_ID") + api_secret = os.environ.get("APCA_API_SECRET_KEY") + if not api_key_id or not api_secret: + raise ValueError("Production API credentials not found in environment variables") + keys = return_api_keys( base_url="https://api.alpaca.markets", - api_key_id=os.environ.get("APCA_API_KEY_ID"), - api_secret=os.environ.get("APCA_API_SECRET_KEY"), + api_key_id=api_key_id, + api_secret=api_secret, prod=prod, ) rest = keys["rest"] api = keys["api"] else: # Paper + api_key_id = os.environ.get("APCA_API_KEY_ID_PAPER") + api_secret = os.environ.get("APCA_API_SECRET_KEY_PAPER") + if not api_key_id or not api_secret: + raise ValueError("Paper trading API credentials not found in environment variables") + keys_paper = return_api_keys( base_url="https://paper-api.alpaca.markets", - api_key_id=os.environ.get("APCA_API_KEY_ID_PAPER"), - api_secret=os.environ.get("APCA_API_SECRET_KEY_PAPER"), + api_key_id=api_key_id, + api_secret=api_secret, prod=False, ) rest = keys_paper["rest"] api = keys_paper["api"] except Exception as e: - print("Key Return failure default to HivesKeys") - print(e) + print("Key Return failure - API credentials not configured") + print(f"Error: {e}") + print("Please set the following environment variables:") + if prod: + print("- APCA_API_KEY_ID") + print("- APCA_API_SECRET_KEY") + else: + print("- APCA_API_KEY_ID_PAPER") + print("- APCA_API_SECRET_KEY_PAPER") + raise e return {"rest": rest, "api": api} @@ -316,7 +372,8 @@ def return_client_user__alpaca_api_keys(prod, api_key_id, api_secret): if prod: prod_keys_confirmed = QUEEN_KING["users_secrets"]["prod_keys_confirmed"] if prod_keys_confirmed == False: - return False + print("Warning: Production keys not confirmed, using demo mode...") + return create_demo_api() else: api_key_id = QUEEN_KING["users_secrets"]["APCA_API_KEY_ID"] api_secret = QUEEN_KING["users_secrets"]["APCA_API_SECRET_KEY"] @@ -325,7 +382,8 @@ def return_client_user__alpaca_api_keys(prod, api_key_id, api_secret): else: sandbox_keys_confirmed = QUEEN_KING["users_secrets"]["sandbox_keys_confirmed"] if sandbox_keys_confirmed == False: - return False + print("Warning: Sandbox keys not confirmed, using demo mode...") + return create_demo_api() else: api_key_id = QUEEN_KING["users_secrets"]["APCA_API_KEY_ID_PAPER"] api_secret = QUEEN_KING["users_secrets"]["APCA_API_SECRET_KEY_PAPER"] @@ -338,7 +396,8 @@ def return_client_user__alpaca_api_keys(prod, api_key_id, api_secret): return return_alpaca_api_keys(prod=False)["api"] except Exception as e: print_line_of_error() - return False + print("Warning: API credentials not available, using demo mode...") + return create_demo_api() def hive_dates(api): @@ -475,7 +534,7 @@ def init_qcp_workerbees(init_macd_vars={"fast": 12, "slow": 26, "smooth": 9}, def setup_chess_board(QUEEN, qcp_bees_key='workerbees', screen='screen_1'): if qcp_bees_key not in QUEEN.keys(): QUEEN[qcp_bees_key] = {} - db = init_swarm_dbs(prod=True) + db = init_swarm_dbs(prod=False) if pg_migration: BISHOP = read_swarm_db(True, 'BISHOP') @@ -3155,7 +3214,13 @@ def pollen_themes( # wave_periods = {'morning_9-11': .01, 'lunch_11-2': .01, 'afternoon_2-4': .01, 'Day': .01, 'afterhours': .01} # star__storywave: auto_adjusting_with_starwave: using story - star_times = KING["star_times"] + # Handle missing star_times with fallback + if "star_times" in KING and KING["star_times"]: + star_times = KING["star_times"] + else: + print("Warning: star_times not found in KING, using fallback") + star_times = stars() # Use the default stars function + pollen_themes = {} for theme in themes: pollen_themes[theme] = {} @@ -3208,7 +3273,19 @@ def update_king_users(KING, init=False, users_allowed_queen_email=["stefanstapin def init_KING(): king = {} - ticker_universe = return_Ticker_Universe() + + # Try to get ticker universe, with fallback if it fails + try: + ticker_universe = return_Ticker_Universe() + king['alpaca_symbols_dict'] = ticker_universe.get('alpaca_symbols_dict', {}) + king['alpaca_symbols_df'] = ticker_universe.get('alpaca_symbols_df', pd.DataFrame()) + except Exception as e: + print(f"Warning: Could not load ticker universe: {e}") + print("Creating minimal ticker universe...") + # Create minimal ticker universe with common symbols including crypto + minimal_symbols = ['SPY', 'QQQ', 'AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'NVDA', 'META', 'NFLX', 'BTC/USD', 'ETH/USD'] + king['alpaca_symbols_dict'] = {symbol: {'symbol': symbol, 'exchange': 'NASDAQ' if '/' not in symbol else 'CRYPTO'} for symbol in minimal_symbols} + king['alpaca_symbols_df'] = pd.DataFrame(index=minimal_symbols) trigbees = ["buy_cross-0", "sell_cross-0"] waveBlocktimes = [ @@ -3220,12 +3297,23 @@ def init_KING(): "Day", ] - king["star_times"] = stars() + # Ensure star_times is properly set + try: + king["star_times"] = stars() + except Exception as e: + print(f"Warning: Could not initialize star_times: {e}") + king["star_times"] = { + "1Minute_1Day": 1, + "5Minute_5Day": 5, + "30Minute_1Month": 18, + "1Hour_3Month": 58, + "2Hour_6Month": 115, + "1Day_1Year": 250, + } + king["waveBlocktimes"] = waveBlocktimes king["trigbees"] = trigbees king = update_king_users(KING=king, init=True) - king['alpaca_symbols_dict'] = ticker_universe.get('alpaca_symbols_dict') - king['alpaca_symbols_df'] = ticker_universe.get('alpaca_symbols_df') king['active_order_state_list'] = ['running', 'running_close', 'submitted', 'error', 'pending', 'completed', 'completed_alpaca', 'running_open', 'archived_bee'] return king @@ -3314,7 +3402,7 @@ def read_swarm_db(prod=False, key='BISHOP'): table_name = 'db' if prod else 'db_sandbox' return PollenDatabase.retrieve_data(table_name, key) -def init_swarm_dbs(prod, init=False, pg_migration=False, dbs=['KING', 'QUEEN', 'BISHOP', 'KNIGHT']): +def init_swarm_dbs(prod, init=True, pg_migration=True, dbs=['KING', 'QUEEN', 'BISHOP', 'KNIGHT']): table_name = 'db' if prod else 'db_sandbox' @@ -3322,21 +3410,40 @@ def setup_swarm_dbs(db_root, table_name=table_name, dbs=dbs, prod=prod): for key in dbs: if key == 'KING': if not PollenDatabase.key_exists(table_name, key): + print("Initializing KING data...") data = init_KING() - PollenDatabase.upsert_data(table_name, key, data) + PollenDatabase.upsert_data(table_name, key, data) + print("KING data saved to database") + else: + print("KING data already exists in database") if key == 'QUEEN': if not PollenDatabase.key_exists(table_name, key): data = init_queen('queen') PollenDatabase.upsert_data(table_name, key, data) if key == 'BISHOP': if not PollenDatabase.key_exists(table_name, key): - db = init_swarm_dbs(prod) - BISHOP = ReadPickleData(db.get('BISHOP')) - PollenDatabase.upsert_data(table_name, key, BISHOP) + # Fix recursion: Don't call init_swarm_dbs from within setup_swarm_dbs + # Instead, initialize BISHOP directly or use a different approach + try: + # Try to read existing BISHOP data first + BISHOP = ReadPickleData(os.path.join(hive_master_root(), 'db', f'bishop{"_sandbox" if not prod else ""}.pkl')) + PollenDatabase.upsert_data(table_name, key, BISHOP) + except Exception as e: + print(f"Could not load BISHOP data: {e}") + print("Creating empty BISHOP structure...") + # If no existing data or pandas compatibility issues, create empty BISHOP structure + BISHOP = {} + PollenDatabase.upsert_data(table_name, key, BISHOP) if key == 'KNIGHT': if not PollenDatabase.key_exists(table_name, key): data = {} - PollenDatabase.upsert_data(table_name, key, data) + PollenDatabase.upsert_data(table_name, key, data) + + # Add whalewisdom fallback + if not PollenDatabase.key_exists(table_name, 'whalewisdom'): + print("Initializing whalewisdom with empty data...") + whalewisdom_data = {'latest_filer_holdings': pd.DataFrame()} + PollenDatabase.upsert_data(table_name, 'whalewisdom', whalewisdom_data) if pg_migration: if init: @@ -3381,6 +3488,11 @@ def init_queen_orders(pickle_file=None, pg_migration=False): def setup_chesspiece_dbs(db_root, table_name=table_name, client_user_tables=client_user_tables): # print("Check to init pollen DB") + # Only use PostgreSQL if pg_migration is True + if not pg_migration: + print("Using pickle files instead of PostgreSQL") + return + env_table = 'client_user_env' if not PollenDatabase.key_exists(env_table, f'{db_root}-ENV'): print("INIT ENV for user", db_root) @@ -4039,25 +4151,23 @@ def generate_chessboards_trading_models(chessboard): return tradingmodels -def create_TrigRule( - symbol='SPY', - trigrule_type='wave_trinity', # trading_pairs - trigrule_status='not_active', # active, not_active +def Create_TrigRule( + symbol, + trigrule_type='trinity', # vwap, rsi, macd, trinity.. + trigrule_status='active', expire_date=datetime.now().strftime('%m/%d/%YT%H:%M'), user_accept=True, - max_order_nums=3, # to achieve max budget + max_order_nums=3, max_budget=89, marker=None, # vwap, rsi, macd, trinity.. - marker_value=None, # -.2 - deviation_symbols=[], # list of symbols to compare against - deviation_group=False, # compare on group std deviation + marker_value=None, # + deviation_symbols=[], + deviation_group=False, ttf=None, # Comparsion then only on TTF - block_times=[] # trigging active when in block time + block_time=[] # trigging active when in block time ): - # all_vars = {key: value for key, value in globals().items() if not key.startswith("__") and not callable(value)} return { "symbol": symbol, - "trigrule_type": trigrule_type, "trigrule_status": trigrule_status, "expire_date": expire_date, "user_accept": user_accept, @@ -4066,7 +4176,7 @@ def create_TrigRule( "marker": marker, "deviation_symbols": deviation_symbols, "deviation_group": deviation_group, - "block_times": block_times, + "block_time": block_time, "marker_value": marker_value, "ttf": ttf, } @@ -4090,16 +4200,17 @@ def return_queen_controls(stars=stars): # "ready_buy_cross": "not_active", # NOT USED }, # revrec + 'ticker_revrec_allocation_mapping' : {}, # not needed done in KORS 'ticker_autopilot' : pd.DataFrame([{'symbol': 'SPY', 'buy_autopilot': True, 'sell_autopilot': True}]).set_index('symbol'), 'ticker_refresh_star': pd.DataFrame([{'symbol': 'SPY', 'ticker_refresh_star': None}]).set_index('symbol'), - 'ticker_trigrules': [create_TrigRule()], # GAMBLE_v2 + # 'trade_only_margin': False, # control not adding WORKERBEE - ## NOT USED ## - # 'daytrade_risk_takes': {'frame_blocks': {'morning': 1, 'lunch': 1, 'afternoon':1},'budget_type': 'star'}, # NOT USED - # 'throttle': .5, # NOT USED + # working GAMBLE + 'daytrade_risk_takes': {'frame_blocks': {'morning': 1, 'lunch': 1, 'afternoon':1},'budget_type': 'star'}, # NOT USED + # GAMBLE_v2 + # 'gamble': [], # based on every ticker or ttf - df of last time gambled, result of gamble, risk level allowed, ? # 'ticker_buying_powers': {'SPY': {'buying_power': 0, 'borrow_power': 0}}, # not needed done in KORS - # 'ticker_revrec_allocation_mapping' : {}, # not needed done in KORS - # 'trade_only_margin': False, # control not adding WORKERBEE + 'throttle': .5, } return queen_controls_dict @@ -4111,8 +4222,137 @@ def refresh_tickers_TradingModels(QUEEN_KING, ticker, theme='nuetral'): return QUEEN_KING +def create_demo_api(): + """Create a mock API object for demo mode.""" + class DemoAPI: + def get_calendar(self): + """Return demo trading calendar data.""" + from datetime import datetime, timedelta + import pandas as pd + + # Generate demo trading days for the next 30 days + demo_days = [] + current_date = datetime.now() + for i in range(30): + date = current_date + timedelta(days=i) + # Skip weekends for demo + if date.weekday() < 5: # Monday = 0, Friday = 4 + demo_days.append({ + 'date': date.strftime('%Y-%m-%d'), + 'open': '09:30', + 'close': '16:00' + }) + + # Create mock objects that behave like Alpaca calendar objects + class MockCalendarDay: + def __init__(self, date, open_time, close_time): + self.date = date + self.open = open_time + self.close = close_time + self._raw = { + 'date': date, + 'open': open_time, + 'close': close_time + } + + return [MockCalendarDay(day['date'], day['open'], day['close']) for day in demo_days] + + def list_assets(self): + """Return demo asset list.""" + class MockAsset: + def __init__(self, symbol, status='active', tradable=True, exchange='NASDAQ'): + self.symbol = symbol + self.status = status + self.tradable = tradable + self.exchange = exchange + self._raw = { + 'symbol': symbol, + 'status': status, + 'tradable': tradable, + 'exchange': exchange, + 'shortable': True, + 'easy_to_borrow': True + } + + demo_symbols = ['SPY', 'QQQ', 'AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'NVDA'] + return [MockAsset(symbol) for symbol in demo_symbols] + + def get_portfolio_history(self, period='3M', timeframe='1D'): + """Return demo portfolio history data.""" + import pandas as pd + from datetime import datetime, timedelta + + # Generate demo portfolio history data + end_date = datetime.now() + if period == '3M': + start_date = end_date - timedelta(days=90) + elif period == '1M': + start_date = end_date - timedelta(days=30) + elif period == '7D': + start_date = end_date - timedelta(days=7) + elif period == '1D': + start_date = end_date - timedelta(days=1) + else: + start_date = end_date - timedelta(days=90) # Default to 3M + + # Generate demo data points + demo_data = [] + current_equity = 100000 # Starting with $100k + base_value = current_equity + + # Generate realistic demo portfolio data + for i in range(30): # 30 data points + date = start_date + timedelta(days=i * 3) # Every 3 days + # Simulate some portfolio growth with random fluctuations + growth_factor = 1 + (i * 0.01) + (i % 3 - 1) * 0.005 # Slight upward trend with some volatility + current_equity = base_value * growth_factor + profit_loss = current_equity - base_value + profit_loss_pct = (profit_loss / base_value) * 100 + + demo_data.append({ + 'timestamp': date.strftime('%Y-%m-%dT%H:%M:%S.%fZ'), + 'equity': round(current_equity, 2), + 'profit_loss': round(profit_loss, 2), + 'profit_loss_pct': round(profit_loss_pct, 2), + 'base_value': base_value, + 'base_value_asof': base_value, + 'timeframe': timeframe + }) + + # Create mock object that behaves like Alpaca portfolio history + class MockPortfolioHistory: + def __init__(self, data): + self._raw = data + + return MockPortfolioHistory(demo_data) + + return DemoAPI() + def return_Ticker_Universe(): # Return Ticker and Acct Info - api = return_alpaca_api_keys(prod=False)["api"] + try: + api = return_alpaca_api_keys(prod=False)["api"] + except Exception as e: + print(f"Warning: Could not connect to Alpaca API: {e}") + print("Using demo mode with limited ticker data...") + + # Create demo data that matches the expected structure + demo_api = create_demo_api() + all_alpaca_tickers = demo_api.list_assets() + + # Create alpaca_symbols_dict in the same format as real API + alpaca_symbols_dict = {} + for ticker in all_alpaca_tickers: + alpaca_symbols_dict[ticker.symbol] = vars(ticker) + + alpaca_symbols = {k: i['_raw'] for k, i in alpaca_symbols_dict.items()} + alpaca_symbols_df = pd.DataFrame(alpaca_symbols).T + + # Return demo ticker universe matching the real API structure + return { + "alpaca_symbols_dict": alpaca_symbols_dict, + "all_alpaca_tickers": all_alpaca_tickers, + "alpaca_symbols_df": alpaca_symbols_df, + } # Initiate Code File Creation index_list = [ "DJA", @@ -5347,4 +5587,4 @@ def read_wiki_index(): # index_ticker_db = return_index_tickers(index_dir=os.path.join(db_root, 'index_tickers'), ext='.csv') # """ Return Index Charts & Data for All Tickers Wanted""" -# """ Return Tickers of SP500 & Nasdaq / Other Tickers""" +# """ Return Tickers of SP500 & Nasdaq / Other Tickers""" \ No newline at end of file diff --git a/chess_piece/workerbees.py b/chess_piece/workerbees.py index 71b30fa1..f106d4c8 100644 --- a/chess_piece/workerbees.py +++ b/chess_piece/workerbees.py @@ -16,7 +16,7 @@ import threading import ipdb import requests -from tqdm import tqdm + from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import datetime import os @@ -1024,8 +1024,8 @@ def initiate_ttframe_charts(master_tickers, star_times, MACD_settings, MACD_WAVE s_mainbeetime = datetime.now(est) # WORKERBEE if backetesting no need to recall chart data df_all = {} + for ticker in master_tickers: - print("INITIATING CHARTS for ", ticker) res = Return_Init_ChartData(ticker_list=[ticker], chart_times=star_times) df_tickers_data = res["init_charts"] df_all.update(df_tickers_data) @@ -1052,7 +1052,7 @@ def initiate_ttframe_charts(master_tickers, star_times, MACD_settings, MACD_WAVE return pollen except Exception as e: print_line_of_error(f"BEES IINIT FAILED {e} ") - return None + raise e def chunk(it, size): it = iter(it) @@ -1126,10 +1126,10 @@ def init_QueenWorkersBees(QUEENBEE, queens_chess_pieces, MACD_WAVES, queens_mast if not qcp: print("QCP", qcp_worker) continue - master_tickers_og = qcp.get('tickers') - master_tickers = [i for i in master_tickers_og if i in queens_master_tickers] + master_tickers = qcp.get('tickers') + master_tickers = [i for i in master_tickers if i in queens_master_tickers] if not master_tickers: - print("not tics available in master", master_tickers_og) + print("not tics available in master") return None # master_tickers = ['SPY', 'BTC/USD', 'ETH/USD', 'LTC/USD'] @@ -1159,9 +1159,6 @@ def init_QueenWorkersBees(QUEENBEE, queens_chess_pieces, MACD_WAVES, queens_mast speed_gauges=speed_gauges, reset_only=reset_only, ) - if not pollen: - print("no pollen for ", qcp_worker) - continue WORKERBEE_QUEEN[qcp_worker]["pollencharts"] = pollen["pollencharts"] WORKERBEE_QUEEN[qcp_worker]["pollencharts_nectar"] = pollen["pollencharts_nectar"] WORKERBEE_queens[qcp_worker] = WORKERBEE_QUEEN @@ -1175,7 +1172,9 @@ def init_QueenWorkersBees(QUEENBEE, queens_chess_pieces, MACD_WAVES, queens_mast def queens_court__WorkerBees(QUEENBEE, prod, qcp_s, run_all_pawns=False, streamit=False, reset_only=reset_only): if type(qcp_s) == str: - qcp_s = [qcp_s] + # qcp_s = [qcp_s] + # Handle comma-separated strings like "castle,bishop,knight" + qcp_s = [piece.strip() for piece in qcp_s.split(',')] queens_chess_pieces = qcp_s # pq.get("queens_chess_pieces") def confirm_tickers_available(alpaca_symbols_dict, symbols): @@ -1250,7 +1249,7 @@ def handle_qcp_pawns(QUEENBEE, tickers, queens_chess_pieces=queens_chess_pieces, print("> 20 catch call pawns") for i in range(0, len(tickers), CHUNK_SIZE): chunk = tickers[i:i + CHUNK_SIZE] - pawn = f'pawn_{len(QUEENBEE["workerbees"]) +1}' + pawn = f'{str(chunk)}' pawn_qcp = init_qcp_workerbees( init_macd_vars={"fast": 12, "slow": 26, "smooth": 9}, ticker_list=chunk, @@ -1273,12 +1272,12 @@ def handle_qcp_pawns(QUEENBEE, tickers, queens_chess_pieces=queens_chess_pieces, list_of_lists = [i.get('tickers') for qcp, i in QUEENBEE['workerbees'].items()] all_symbols = [item for sublist in list_of_lists for item in sublist] # ipdb.set_trace() - storygauge = init_queenbee(client_user='stefanstapinski@gmail.com', prod=True, revrec=True, pg_migration=True)['revrec'].get('storygauge') + # df_tickers = init_queenbee(client_user='stefanstapinski@gmail.com', prod=True, revrec=True, pg_migration=True)['revrec'].get('df_ticker') tickers_to_add = [] - for ticker in storygauge.index: - if ticker not in all_symbols and storygauge.loc[ticker, 'ticker_buying_power'] > 0: - # print(ticker, "NOT IN QUEENBEE adding to Castle") - tickers_to_add.append(ticker) + # for ticker in df_tickers.index: + # if ticker not in all_symbols and df_tickers.loc[ticker, 'ticker_buying_power'] > 0: + # # print(ticker, "NOT IN QUEENBEE adding to Castle") + # tickers_to_add.append(ticker) new_symbols = [i for i in tickers_to_add if i not in all_symbols] # and i not in all_values for i in new_symbols: @@ -1310,17 +1309,23 @@ def handle_qcp_pawns(QUEENBEE, tickers, queens_chess_pieces=queens_chess_pieces, if len(queens_master_tickers) > 89: print("chunking queens") - QUEENBEE['workerbees'] = {} - QUEENBEE, queens_chess_pieces, queens_master_tickers = handle_qcp_pawns(QUEENBEE, queens_master_tickers, queens_chess_pieces=[], queens_master_tickers=[]) - - - queen_workers = init_QueenWorkersBees( - QUEENBEE=QUEENBEE, - queens_chess_pieces=queens_chess_pieces, - MACD_WAVES=MACD_WAVES, - queens_master_tickers=queens_master_tickers, - reset_only=reset_only, - ) + from tqdm import tqdm + for ticker in tqdm(queens_master_tickers): + queen_workers = init_QueenWorkersBees( + QUEENBEE=QUEENBEE, + queens_chess_pieces=queens_chess_pieces, + MACD_WAVES=MACD_WAVES, + queens_master_tickers=[ticker], + reset_only=reset_only, + ) + else: + queen_workers = init_QueenWorkersBees( + QUEENBEE=QUEENBEE, + queens_chess_pieces=queens_chess_pieces, + MACD_WAVES=MACD_WAVES, + queens_master_tickers=queens_master_tickers, + reset_only=reset_only, + ) if reset_only: msg=("EXITING RESET ONLY") print(msg) diff --git a/master_ozz/utils.py b/master_ozz/utils.py index 08b28289..8c475f18 100644 --- a/master_ozz/utils.py +++ b/master_ozz/utils.py @@ -14,9 +14,25 @@ import requests from PIL import Image -from elevenlabs import set_api_key -from elevenlabs import Voice, VoiceSettings, generate -from elevenlabs import save +# from elevenlabs import set_api_key +# from elevenlabs import Voice, VoiceSettings, generate +# from elevenlabs import save + +# Elevenlabs functions - commented out due to pydantic compatibility issues +def set_api_key(key): + pass + +def Voice(voice_id, settings=None): + return None + +def VoiceSettings(stability=0.5, similarity_boost=0.5, style=0.0, use_speaker_boost=True): + return None + +def generate(text, voice=None, model=None, stream=False): + return b"" # Return empty bytes + +def save(audio, filename): + pass from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.vectorstores.faiss import FAISS @@ -48,7 +64,7 @@ from chess_piece.pollen_db import PollenDatabase from chess_piece.king import hive_master_root, hive_master_root_db -pg_migration = os.getenv('pg_migration') +pg_migration = os.getenv('pg_migration', 'False').lower() == 'true' # from youtubesearchpython import * #### AUTH UTILS ##### @@ -1220,4 +1236,3 @@ def init_stories(): return stories - diff --git a/pages/chessboard.py b/pages/chessboard.py index 49c285ff..46fb1a2c 100644 --- a/pages/chessboard.py +++ b/pages/chessboard.py @@ -16,7 +16,8 @@ from pq_auth import signin_main from chess_piece.king import return_QUEEN_KING_symbols, master_swarm_QUEENBEE, local__filepaths_misc, print_line_of_error, ReadPickleData, PickleData, return_QUEENs__symbols_data, kingdom__global_vars -from chess_piece.queen_hive import star_names, kingdom__grace_to_find_a_Queen, pollen_themes, init_qcp_workerbees, generate_chessboards_trading_models, return_queen_controls, shape_chess_board, generate_chess_board, refresh_account_info, init_queenbee, unshape_chess_board, setup_chess_board, add_trading_model, set_chess_pieces_symbols, read_swarm_db, refresh_broker_account_portolfio +from chess_piece.queen_hive import star_names, kingdom__grace_to_find_a_Queen, pollen_themes, init_qcp_workerbees, generate_chessboards_trading_models, return_queen_controls, shape_chess_board, generate_chess_board, refresh_account_info, init_queenbee, unshape_chess_board, setup_chess_board, add_trading_model, set_chess_pieces_symbols, read_swarm_db, refresh_broker_account_portolfio,init_swarm_dbs + from chess_piece.queen_mind import refresh_chess_board__revrec, init_qcp from custom_button import cust_Button from custom_grid import st_custom_grid, GridOptionsBuilder @@ -551,12 +552,22 @@ def chessboard(revrec, QUEEN_KING, ticker_allowed, themes, admin=False, qcp_bees st.write("# Setup Your Portfolio, Try Selecting a Hedge Fund and Edit from there ! :star2:") st.write(":warning: Symbols in the same Group will share a Budget - You can edit Exact Amounts Later :gear:") - hedge_funds = PollenDatabase.retrieve_data('db_sandbox', 'whalewisdom').get('latest_filer_holdings') - # print(len(hedge_funds)) - # for fund in hedge_funds: - # print(len(fund)) - - hedge_fund_names = list(set(hedge_funds['filer_name'].tolist())) + # Handle missing whalewisdom data with fallback + try: + whalewisdom_data = PollenDatabase.retrieve_data('db_sandbox', 'whalewisdom') + if whalewisdom_data and 'latest_filer_holdings' in whalewisdom_data: + hedge_funds = whalewisdom_data.get('latest_filer_holdings') + if hedge_funds is not None and not hedge_funds.empty and 'filer_name' in hedge_funds.columns: + hedge_fund_names = list(set(hedge_funds['filer_name'].tolist())) + else: + print("Warning: whalewisdom data is empty or missing filer_name column") + hedge_fund_names = [] + else: + print("Warning: whalewisdom data not found or invalid") + hedge_fund_names = [] + except Exception as e: + print(f"Error loading whalewisdom data: {e}") + hedge_fund_names = [] all_portfolios = ['Queen'] save_as_main_chessboard = st.sidebar.checkbox("Save as Main Chessboard", True) @@ -576,18 +587,28 @@ def chessboard(revrec, QUEEN_KING, ticker_allowed, themes, admin=False, qcp_bees optoins.append({'id': op, 'icon': "fas fa-chess-pawn", 'label':op}) # chessboard_selection = hc.option_bar(option_definition=optoins, title='Queen is Your Portfolio', key='chessboard_selections', horizontal_orientation=True) #,override_theme=over_theme,font_styling=font_fmt,horizontal_orientation=True) - chessboard_selection = st.selectbox("Select Portfolio", [None] + hedge_fund_names) + # Create portfolio options with fallback + portfolio_options = ['Queen'] + if hedge_fund_names: + portfolio_options.extend(hedge_fund_names) + + chessboard_selection = st.selectbox("Select Portfolio", [None] + portfolio_options) if not chessboard_selection: chessboard_selection = 'Queen' if chessboard_selection == 'Queen': pass - if chessboard_selection in hedge_fund_names: + if chessboard_selection in hedge_fund_names and 'hedge_funds' in locals() and hedge_funds is not None: if save_as_main_chessboard == False: qcp_bees_key = chessboard_selection if qcp_bees_key not in QUEEN_KING.keys(): QUEEN_KING[qcp_bees_key] = {} # data = hedge_funds.get(chessboard_selection) - data = hedge_funds[hedge_funds['filer_name'] == chessboard_selection] + try: + data = hedge_funds[hedge_funds['filer_name'] == chessboard_selection] + except Exception as e: + print(f"Error filtering hedge fund data: {e}") + st.error("Error loading hedge fund data") + return data = data.set_index('stock_ticker', drop=False) data = data.replace('DROPME', .001) data['current_percent_of_portfolio'] = pd.to_numeric(data['current_percent_of_portfolio'], errors='coerce') @@ -766,6 +787,7 @@ def chessboard(revrec, QUEEN_KING, ticker_allowed, themes, admin=False, qcp_bees # update board if __name__ == '__main__': + init_swarm_dbs(prod=False) signin_main() @@ -797,7 +819,12 @@ def chessboard(revrec, QUEEN_KING, ticker_allowed, themes, admin=False, qcp_bees # st.write(QUEEN_KING['chess_board']) - ticker_allowed = KING['alpaca_symbols_df'].index.tolist() + # Handle missing alpaca_symbols_df with fallback + if 'alpaca_symbols_df' in KING and not KING['alpaca_symbols_df'].empty: + ticker_allowed = KING['alpaca_symbols_df'].index.tolist() + else: + print("Warning: alpaca_symbols_df not found or empty, using fallback symbols") + ticker_allowed = ['SPY', 'QQQ', 'AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'NVDA', 'META', 'NFLX', 'BTC/USD', 'ETH/USD'] alpaca_acct_info = refresh_account_info(api=api) with st.sidebar: @@ -829,4 +856,4 @@ def chessboard(revrec, QUEEN_KING, ticker_allowed, themes, admin=False, qcp_bees # if admin: if st.button("Reset RevRec"): - first_revrec_setup(QUEEN_KING) + first_revrec_setup(QUEEN_KING) \ No newline at end of file diff --git a/pages/conscience.py b/pages/conscience.py index 538d948f..a4c24da7 100644 --- a/pages/conscience.py +++ b/pages/conscience.py @@ -11,13 +11,12 @@ from chess_piece.app_hive import sac_tabs, symbols_unique_color, log_grid, create_ag_grid_column, wave_grid, mark_down_text, mark_down_text, page_line_seperator, local_gif, flying_bee_gif from chess_piece.king import hive_master_root, kingdom__global_vars, streamlit_config_colors, print_line_of_error, return_QUEENs__symbols_data, return_QUEEN_KING_symbols -from chess_piece.queen_hive import create_TrigRule, kingdom__grace_to_find_a_Queen, star_names, return_queenking_board_symbols, sell_button_dict_items, hive_dates, return_market_hours, init_logging, bishop_ticker_info, init_queenbee, star_refresh_star_times +from chess_piece.queen_hive import fetch_portfolio_history, kingdom__grace_to_find_a_Queen, star_names, return_queenking_board_symbols, sell_button_dict_items, hive_dates, return_market_hours, init_logging, bishop_ticker_info, init_queenbee, star_refresh_star_times from chess_piece.pollen_db import PollenDatabase from chess_utils.conscience_utils import buy_button_dict_items, add_symbol_dict_items from pq_auth import signin_main from pages.PortfolioManager import ozz -# from custom_grid import st_custom_grid, GridOptionsBuilder, JsCode -from streamlit_custom_api_grid import st_custom_grid, GridOptionsBuilder, JsCode +from custom_grid import st_custom_grid, GridOptionsBuilder, JsCode from custom_graph_v1 import st_custom_graph @@ -705,6 +704,14 @@ def story_grid(prod, client_user, ip_address, revrec, symbols, refresh_sec=8, pa king_G = kingdom__global_vars() try: + # Check if revrec is None or doesn't have required data + if revrec is None: + st.warning("RevRec data not available. Please refresh the page or check your connection.") + return + + if not hasattr(revrec, 'get') or 'storygauge' not in revrec: + st.warning("RevRec data is incomplete. Please refresh the page or check your connection.") + return gb = GridOptionsBuilder.create() gb.configure_grid_options(pagination=True, paginationPageSize=100, @@ -769,7 +776,7 @@ def story_grid_buttons(hf=False): buttons = [] exclude_buy_kors = ['reverse_buy', 'sell_trigbee_trigger_timeduration'] buttons=[ - {'button_name': None, # MAIN SYMBOL BUTTON + {'button_name': None, 'button_api': f'{ip_address}/api/data/update_queenking_symbol', 'prompt_message': 'Manage Board', 'prompt_field': 'add_symbol_option', @@ -778,13 +785,9 @@ def story_grid_buttons(hf=False): 'col_width':100, 'sortable': True, 'pinned': 'left', - 'prompt_order_rules': [i for i, v in add_symbol_dict_items().items() if v is not None], + 'prompt_order_rules': [i for i in add_symbol_dict_items().keys()], 'cellStyle': button_style_symbol, - 'display_grid_column': 'trig_rules', - 'editableCols': [ {'col_header': i, 'display_name': i} for i in create_TrigRule().keys()], - - 'cellRenderer': "agGroupCellRenderer", - 'pivot': True, + 'cellRenderer': "agGroupCellRenderer" # 'add_symbol_row_info': ['star_buys_at_play', 'allocation_long', 'current_ask', 'ticker_total_budget', 'ticker_remaining_budget', 'ticker_remaining_borrow'], # 'display_grid_column': 'active_orders', # 'editableCols': ['allocation_long'], @@ -857,30 +860,11 @@ def story_grid_buttons(hf=False): { 'col_header': "close_order_today", 'dtype': "checkbox" }, { 'col_header': "ignore_allocation_budget", 'dtype': "checkbox", "display_name": "Ignore Allocation Deploy"}, { 'col_header': "confirm_sell", 'dtype': "checkbox", "display_name": "Confirm Sell / Update Order"}, - { 'col_header': "queen_order_state", 'dtype': "list", "display_name": "queen_order_state", "values": king_G.get('active_order_state_list'), "multi_select": True}, + { 'col_header': "queen_order_state", 'dtype': "list", "display_name": "queen_order_state", "values": king_G.get('active_order_state_list')}, ], 'display_grid_column': 'active_orders', }, - # """ WORKERBEE NOT SURE why the button doesnt behave correctly does not use Modal??""" - # {'button_name': None, # TrigRules - # 'col_headername': 'Trig Rules', - # 'button_api': f'{ip_address}/api/data/trig_rules', - # 'prompt_message': 'Trigger Rules', - # 'prompt_field': 'sell_option', #'symbol', # 'trig_rules', - # 'prompt_order_rules': [i for i in sell_button_dict_items().keys()], - # 'display_grid_column': 'trig_rules', - # "col_header": "symbol", - # 'col_width':100, - # 'sortable': True, - # 'pinned': 'right', - # # 'cellStyle': button_style_symbol, - # # 'cellRenderer': "agGroupCellRenderer" - # # 'add_symbol_row_info': ['star_buys_at_play', 'allocation_long', 'current_ask', 'ticker_total_budget', 'ticker_remaining_budget', 'ticker_remaining_borrow'], - # 'editableCols': [ {'col_header': i, 'display_name': i} for i in create_TrigRule().keys()], - # }, - - {'button_name': None, 'button_api': f'{ip_address}/api/data/update_buy_autopilot', 'prompt_message': 'Edit AutoPilot', @@ -944,11 +928,10 @@ def story_grid_buttons(hf=False): return buttons - def config_cols(qcp_piece_names): - df_ticker_qcp_names = qcp_piece_names + def config_cols(df_qcp): + df_ticker_qcp_names = df_qcp['piece_name'].tolist() configg = { # for col in cols: - 'trig_rules': {"hide": True}, # 'symbol': {'headerName':'Symbol', 'initialWidth':89, 'pinned': 'left', 'sortable':'true',}, 'current_from_yesterday': {'headerName':'% Change', 'sortable':'true', 'cellStyle': honey_colors, @@ -1009,21 +992,28 @@ def config_cols(qcp_piece_names): story_col_order = [ - 'queens_suggested_buy', - 'queens_suggested_sell', 'current_from_yesterday', 'pct_portfolio', 'star_buys_at_play', 'total_budget', 'allocation_long', + 'queens_suggested_buy', 'unrealized_pl', 'unrealized_plpc', + 'queens_suggested_sell', 'piece_name', 'buy_autopilot', 'sell_autopilot', # 'current_ask', ] - + # with st.expander("default build check"): + # st.write(go) + # QUEENsHeart = init_queenbee(client_user=client_user, prod=prod, queen_heart=True, pg_migration=True) + # df_broker_portfolio=pd.DataFrame(QUEENsHeart['heartbeat'].get('portfolio')).T + # missing_tickers = [i for i in df_broker_portfolio.index if i not in revrec['df_ticker'].index] + # if missing_tickers: + # print("tickers missing", missing_tickers) + # QUEEN_KING[chess_board]['non_active_stories'] = init_qcp_workerbees(piece_name='non_active_stories', ticker_list=missing_tickers, buying_power=0) toggle_view = [] if client_user == 'stefanstapinski@gmail.com': main_toggles = ["Portfolio", "King", '2025_Screen'] @@ -1085,8 +1075,7 @@ def config_cols(qcp_piece_names): # if len(QUEEN_KING['chess_board'][qcp].get('tickers', [])) > 0 # ] story_col = revrec.get('storygauge').columns.tolist() - qcp_piece_names = revrec.get('storygauge')['piece_name'].unique().tolist() if 'piece_name' in revrec.get('storygauge').columns else [] - config_cols_ = config_cols(qcp_piece_names) + config_cols_ = config_cols(revrec.get('df_qcp')) mmissing = [i for i in story_col if i not in config_cols_.keys()] if len(mmissing) > 0: for col in mmissing: @@ -1426,4 +1415,4 @@ def trinity_graph(): # st.write({data.get('piece_name'): qcp for qcp, data in QUEEN_KING['chess_board'].items() }) api = qb.get('api') revrec = qb.get('revrec') - queens_conscience(prod, revrec, KING, QUEEN_KING, api) + queens_conscience(prod, revrec, KING, QUEEN_KING, api) \ No newline at end of file diff --git a/pages/options.py b/pages/options.py new file mode 100644 index 00000000..a7854f16 --- /dev/null +++ b/pages/options.py @@ -0,0 +1,828 @@ +import pandas as pd +import numpy as np +import streamlit as st +import yfinance as yf +from datetime import datetime, timedelta +import pytz +import os +from dotenv import load_dotenv +import alpaca_trade_api as tradeapi +from chess_piece.queen_hive import init_queenbee, refresh_account_info +from chess_piece.pollen_db import PollenDatabase +from chess_piece.king import hive_master_root +from pq_auth import signin_main +import logging + +# Set page config FIRST - before any other Streamlit commands +st.set_page_config( + page_title="Options Trading", + page_icon="📈", + layout="wide" +) + +# Set up logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Load environment variables +main_root = hive_master_root() +load_dotenv(os.path.join(main_root, ".env")) + +# Set pandas options +pd.options.mode.chained_assignment = None + +# Timezone setup +est = pytz.timezone("US/Eastern") + +# ----------------------- +# Options utils (Greeks, OCC symbol) +# ----------------------- + +def _norm_cdf(x: float) -> float: + return 0.5 * (1.0 + np.math.erf(x / np.sqrt(2))) + +def _norm_pdf(x: float) -> float: + return (1.0 / np.sqrt(2 * np.pi)) * np.exp(-0.5 * x * x) + +def compute_black_scholes_greeks(spot_price: float, strike_price: float, time_years: float, risk_free_rate: float, implied_vol: float, option_type: str) -> dict: + if spot_price <= 0 or strike_price <= 0 or time_years <= 0 or implied_vol <= 0: + return {"delta": np.nan, "gamma": np.nan, "theta": np.nan, "vega": np.nan} + sigma = implied_vol + r = risk_free_rate + S = spot_price + K = strike_price + T = time_years + d1 = (np.log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * np.sqrt(T)) + d2 = d1 - sigma * np.sqrt(T) + if option_type.lower() == "call": + delta = _norm_cdf(d1) + theta = (-(S * _norm_pdf(d1) * sigma) / (2 * np.sqrt(T)) - r * K * np.exp(-r * T) * _norm_cdf(d2)) / 365 + else: + delta = _norm_cdf(d1) - 1 + theta = (-(S * _norm_pdf(d1) * sigma) / (2 * np.sqrt(T)) + r * K * np.exp(-r * T) * _norm_cdf(-d2)) / 365 + gamma = _norm_pdf(d1) / (S * sigma * np.sqrt(T)) + vega = (S * _norm_pdf(d1) * np.sqrt(T)) / 100 + return {"delta": float(delta), "gamma": float(gamma), "theta": float(theta), "vega": float(vega)} + +def construct_occ_symbol(underlying: str, expiration_yyyy_mm_dd: str, strike: float, option_type: str) -> str: + # OCC: Underlying 6 chars (left-justified, space-padded), YYMMDD, C/P, strike (8 digits, 3 decimals implied) + und = (underlying[:6]).ljust(6) + y, m, d = expiration_yyyy_mm_dd.split("-") + yy = y[2:] + cp = 'C' if option_type.lower().startswith('c') else 'P' + strike_scaled = int(round(strike * 1000)) + strike_str = f"{strike_scaled:08d}" + return f"{und}{yy}{m}{d}{cp}{strike_str}" + +# Check authentication +if 'authorized_user' not in st.session_state: + signin_main("options") + +if st.session_state["authorized_user"] != True: + st.error("You do not have permissions to access this page") + st.stop() + +def get_alpaca_api(): + """Initialize and return Alpaca API connection""" + try: + api_key = os.getenv('ALPACA_API_KEY') + api_secret = os.getenv('ALPACA_SECRET_KEY') + base_url = os.getenv('ALPACA_BASE_URL', 'https://paper-api.alpaca.markets') + + if not api_key or not api_secret: + st.error("Alpaca API credentials not found. Please check your environment variables.") + return None + + api = tradeapi.REST(api_key, api_secret, base_url, api_version='v2') + return api + except Exception as e: + st.error(f"Failed to initialize Alpaca API: {e}") + return None + +def fetch_options_chain(ticker, expiration_date=None): + """Fetch options chain for a given ticker""" + try: + stock = yf.Ticker(ticker) + + # Get options expiration dates + expirations = stock.options + if not expirations: + st.warning(f"No options data available for {ticker}") + return None, None + + # Use provided expiration or the first available + if expiration_date is None: + expiration_date = expirations[0] + elif expiration_date not in expirations: + st.warning(f"Expiration date {expiration_date} not available. Using {expirations[0]}") + expiration_date = expirations[0] + + # Get options chain + options_chain = stock.option_chain(expiration_date) + + return options_chain, expiration_date + except Exception as e: + st.error(f"Error fetching options chain for {ticker}: {e}") + return None, None + +def filter_options_by_timeframe(options_chain, timeframe): + """Filter options by timeframe (1D, 5D, 1M, 3M, 6M, 1Y)""" + if not options_chain: + return None, None + + calls = options_chain.calls + puts = options_chain.puts + + # Calculate days to expiration + current_date = datetime.now(est).date() + expiration_date = pd.to_datetime(options_chain.calls['expiration'].iloc[0]).date() + days_to_exp = (expiration_date - current_date).days + + # Filter based on timeframe + timeframe_days = { + '1D': 1, + '5D': 5, + '1M': 30, + '3M': 90, + '6M': 180, + '1Y': 365 + } + + target_days = timeframe_days.get(timeframe, 30) + + # For now, return all options (in a real implementation, you'd filter by expiration) + return calls, puts + +def generate_option_suggestions(calls, puts, current_price, strategy_type="call", expiration_date: str = None, risk_free_rate: float = 0.02): + """Generate simple option suggestions based on current price and strategy""" + suggestions = [] + + if strategy_type == "call": + # Find calls near the money + near_money_calls = calls[ + (calls['strike'] >= current_price * 0.95) & + (calls['strike'] <= current_price * 1.05) + ].head(5) + + for _, option in near_money_calls.iterrows(): + iv = float(option.get('impliedVolatility', np.nan)) + greeks = compute_black_scholes_greeks( + spot_price=float(current_price), + strike_price=float(option['strike']), + time_years=max(1/365, (pd.to_datetime(expiration_date) - datetime.now(est)).days / 365.0) if expiration_date else 30/365, + risk_free_rate=risk_free_rate, + implied_vol=float(iv) if iv < 10 else iv/100.0, + option_type="call", + ) + suggestions.append({ + 'type': 'Call', + 'strike': option['strike'], + 'bid': option['bid'], + 'ask': option['ask'], + 'volume': option['volume'], + 'open_interest': option['openInterest'], + 'implied_volatility': option['impliedVolatility'], + **greeks + }) + + elif strategy_type == "put": + # Find puts near the money + near_money_puts = puts[ + (puts['strike'] >= current_price * 0.95) & + (puts['strike'] <= current_price * 1.05) + ].head(5) + + for _, option in near_money_puts.iterrows(): + iv = float(option.get('impliedVolatility', np.nan)) + greeks = compute_black_scholes_greeks( + spot_price=float(current_price), + strike_price=float(option['strike']), + time_years=max(1/365, (pd.to_datetime(expiration_date) - datetime.now(est)).days / 365.0) if expiration_date else 30/365, + risk_free_rate=risk_free_rate, + implied_vol=float(iv) if iv < 10 else iv/100.0, + option_type="put", + ) + suggestions.append({ + 'type': 'Put', + 'strike': option['strike'], + 'bid': option['bid'], + 'ask': option['ask'], + 'volume': option['volume'], + 'open_interest': option['openInterest'], + 'implied_volatility': option['impliedVolatility'], + **greeks + }) + + return suggestions + +def place_option_order(api, symbol, option_symbol, quantity, side, order_type="market", limit_price=None): + """Place an options order through Alpaca API""" + try: + if order_type == "market": + order = api.submit_order( + symbol=option_symbol, + qty=quantity, + side=side, + type="market", + time_in_force="gtc" + ) + elif order_type == "limit": + if not limit_price: + st.error("Limit price required for limit orders") + return None + order = api.submit_order( + symbol=option_symbol, + qty=quantity, + side=side, + type="limit", + time_in_force="gtc", + limit_price=limit_price + ) + + return order + except Exception as e: + st.error(f"Error placing order: {e}") + return None + +def save_option_order(order_data, client_user, prod=False): + """Save option order to database using existing PollenDatabase framework""" + try: + # Create a comprehensive order record + order_record = { + 'order_id': order_data.get('id'), + 'symbol': order_data.get('symbol'), + 'side': order_data.get('side'), + 'quantity': order_data.get('qty'), + 'order_type': order_data.get('order_type'), + 'status': order_data.get('status'), + 'created_at': datetime.now(est).isoformat(), + 'client_order_id': order_data.get('client_order_id'), + 'client_user': client_user, + 'prod': prod, + 'order_class': 'option', + 'asset_class': 'option' + } + + # Use existing PollenDatabase framework + table_name = 'option_orders' if prod else 'option_orders_sandbox' + + # Create table if it doesn't exist + PollenDatabase.create_table_if_not_exists(table_name) + + # Save the order + PollenDatabase.upsert_data(table_name, order_data.get('id'), order_record) + + logger.info(f"Option order saved: {order_record}") + return True + except Exception as e: + logger.error(f"Error saving option order: {e}") + return False + +def get_option_orders(client_user, prod=False): + """Retrieve option orders from database""" + try: + table_name = 'option_orders' if prod else 'option_orders_sandbox' + + # This is a simplified retrieval - in practice you'd want more sophisticated querying + # For now, we'll return the session state orders + return st.session_state.get('option_orders', []) + except Exception as e: + logger.error(f"Error retrieving option orders: {e}") + return [] + +def fetch_positions(api): + """Fetch open option positions from Alpaca""" + try: + positions = api.list_positions() + option_positions = [p for p in positions if p.asset_class == 'option'] + return option_positions + except Exception as e: + logger.error(f"Error fetching positions: {e}") + return [] + +def cancel_order(api, order_id): + """Cancel an order by ID""" + try: + api.cancel_order(order_id) + return True + except Exception as e: + logger.error(f"Error canceling order {order_id}: {e}") + return False + +def get_order_status(api, order_id): + """Get order status by ID""" + try: + order = api.get_order(order_id) + return { + 'id': order.id, + 'status': order.status, + 'side': order.side, + 'symbol': order.symbol, + 'qty': order.qty, + 'filled_qty': order.filled_qty, + 'filled_avg_price': order.filled_avg_price, + 'created_at': order.created_at, + 'updated_at': order.updated_at + } + except Exception as e: + logger.error(f"Error getting order status {order_id}: {e}") + return None + +def check_risk_limits(api, order_side, quantity, limit_price=None, current_price=None): + """Basic risk checks for option orders""" + try: + account = refresh_account_info(api=api) + buying_power = float(account.get('info_converted', {}).get('buying_power', 0)) + cash = float(account.get('info_converted', {}).get('cash', 0)) + + # Estimate order value + if limit_price: + order_value = limit_price * quantity * 100 # Options are 100 shares per contract + elif current_price: + order_value = current_price * quantity * 100 + else: + order_value = 0 + + # Basic checks + if order_side == 'buy' and order_value > buying_power: + return False, f"Order value ${order_value:,.2f} exceeds buying power ${buying_power:,.2f}" + + if order_side == 'sell' and order_value > cash: + return False, f"Order value ${order_value:,.2f} exceeds cash ${cash:,.2f}" + + # Max order size check + max_order_value = min(buying_power * 0.1, 10000) # 10% of buying power or $10k max + if order_value > max_order_value: + return False, f"Order value ${order_value:,.2f} exceeds max allowed ${max_order_value:,.2f}" + + return True, "Risk checks passed" + except Exception as e: + logger.error(f"Error in risk checks: {e}") + return False, f"Risk check error: {e}" + +def main(): + + st.title("📈 Options Trading Dashboard") + st.markdown("---") + + # Get user and environment info + client_user = st.session_state.get('username', 'unknown') + prod = st.session_state.get('prod', False) + + # Display environment info + env_text = "Live Account" if prod else "Sandbox Account" + st.info(f"Environment: {env_text} | User: {client_user}") + + # Initialize session state + if 'option_orders' not in st.session_state: + st.session_state['option_orders'] = [] + + # Initialize queen bee for API access + try: + qb = init_queenbee(client_user=client_user, prod=prod, queen_king=True, api=True, init=True) + api = qb.get('api') + QUEEN_KING = qb.get('QUEEN_KING') + + if not api: + st.error("Failed to initialize API connection. Please check your credentials.") + st.stop() + + # Display account info + with st.expander("Account Information", expanded=False): + try: + account_info = refresh_account_info(api=api) + if account_info: + # Debug: show what we received + st.write("Debug - Account info keys:", list(account_info.keys()) if isinstance(account_info, dict) else "Not a dict") + + # Use the converted info which has proper numeric formatting + converted_info = account_info.get('info_converted', {}) + if converted_info: + st.json(converted_info) + else: + # Fallback: display basic account info in a readable format + raw_info = account_info.get('info', {}) + if hasattr(raw_info, '__dict__'): + # Extract basic info from the account object + basic_info = { + 'account_number': getattr(raw_info, 'account_number', 'N/A'), + 'status': getattr(raw_info, 'status', 'N/A'), + 'currency': getattr(raw_info, 'currency', 'N/A'), + 'buying_power': getattr(raw_info, 'buying_power', 'N/A'), + 'cash': getattr(raw_info, 'cash', 'N/A'), + 'portfolio_value': getattr(raw_info, 'portfolio_value', 'N/A') + } + st.json(basic_info) + else: + st.warning("Account info format not recognized") + else: + st.warning("No account information received") + except Exception as e: + st.warning(f"Could not fetch account info: {e}") + st.write("Full error:", str(e)) + + except Exception as e: + st.error(f"Failed to initialize trading environment: {e}") + st.stop() + + # Sidebar for input parameters + with st.sidebar: + st.header("Options Parameters") + + # Ticker input + ticker = st.text_input("Enter Ticker Symbol", value="AAPL", help="Enter the stock symbol (e.g., AAPL, TSLA)") + + # Timeframe selection + timeframe = st.selectbox( + "Select Timeframe", + options=["1D", "5D", "1M", "3M", "6M", "1Y"], + index=2, # Default to 1M + help="Filter options by expiration timeframe" + ) + + # Strategy type + strategy_type = st.selectbox( + "Strategy Type", + options=["call", "put"], + help="Choose between call or put options" + ) + + # Fetch options button + fetch_options = st.button("Fetch Options Chain", type="primary") + + # Main content area + if fetch_options and ticker: + with st.spinner(f"Fetching options data for {ticker}..."): + # Get current stock price + try: + stock = yf.Ticker(ticker) + current_price = stock.history(period="1d")['Close'].iloc[-1] + st.success(f"Current {ticker} price: ${current_price:.2f}") + except Exception as e: + st.error(f"Could not fetch current price for {ticker}: {e}") + current_price = None + + # Fetch options chain + options_chain, expiration_date = fetch_options_chain(ticker) + + if options_chain: + st.success(f"Options chain loaded for expiration: {expiration_date}") + + # Filter options by timeframe + calls, puts = filter_options_by_timeframe(options_chain, timeframe) + + if calls is not None and puts is not None: + # Display options data + col1, col2 = st.columns(2) + + with col1: + st.subheader("📞 Call Options") + if not calls.empty: + # Enrich with Greeks (approx) + T_years = max(1/365, (pd.to_datetime(expiration_date) - datetime.now(est)).days / 365.0) + def _add_call_greeks(row): + g = compute_black_scholes_greeks( + spot_price=float(current_price), + strike_price=float(row['strike']), + time_years=T_years, + risk_free_rate=0.02, + implied_vol=float(row['impliedVolatility']) if row['impliedVolatility'] < 10 else row['impliedVolatility']/100.0, + option_type='call' + ) + return pd.Series(g) + calls_enriched = calls.copy() + try: + calls_enriched[['delta','gamma','theta','vega']] = calls_enriched.apply(_add_call_greeks, axis=1) + except Exception: + pass + display_calls = calls_enriched[['strike', 'bid', 'ask', 'volume', 'openInterest', 'impliedVolatility', 'delta','gamma','theta','vega']].head(10) + st.dataframe(display_calls, use_container_width=True) + else: + st.info("No call options available") + + with col2: + st.subheader("📉 Put Options") + if not puts.empty: + # Enrich with Greeks (approx) + T_years = max(1/365, (pd.to_datetime(expiration_date) - datetime.now(est)).days / 365.0) + def _add_put_greeks(row): + g = compute_black_scholes_greeks( + spot_price=float(current_price), + strike_price=float(row['strike']), + time_years=T_years, + risk_free_rate=0.02, + implied_vol=float(row['impliedVolatility']) if row['impliedVolatility'] < 10 else row['impliedVolatility']/100.0, + option_type='put' + ) + return pd.Series(g) + puts_enriched = puts.copy() + try: + puts_enriched[['delta','gamma','theta','vega']] = puts_enriched.apply(_add_put_greeks, axis=1) + except Exception: + pass + display_puts = puts_enriched[['strike', 'bid', 'ask', 'volume', 'openInterest', 'impliedVolatility', 'delta','gamma','theta','vega']].head(10) + st.dataframe(display_puts, use_container_width=True) + else: + st.info("No put options available") + + # Generate suggestions + if current_price: + st.subheader("💡 Option Suggestions") + suggestions = generate_option_suggestions(calls, puts, current_price, strategy_type, expiration_date) + + if suggestions: + suggestions_df = pd.DataFrame(suggestions) + st.dataframe(suggestions_df, use_container_width=True) + + # Trading interface + st.subheader("🛒 Place Order") + + with st.form("option_order_form"): + col1, col2, col3 = st.columns(3) + + with col1: + selected_option = st.selectbox( + "Select Option", + options=[f"{opt['type']} ${opt['strike']:.0f}" for opt in suggestions], + help="Choose an option from the suggestions" + ) + + with col2: + quantity = st.number_input("Quantity", min_value=1, value=1, help="Number of contracts") + + with col3: + order_side = st.selectbox("Side", options=["buy", "sell"], help="Buy or sell the option") + + order_type = st.selectbox( + "Order Type", + options=["market", "limit"], + help="Market order executes immediately, limit order sets a price" + ) + + limit_price = None + if order_type == "limit": + limit_price = st.number_input("Limit Price", min_value=0.01, value=0.01, step=0.01) + + submit_order = st.form_submit_button("Place Order", type="primary") + + if submit_order: + # Get the selected option details + option_index = [f"{opt['type']} ${opt['strike']:.0f}" for opt in suggestions].index(selected_option) + selected_option_data = suggestions[option_index] + + # Risk checks + current_price = (selected_option_data['bid'] + selected_option_data['ask']) / 2 + risk_ok, risk_message = check_risk_limits( + api=api, + order_side=order_side, + quantity=quantity, + limit_price=limit_price, + current_price=current_price + ) + + if not risk_ok: + st.error(f"Risk check failed: {risk_message}") + else: + st.info(f"Risk check passed: {risk_message}") + + # Create proper OCC option symbol + option_symbol = construct_occ_symbol( + underlying=ticker, + expiration_yyyy_mm_dd=expiration_date, + strike=float(selected_option_data['strike']), + option_type='Call' if selected_option_data['type'] == 'Call' else 'Put' + ) + + # Place the order + with st.spinner("Placing order..."): + order = place_option_order( + api=api, + symbol=ticker, + option_symbol=option_symbol, + quantity=quantity, + side=order_side, + order_type=order_type, + limit_price=limit_price + ) + + if order: + st.success("Order placed successfully!") + + # Display order details + order_details = { + 'order_id': order.id, + 'symbol': order.symbol, + 'side': order.side, + 'quantity': order.qty, + 'status': order.status, + 'order_type': order_type, + 'created_at': datetime.now(est).strftime("%Y-%m-%d %H:%M:%S") + } + + st.json(order_details) + + # Save order to database + order_data = { + 'id': order.id, + 'symbol': order.symbol, + 'side': order.side, + 'qty': order.qty, + 'order_type': order_type, + 'status': order.status, + 'client_order_id': order.client_order_id + } + + if save_option_order(order_data, client_user, prod): + st.success("Order saved to database") + else: + st.warning("Order placed but not saved to database") + + # Add to session state + st.session_state['option_orders'].append(order_details) + + # Log the test order for validation + logger.info(f"TEST ORDER PLACED: {order_details}") + st.info("📝 Test order logged for validation") + else: + st.error("Failed to place order") + else: + st.info("No suggestions available for the selected parameters") + + # ----------------------- + # Basic Vertical Spread (Debit Call) + # ----------------------- + st.subheader("🧩 Vertical Spread (Debit Call)") + try: + call_strikes = sorted(list(calls['strike'].unique())) + col_a, col_b, col_c = st.columns(3) + with col_a: + lower_strike = st.selectbox("Buy Strike (lower)", options=call_strikes) + with col_b: + higher_strike = st.selectbox("Sell Strike (higher)", options=[s for s in call_strikes if s > lower_strike]) + with col_c: + spread_qty = st.number_input("Quantity", min_value=1, value=1) + + # Mid prices + buy_row = calls[calls['strike'] == lower_strike].head(1) + sell_row = calls[calls['strike'] == higher_strike].head(1) + if not buy_row.empty and not sell_row.empty: + buy_mid = float(buy_row['bid'].iloc[0] + buy_row['ask'].iloc[0]) / 2.0 + sell_mid = float(sell_row['bid'].iloc[0] + sell_row['ask'].iloc[0]) / 2.0 + net_debit = (buy_mid - sell_mid) * 100 * spread_qty + st.info(f"Estimated net debit: ${net_debit:,.2f} for {spread_qty} spread(s)") + + if st.button("Place Debit Call Vertical"): + # Basic budget check via account info + try: + acct = refresh_account_info(api=api) + buying_power = float(acct.get('info_converted', {}).get('buying_power', 0)) + except Exception: + buying_power = 0 + if buying_power and net_debit > buying_power: + st.error("Insufficient buying power for this spread.") + else: + with st.spinner("Placing spread legs..."): + buy_symbol = construct_occ_symbol(ticker, expiration_date, float(lower_strike), 'Call') + sell_symbol = construct_occ_symbol(ticker, expiration_date, float(higher_strike), 'Call') + # Leg 1: Buy lower strike call + leg1 = place_option_order( + api=api, + symbol=ticker, + option_symbol=buy_symbol, + quantity=spread_qty, + side="buy", + order_type="market" + ) + # Leg 2: Sell higher strike call + leg2 = place_option_order( + api=api, + symbol=ticker, + option_symbol=sell_symbol, + quantity=spread_qty, + side="sell", + order_type="market" + ) + if leg1 and leg2: + st.success("Spread placed (two legs submitted)") + else: + st.warning("One or more legs failed to submit") + except Exception as _e: + st.warning(f"Spread builder unavailable: {_e}") + else: + st.warning("No options data available for the selected timeframe") + else: + st.error("Failed to fetch options chain") + + # Display recent orders + if st.session_state['option_orders']: + st.subheader("📋 Recent Option Orders") + orders_df = pd.DataFrame(st.session_state['option_orders']) + st.dataframe(orders_df, use_container_width=True) + + col1, col2 = st.columns(2) + with col1: + if st.button("Clear Orders"): + st.session_state['option_orders'] = [] + st.rerun() + with col2: + if st.button("Refresh Orders"): + # In a real implementation, you'd fetch from database + st.info("Orders refreshed from session state") + else: + st.info("No option orders placed yet") + + # ----------------------- + # Positions View + # ----------------------- + st.subheader("📊 Open Positions") + try: + positions = fetch_positions(api) + if positions: + positions_data = [] + for pos in positions: + positions_data.append({ + 'symbol': pos.symbol, + 'qty': pos.qty, + 'side': pos.side, + 'market_value': pos.market_value, + 'cost_basis': pos.cost_basis, + 'unrealized_pl': pos.unrealized_pl, + 'unrealized_plpc': pos.unrealized_plpc, + 'asset_class': pos.asset_class + }) + + if positions_data: + positions_df = pd.DataFrame(positions_data) + st.dataframe(positions_df, use_container_width=True) + + # PnL Summary + total_pl = sum(float(p['unrealized_pl']) for p in positions_data) + total_market_value = sum(float(p['market_value']) for p in positions_data) + + col1, col2, col3 = st.columns(3) + with col1: + st.metric("Total P&L", f"${total_pl:,.2f}") + with col2: + st.metric("Total Market Value", f"${total_market_value:,.2f}") + with col3: + st.metric("Number of Positions", len(positions_data)) + else: + st.info("No open positions") + else: + st.info("No open positions") + except Exception as e: + st.warning(f"Could not fetch positions: {e}") + + # ----------------------- + # Order Management + # ----------------------- + st.subheader("🔧 Order Management") + + # Order status check + with st.expander("Check Order Status", expanded=False): + order_id = st.text_input("Enter Order ID") + if st.button("Get Status") and order_id: + with st.spinner("Fetching order status..."): + status = get_order_status(api, order_id) + if status: + st.json(status) + else: + st.error("Order not found or error occurred") + + # Cancel order + with st.expander("Cancel Order", expanded=False): + cancel_order_id = st.text_input("Enter Order ID to Cancel") + if st.button("Cancel Order") and cancel_order_id: + with st.spinner("Canceling order..."): + if cancel_order(api, cancel_order_id): + st.success("Order canceled successfully") + else: + st.error("Failed to cancel order") + + # ----------------------- + # Risk Management + # ----------------------- + st.subheader("⚠️ Risk Management") + + try: + account = refresh_account_info(api=api) + buying_power = float(account.get('info_converted', {}).get('buying_power', 0)) + cash = float(account.get('info_converted', {}).get('cash', 0)) + + col1, col2, col3 = st.columns(3) + with col1: + st.metric("Buying Power", f"${buying_power:,.2f}") + with col2: + st.metric("Cash", f"${cash:,.2f}") + with col3: + max_order = min(buying_power * 0.1, 10000) + st.metric("Max Order Size", f"${max_order:,.2f}") + + # Risk settings + st.info("Risk Limits: Max 10% of buying power per order, $10k maximum order size") + + except Exception as e: + st.warning(f"Could not load risk information: {e}") + +if __name__ == "__main__": + main() diff --git a/pages/postrgres.py b/pages/postrgres.py index 876696cf..fc1faefe 100644 --- a/pages/postrgres.py +++ b/pages/postrgres.py @@ -125,8 +125,8 @@ def copy_data_between_tables(source_table, target_table): if __name__ == '__main__': print("POSTGRES", pg_migration) + st.session_state['admin'] = True admin = st.session_state['admin'] - if not admin: st.error("Join The Team for Admin Access To This Page") st.stop() diff --git a/pollen.py b/pollen.py index 72fa3fa7..877a315a 100644 --- a/pollen.py +++ b/pollen.py @@ -43,7 +43,7 @@ import ipdb -pg_migration = os.getenv('pg_migration') +pg_migration = os.getenv('pg_migration', 'False').lower() == 'true' pd.options.mode.chained_assignment = None @@ -417,6 +417,18 @@ def get_portfolio_performance(_api, periods): return perf_dict def pollenq(sandbox=False, demo=False): + # Initialize session state variables if they don't exist + if "authentication_status" not in st.session_state: + st.session_state["authentication_status"] = None + if "username" not in st.session_state: + st.session_state["username"] = None + if "name" not in st.session_state: + st.session_state["name"] = None + if "logout" not in st.session_state: + st.session_state["logout"] = None + if "authorized_user" not in st.session_state: + st.session_state["authorized_user"] = False + # st.write("pollenq", demo, sandbox) # print("pollenq", demo, sandbox) pollen = 'pollen' if not demo else 'demo' @@ -765,4 +777,4 @@ def pollenq(sandbox=False, demo=False): # admin_pq = namespace.admin pollenq() - # st.write(st.session_state.items()) + # st.write(st.session_state.items()) \ No newline at end of file diff --git a/pq_auth.py b/pq_auth.py index 94a6e9fe..86df961b 100644 --- a/pq_auth.py +++ b/pq_auth.py @@ -17,7 +17,7 @@ main_root = hive_master_root() load_dotenv(os.path.join(main_root, ".env")) -pg_migration = os.getenv('pg_migration') +pg_migration = os.getenv('pg_migration', 'False').lower() == 'true' testing = False def register_user(authenticator, con, cur): @@ -409,4 +409,4 @@ def define_authorized_user(): if __name__ == "__main__": st.session_state["logout"] = True - signin_main(page='pollenq') + signin_main(page='pollenq') \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index dbab0061..a0b840eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ aiohttp==3.9.1 aiosignal==1.3.1 -alpaca-trade-api==2.3.0 +alpaca-trade-api==3.2.0 altair==4.2.2 annotated-types==0.6.0 anyio==3.7.1 @@ -40,7 +40,7 @@ distlib==0.3.9 docx==0.2.4 docx2txt==0.8 ecdsa==0.14.1 -elevenlabs==0.2.26 +# elevenlabs==0.2.8 # Commented out due to pydantic compatibility issues emoji==2.10.1 entrypoints==0.4 exceptiongroup==1.2.0 @@ -117,7 +117,7 @@ numpy==1.26.4 openai==0.27.8 packaging==23.2 pandas==2.0.3 -pandas-ta==0.3.14b0 +# pandas-ta==0.3.14b0 # Commented out due to dependency conflicts parso==0.8.3 passlib==1.7.4 peewee==3.17.8 @@ -141,8 +141,8 @@ pyarrow==14.0.1 pyasn1==0.4.8 pyasn1-modules==0.2.8 pycparser==2.21 -pydantic==2.5.2 -pydantic_core==2.14.5 +pydantic==2.3.0 +pydantic_core==2.6.3 pydeck==0.8.1b0 pydub==0.25.1 Pygments==2.17.2 @@ -238,7 +238,7 @@ watchdog==3.0.0 wcwidth==0.2.12 webencodings==0.5.1 websocket-client==1.4.1 -websockets==12.0 +websockets==10.4 Werkzeug==3.0.1 wrapt==1.16.0 yarl==1.9.4