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)