from flask import Flask, request, jsonify from pyngrok import ngrok from alpaca_trade_api.rest import REST, TimeFrame import logging # Flask app setup app = Flask(__name__) # Setup logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') # Alpaca API credentials ALPACA_API_KEY = "PKVIYT9NC2P2GHCB43CJ" # Replace with your actual API Key ALPACA_API_SECRET = "pcnilPA8Zj2gO4pPs2Xth2io063gSymfX35moeac" # Replace with your actual Secret Key ALPACA_BASE_URL = "https://paper-api.alpaca.markets" # Initialize Alpaca API alpaca = REST(ALPACA_API_KEY, ALPACA_API_SECRET, base_url=ALPACA_BASE_URL) # Secret key for the webhook WEBHOOK_SECRET_KEY = "Yuvi23780557" def reformat_ticker(symbol): """Reformat the ticker symbol to remove any exchange prefix.""" return symbol.split(':')[-1] def get_current_price(symbol): """Retrieve the current price of the given symbol.""" try: barset = alpaca.get_bars(symbol, TimeFrame.Minute, limit=1).df if not barset.empty: return barset['close'].iloc[-1] # Return the close price of the last bar else: logging.warning(f"No recent trading data for {symbol}") return None # or some default price except Exception as e: logging.error(f"Error retrieving price for {symbol}: {e}") return None # or some default price def get_user_position(symbol): """Retrieve the user's current position for the given symbol.""" try: position = alpaca.get_position(symbol) return position.qty, position.avg_entry_price except Exception as e: logging.warning(f"Could not retrieve position for {symbol}: {e}") return 0, 0 # Defaults if no position is found @app.route('/webhook', methods=['POST']) def webhook(): """Handle incoming webhook requests.""" data = request.json # Validate data if not all(k in data for k in ["secret_key", "ticker", "quantity", "action"]): logging.warning("Missing data in request") return jsonify({"error": "Missing data"}), 400 # Check if the secret key matches if data.get('secret_key') != WEBHOOK_SECRET_KEY: logging.warning("Invalid secret key received") return jsonify({"error": "Invalid secret key"}), 403 try: # Reformat the ticker symbol symbol = reformat_ticker(data['ticker']) logging.info(f"Received quantity as a string: {data['quantity']}") quantity = float(data['quantity']) logging.info(f"Parsed quantity as a float: {quantity}") action = data['action'].lower() # Retrieve current price and user's position current_price = get_current_price(symbol) user_qty, avg_entry_price = get_user_position(symbol) # Log order attempt and additional info logging.info(f"Order Attempt - Symbol: {symbol}, Quantity: {quantity}, Action: {action}") logging.info(f"Current Price: {current_price}, User Position: {user_qty} at avg price {avg_entry_price}") # Process the order if action in ['buy', 'sell']: formatted_quantity = "{:.8f}".format(quantity) logging.info(f"Formatted quantity: {formatted_quantity}") # Ensure the quantity is greater than 0 if quantity <= 0: raise ValueError("Quantity must be greater than 0") order = alpaca.submit_order(symbol=symbol, qty=formatted_quantity, side=action, type='market', time_in_force='gtc') logging.info(f"Order submitted successfully - Order ID: {order.id}") return jsonify({"message": "Order submitted", "order_id": order.id}), 200 else: logging.warning("Invalid action received") return jsonify({"error": "Invalid action"}), 400 except ValueError as e: # Handle the specific error when conversion fails or quantity is invalid logging.error(f"Error in processing order: {e}") return jsonify({"error": "Invalid quantity format"}), 400 except Exception as e: # Handle any other exceptions logging.error(f"Error in processing order: {e}") return jsonify({"error": str(e)}), 500 if __name__ == '__main__': # Start ngrok when app is run ngrok_tunnel = ngrok.connect(5000, bind_tls=True, hostname='ysps.ngrok.io') logging.info(f'ngrok tunnel "webhook" -> {ngrok_tunnel.public_url}') # Run the Flask app app.run(port=5000)