├── .gitignore ├── api ├── database.py ├── index.py ├── models │ ├── analyst.py │ ├── brokerage.py │ └── trader.py └── routes │ ├── analyst.py │ ├── auth.py │ ├── brokerage.py │ ├── trader.py │ └── utils.py ├── requirements.txt └── vercel.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.so 6 | .Python 7 | build/ 8 | develop-eggs/ 9 | dist/ 10 | downloads/ 11 | eggs/ 12 | .eggs/ 13 | lib/ 14 | lib64/ 15 | parts/ 16 | sdist/ 17 | var/ 18 | wheels/ 19 | *.egg-info/ 20 | .installed.cfg 21 | *.egg 22 | 23 | # Virtual Environment 24 | venv/ 25 | env/ 26 | ENV/ 27 | myenv/ 28 | .env 29 | .venv 30 | .env.local 31 | .env.development.local 32 | .env.test.local 33 | .env.production.local 34 | 35 | # IDE 36 | .idea/ 37 | .vscode/ 38 | *.swp 39 | *.swo 40 | .DS_Store 41 | .vs/ 42 | 43 | # Vercel 44 | .vercel 45 | .now 46 | 47 | # Logs 48 | *.log 49 | logs/ 50 | npm-debug.log* 51 | yarn-debug.log* 52 | yarn-error.log* 53 | 54 | # Coverage reports 55 | htmlcov/ 56 | .tox/ 57 | .coverage 58 | .coverage.* 59 | .cache 60 | nosetests.xml 61 | coverage.xml 62 | *.cover 63 | .hypothesis/ 64 | 65 | # Project specific 66 | node_modules/ 67 | .pytest_cache/ -------------------------------------------------------------------------------- /api/database.py: -------------------------------------------------------------------------------- 1 | from fastapi import HTTPException 2 | from motor.motor_asyncio import AsyncIOMotorClient 3 | import os 4 | from dotenv import load_dotenv 5 | from .models.analyst import Analyst 6 | 7 | # Load environment variables 8 | load_dotenv() 9 | 10 | async def get_database(collection_name: str): 11 | try: 12 | MONGODB_URL = os.getenv("MONGODB_URL") 13 | client = AsyncIOMotorClient(MONGODB_URL) 14 | 15 | # Get list of all databases 16 | dbs = await client.list_database_names() 17 | 18 | # Check if optionsTrading database exists 19 | if "optionsTrading" not in dbs: 20 | print("Creating optionsTrading database...") 21 | # Create database by inserting a document 22 | db = client.get_database("optionsTrading") 23 | await db.create_collection("traders") # Create traders collection 24 | print("Created optionsTrading database with collections") 25 | 26 | # Get database and collection 27 | db = client.get_database("optionsTrading") 28 | 29 | # Check if collection exists 30 | collections = await db.list_collection_names() 31 | 32 | if "analyst" not in collections: 33 | print("Creating analyst collection with initial data...") 34 | analyst_collection = db.get_collection("analyst") 35 | 36 | # Initial analysts data 37 | initial_analysts = [{ 38 | "name": "John", 39 | "type": "analyst1", 40 | "status": "start" 41 | }, 42 | { 43 | "name": "WiseGuy", 44 | "type": "analyst2", 45 | "status": "start" 46 | }, 47 | { 48 | "name": "Tommy", 49 | "type": "analyst3", 50 | "status": "start" 51 | }, 52 | { 53 | "name": "Johnny", 54 | "type": "analyst4", 55 | "status": "start" 56 | }] 57 | await analyst_collection.insert_many(initial_analysts) 58 | print("Created analyst collection with all initial data") 59 | 60 | collection = db.get_collection(collection_name) 61 | return collection 62 | 63 | except Exception as e: 64 | print(f"Database connection error: {str(e)}") 65 | raise HTTPException(status_code=500, detail="Database connection failed") 66 | 67 | try: 68 | MONGODB_URL = os.getenv("MONGODB_URL") 69 | client = AsyncIOMotorClient(MONGODB_URL) 70 | db = client.get_database("optionsTrading") 71 | 72 | # Check if collection exists 73 | collections = await db.list_collection_names() 74 | if "analyst" not in collections: 75 | print("Creating analyst collection with first row...") 76 | analyst_collection = db.get_collection("analyst") 77 | 78 | # First row of data 79 | first_analyst = [{ 80 | "name": "John", 81 | "type": "analyst1" 82 | }, 83 | { 84 | "name": "WiseGuy", 85 | "type": "analyst2" 86 | }, 87 | { 88 | "name": "Tommy", 89 | "type": "analyst3" 90 | }, 91 | { 92 | "name": "Johnny", 93 | "type": "analyst4" 94 | }] 95 | await analyst_collection.insert_many(first_analyst) 96 | print("Created analyst collection with first row of data") 97 | 98 | client.close() 99 | except Exception as e: 100 | print(f"Error initializing analyst collection: {str(e)}") 101 | raise HTTPException(status_code=500, detail="Failed to initialize analyst collection") -------------------------------------------------------------------------------- /api/index.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, HTTPException 2 | # from motor.motor_asyncio import AsyncIOMotorClient 3 | from fastapi.middleware.cors import CORSMiddleware 4 | from dotenv import load_dotenv 5 | from .routes import auth 6 | from .routes import trader 7 | from .routes import brokerage 8 | from .routes import analyst 9 | from bson import ObjectId 10 | import os 11 | # import platform 12 | # import asyncio 13 | from .database import get_database 14 | from datetime import datetime 15 | from zoneinfo import ZoneInfo 16 | from apscheduler.schedulers.asyncio import AsyncIOScheduler 17 | from apscheduler.triggers.cron import CronTrigger 18 | from apscheduler.schedulers.background import BackgroundScheduler 19 | from .routes.utils import parse_option_date 20 | from dotenv import load_dotenv 21 | import requests 22 | import ntplib 23 | load_dotenv() 24 | 25 | app = FastAPI() 26 | 27 | # Add CORS middleware 28 | app.add_middleware( 29 | CORSMiddleware, 30 | allow_origins=["*"], # Allows all origins 31 | allow_credentials=True, 32 | allow_methods=["*"], # Allows all methods 33 | allow_headers=["*"], # Allows all headers 34 | ) 35 | 36 | 37 | # Include routers 38 | app.include_router(auth.router, prefix="/api/auth") 39 | app.include_router(trader.router, prefix="/api/trader") 40 | app.include_router(brokerage.router, prefix="/api/brokerage") 41 | app.include_router(analyst.router, prefix="/api/analyst") 42 | 43 | @app.get("/") 44 | async def read_root(): 45 | return {"message": "Welcome to FastAPI with MongoDB"} 46 | 47 | # Example endpoint to get items 48 | @app.get("/items") 49 | async def get_items(): 50 | try: 51 | # Get count of traders collection 52 | trader_collection = await get_database("traders") 53 | count = await trader_collection.count_documents({}) 54 | print(count) 55 | # Or get all traders 56 | return {"total_traders": count} 57 | # return {"message": "Hello World"} 58 | except Exception as e: 59 | raise HTTPException(status_code=500, detail=str(e)) 60 | 61 | # Example endpoint to create an item 62 | @app.post("/items") 63 | async def create_item(item: dict): 64 | try: 65 | global traders 66 | result = await traders.insert_one(item) 67 | return {"id": str(result.inserted_id)} 68 | except Exception as e: 69 | raise HTTPException(status_code=500, detail=str(e)) 70 | 71 | scheduler = AsyncIOScheduler() 72 | 73 | async def check_open_positions(): 74 | return "Checking open positions" 75 | 76 | async def check_stoploss_profit(position_id, option_symbol, entry_price, user_id, total_amount, sold_amount, asset_id): 77 | trader_collection = await get_database("traders") 78 | trader = await trader_collection.find_one({"_id": ObjectId(user_id)}) 79 | 80 | brokerage_collection = await get_database("brokerageCollection") 81 | brokerage = await brokerage_collection.find_one({"_id": ObjectId(trader["brokerageName"])}) 82 | 83 | api_key = brokerage["API_KEY"] 84 | api_secret = brokerage["SECRET_KEY"] 85 | 86 | headers = { 87 | "APCA-API-KEY-ID": api_key, 88 | "APCA-API-SECRET-KEY": api_secret, 89 | "Content-Type": "application/json", 90 | "Accept": "application/json" 91 | } 92 | print("asset_id: ", asset_id) 93 | url = f"https://paper-api.alpaca.markets/v2/positions/{asset_id}" 94 | 95 | response = requests.get(url, headers=headers) 96 | response_json = response.json() 97 | current_price = float(response_json["current_price"]) 98 | entry_price = float(response_json["avg_entry_price"]) 99 | 100 | trader_collection = await get_database("traders") 101 | trader = await trader_collection.find_one({"_id": ObjectId(user_id)}) 102 | 103 | if trader: 104 | stoploss = float(trader.get("stopLoss", 0)) 105 | profit = float(trader.get("profitTaking", 0)) 106 | 107 | if stoploss != 0: 108 | stoploss_condition_price = entry_price * (1 - stoploss / 100) 109 | print("stoploss_condition_price: ", stoploss_condition_price) 110 | 111 | if stoploss_condition_price >= current_price: 112 | try: 113 | position_collection = await get_database("positions") 114 | open_position = await position_collection.find_one({"asset_id": asset_id}) 115 | if open_position: 116 | await auto_sell_options(option_symbol, total_amount, sold_amount, position_id, api_key, api_secret) 117 | except Exception as e: 118 | print(f"Error in stoploss check: {e}") 119 | 120 | if profit != 0: 121 | profit_condition_price = entry_price * (1 + profit / 100) 122 | print("profit_condition_price: ", profit_condition_price) 123 | if profit_condition_price <= current_price: 124 | try: 125 | position_collection = await get_database("positions") 126 | open_position = await position_collection.find_one({"asset_id": asset_id}) 127 | print("open_position: ", open_position) 128 | if open_position: 129 | await auto_sell_options(option_symbol, total_amount, sold_amount, position_id, api_key, api_secret) 130 | except Exception as e: 131 | print(f"Error in profit check: {e}") 132 | return "Checking stoploss and profit" 133 | 134 | async def check_market_time(): 135 | try: 136 | # Get time from NTP server 137 | ntp_client = ntplib.NTPClient() 138 | response = ntp_client.request('pool.ntp.org') 139 | # Convert NTP time to datetime and set timezone to ET 140 | current_time = datetime.fromtimestamp(response.tx_time, ZoneInfo("America/New_York")) 141 | except Exception as e: 142 | print(f"Error getting NTP time: {e}") 143 | # Fallback to local time if NTP fails 144 | current_time = datetime.now(ZoneInfo("America/New_York")) 145 | 146 | print("current_time: ", current_time) 147 | 148 | # Check if it's a weekday (0 = Monday, 6 = Sunday) 149 | if current_time.weekday() >= 5: # Saturday or Sunday 150 | return False 151 | 152 | # Create time objects for market open and close 153 | market_open = current_time.replace(hour=9, minute=30, second=0, microsecond=0) 154 | market_close = current_time.replace(hour=16, minute=0, second=0, microsecond=0) 155 | 156 | # Check if current time is within market hours 157 | is_market_open = market_open <= current_time <= market_close 158 | return is_market_open 159 | 160 | async def auto_sell_options(option_symbol , total_amount , sold_amount , position_id , api_key, secret_key): 161 | try: 162 | headers = { 163 | "APCA-API-KEY-ID": api_key, 164 | "APCA-API-SECRET-KEY": secret_key, 165 | "Content-Type": "application/json", 166 | "Accept": "application/json" 167 | } 168 | url = "https://paper-api.alpaca.markets/v2/orders" 169 | payload = { 170 | "type": "market", 171 | "time_in_force": "day", 172 | "symbol": option_symbol, 173 | "qty": total_amount - sold_amount, 174 | "side": "sell", 175 | } 176 | response = requests.post(url, headers=headers, json=payload) 177 | 178 | position_collection = await get_database("positions") 179 | current_date = datetime.now(ZoneInfo("America/New_York")).date() 180 | await position_collection.update_one({"_id": ObjectId(position_id)}, {"$set": {"status": "closed"}}) 181 | await position_collection.update_one({"_id": ObjectId(position_id)}, {"$set": {"soldAmount": total_amount}}) 182 | await position_collection.update_one({"_id": ObjectId(position_id)}, {"$set": {"exitDate": current_date}}) 183 | return response.json() 184 | except Exception as e: 185 | print(f"Error in auto sell options: {e}") 186 | 187 | 188 | async def check_date_expired(option_symbol , total_amount , sold_amount , position_id , user_id): 189 | try: 190 | # print("options symbol" , option_symbol) 191 | month, date = parse_option_date(option_symbol) 192 | try: 193 | # Get time from NTP server 194 | ntp_client = ntplib.NTPClient() 195 | response = ntp_client.request('pool.ntp.org') 196 | # Convert NTP time to datetime and set timezone to ET 197 | current_time = datetime.fromtimestamp(response.tx_time, ZoneInfo("America/New_York")) 198 | except Exception as e: 199 | print(f"Error getting NTP time: {e}") 200 | # Fallback to local time if NTP fails 201 | current_time = datetime.now(ZoneInfo("America/New_York")) 202 | 203 | # Create expiration date object (40 minutes before market close) 204 | expiration_date = current_time.replace(month=int(month), day=int(date), hour=15, minute=20, second=0, microsecond=0) 205 | 206 | # Check if current time is on expiration date and 40 minutes before close 207 | if current_time.date() == expiration_date.date() and current_time >= expiration_date: 208 | print(f"Option {option_symbol} is 40 minutes before market close on expiration date") 209 | trader_collection = await get_database("traders") 210 | trader = await trader_collection.find_one({"_id": ObjectId(user_id)}) 211 | brokerage_collection = await get_database("brokerageCollection") 212 | brokerage = await brokerage_collection.find_one({"_id": ObjectId(trader["brokerageName"])}) 213 | 214 | api_key = brokerage["API_KEY"] 215 | api_secret = brokerage["SECRET_KEY"] 216 | await auto_sell_options(option_symbol , total_amount , sold_amount , position_id , api_key, api_secret) 217 | return True 218 | else: 219 | print(f"Current time: {current_time.strftime('%Y-%m-%d %H:%M:%S')}") 220 | print(f"Expiration check time: {expiration_date.strftime('%Y-%m-%d %H:%M:%S')}") 221 | return False 222 | 223 | except Exception as e: 224 | print(f"Error in date expired check: {e}") 225 | return False 226 | 227 | async def check_funtion(): 228 | try: 229 | # First check if market is open 230 | is_market_open = await check_market_time() 231 | if not is_market_open: 232 | print(f"Market is closed.") 233 | return "Market is closed." 234 | 235 | position_collection = await get_database("positions") 236 | open_positions = await position_collection.find({"status": "open"}).to_list(length=1000) 237 | 238 | if open_positions: 239 | print(f"Found {len(open_positions)} open positions") 240 | for position in open_positions: 241 | await check_stoploss_profit(position["_id"] , position["orderSymbol"] , position["entryPrice"] , position["userID"] , position["amount"] , position["soldAmount"], position["asset_id"]) 242 | else: 243 | print(f"No open positions") 244 | 245 | return "Function executed successfully" 246 | except Exception as e: 247 | print(f"Error in function: {str(e)}") 248 | return "Error occurred" 249 | 250 | # Schedule job to run every 10 seconds with proper cooldown 251 | scheduler.add_job( 252 | check_funtion, 253 | trigger='interval', 254 | seconds=60, # Run every 10 seconds 255 | timezone=ZoneInfo("America/New_York"), # ET timezone 256 | misfire_grace_time=30, # Allow jobs to be 30 seconds late 257 | max_instances=1, # Only allow one instance to run at a time 258 | coalesce=True # Combine multiple pending jobs into one 259 | ) 260 | 261 | # Start the scheduler when the application starts 262 | @app.on_event("startup") 263 | async def start_scheduler(): 264 | scheduler.start() 265 | 266 | # Shutdown the scheduler when the application stops 267 | @app.on_event("shutdown") 268 | async def shutdown_scheduler(): 269 | scheduler.shutdown() 270 | -------------------------------------------------------------------------------- /api/models/analyst.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | class AnalystCreate(BaseModel): 4 | name: str 5 | type: str 6 | 7 | class Config: 8 | json_schema_extra = { 9 | "example": { 10 | "name": "John Doe", 11 | "type": "Technical" 12 | } 13 | } 14 | 15 | class Analyst(BaseModel): 16 | name: str 17 | type: str 18 | 19 | class Config: 20 | json_schema_extra = { 21 | "example": { 22 | "name": "John Doe", 23 | "type": "Technical" 24 | } 25 | } -------------------------------------------------------------------------------- /api/models/brokerage.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | class BrokerageCreate(BaseModel): 4 | brokerageName: str 5 | brokerage: str 6 | loginName: str 7 | password: str 8 | accountNumber: str 9 | apiInfo : str 10 | apiLink : str 11 | 12 | class Config: 13 | json_schema_extra = { 14 | "example": { 15 | "brokerageName": "Zerodha", 16 | "brokerage": "discount", 17 | "loginName": "trader123", 18 | "password": "securepassword123", 19 | "accountNumber": "AB1234", 20 | "apiInfo": "apiInfo", 21 | "apiLink": "apiLink" 22 | } 23 | } 24 | 25 | class Brokerage(BaseModel): 26 | brokerageName: str 27 | brokerage: str 28 | loginName: str 29 | password: str 30 | accountNumber: str 31 | apiInfo : str 32 | apiLink : str 33 | 34 | class Config: 35 | json_schema_extra = { 36 | "example": { 37 | "brokerageName": "Zerodha", 38 | "brokerage": "discount", 39 | "loginName": "trader123", 40 | "password": "securepassword123", 41 | "accountNumber": "AB1234", 42 | "apiInfo": "apiInfo", 43 | "apiLink": "apiLink" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /api/models/trader.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | class TraderCreate(BaseModel): 4 | name: str 5 | password: str 6 | email: str 7 | role : str = "trader" 8 | amount : int = 0 9 | 10 | class Trader(BaseModel): 11 | name: str 12 | password: str 13 | email: str 14 | role : str = "trader" 15 | 16 | class Config: 17 | json_schema_extra = { 18 | "example": { 19 | "email": "john_doe@gmail.com", 20 | "name": "john_doe", 21 | "password": "securepassword123", 22 | "role": "trader" 23 | } 24 | } 25 | 26 | class Position(BaseModel): 27 | orderSymbol: str 28 | symbol: str 29 | quantity: int 30 | analyst: str 31 | side: str 32 | orderType: str 33 | timeInForce: str 34 | date: str 35 | entryPrice : float 36 | 37 | childType : str 38 | userID : str 39 | amount : int = 0 40 | soldAmount : int = 0 41 | exitDate : str = "" 42 | strikePrice : float 43 | 44 | closePrice : float = 0.0 45 | status : str = "open" 46 | 47 | 48 | class Config: 49 | json_schema_extra = { 50 | "example": { 51 | "orderSymbol": "AAPL", 52 | "symbol": "AAPL", 53 | "quantity": 1, 54 | "analyst": "John", 55 | "side": "buy", 56 | "orderType": "market", 57 | "timeInForce": "day", 58 | "date": "2024-03-20", 59 | "entryPrice": 100, 60 | "childType": "option", 61 | "userID": "123", 62 | "amount": 100, 63 | "soldAmount": 0, 64 | "exitDate": "", 65 | "strikePrice": 100 66 | } 67 | } -------------------------------------------------------------------------------- /api/routes/analyst.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, HTTPException 2 | from ..database import get_database 3 | from bson import ObjectId 4 | from pydantic import BaseModel 5 | from ..models.trader import Position 6 | import os 7 | from datetime import datetime 8 | import requests 9 | import json 10 | from dotenv import load_dotenv 11 | from ..routes.utils import parse_option_date, check_option_expiry 12 | 13 | load_dotenv() 14 | 15 | router = APIRouter() 16 | 17 | @router.get("/getAnalysts") 18 | async def get_analysts(): 19 | try: 20 | analyst_collection = await get_database("analyst") 21 | analysts = await analyst_collection.find({}).to_list(1000) 22 | for analyst in analysts: 23 | analyst["_id"] = str(analyst["_id"]) 24 | print("analysts", analysts) 25 | return analysts 26 | except Exception as e: 27 | raise HTTPException(status_code=500, detail=str(e)) 28 | 29 | class Analyst(BaseModel): 30 | name : str 31 | type : str 32 | currentId : str 33 | 34 | @router.post("/updateAnalyst") 35 | async def update_analyst(analyst: Analyst): 36 | try: 37 | print("analyst", analyst) 38 | analyst_collection = await get_database("analyst") 39 | startDate = datetime.now() 40 | if not analyst.currentId == "": 41 | await analyst_collection.update_one({"_id": ObjectId(analyst.currentId)}, {"$set": {"name": analyst.name, "type": analyst.type, "startDate": startDate}}) 42 | return {"message": "Analyst updated successfully"} 43 | else: 44 | await analyst_collection.insert_one({"name": analyst.name, "type": analyst.type , "status": "start", "startDate": startDate}) 45 | return {"message": "Analyst created successfully"} 46 | except Exception as e: 47 | raise HTTPException(status_code=500, detail=str(e)) 48 | 49 | class GetAnalyst(BaseModel): 50 | currentId : str 51 | 52 | @router.post("/deleteAnalyst") 53 | async def get_analyst(analyst: GetAnalyst): 54 | try: 55 | analyst_collection = await get_database("analyst") 56 | await analyst_collection.delete_one({"_id": ObjectId(analyst.currentId)}) 57 | 58 | trader_collection = await get_database("trader") 59 | for i in range(4): 60 | analyst_field = f"analystId{i+1}" 61 | trader = await trader_collection.find_one({analyst_field: ObjectId(analyst.currentId)}) 62 | if trader: 63 | print("match found") 64 | await trader_collection.update_one({analyst_field: ObjectId(analyst.currentId)}, {"$set": {analyst_field: ""}}) 65 | return {"message": "Analyst deleted successfully"} 66 | except Exception as e: 67 | raise HTTPException(status_code=500, detail=str(e)) 68 | -------------------------------------------------------------------------------- /api/routes/auth.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, HTTPException, Request, Header, Depends 2 | # from fastapi.security import OAuth2PasswordRequestForm 3 | from ..models.trader import TraderCreate, Trader 4 | from bson import ObjectId 5 | from passlib.context import CryptContext # type: ignore 6 | import jwt # type: ignore 7 | import os 8 | from pydantic import BaseModel 9 | from typing import Optional 10 | from ..database import get_database 11 | from datetime import datetime 12 | 13 | router = APIRouter() 14 | 15 | # Add this near the top with other initializations 16 | pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") 17 | 18 | 19 | @router.post("/signup", response_model=dict) 20 | async def create_trader(trader: TraderCreate, request: Request): 21 | try: 22 | trader_collection = await get_database("traders") 23 | # Check if trader exists 24 | existing_trader = await trader_collection.find_one({"email": trader.email}) 25 | 26 | if existing_trader: 27 | # If trader exists, return message without updating 28 | raise HTTPException( 29 | status_code=400, 30 | detail="User already exists" 31 | ) 32 | 33 | # Create new trader if doesn't exist 34 | trader_dict = trader.model_dump() 35 | trader_dict["password"] = trader_dict["password"] # Store password directly 36 | trader_dict["user_id"] = str(ObjectId()) 37 | trader_dict["created_at"] = datetime.now().isoformat() 38 | trader_dict["status"] = "start" 39 | trader_dict["stopLoss"] = 0 40 | trader_dict["brokerageName"] = "" 41 | trader_dict["API_KEY"] = "" 42 | trader_dict["SECRET_KEY"] = "" 43 | trader_dict["profitTaking"] = 0 44 | result = await trader_collection.insert_one(trader_dict) 45 | return {"id": str(result.inserted_id)} 46 | 47 | except Exception as e: 48 | raise HTTPException(status_code=500, detail=str(e)) 49 | 50 | 51 | class TraderUpdate(BaseModel): 52 | email: str 53 | amount: int 54 | name : str 55 | password : str 56 | traderId : str 57 | stopLoss : float 58 | profitTaking : float 59 | 60 | @router.post("/updateTrader" , response_model=dict) 61 | async def update_trader(trader: TraderUpdate): 62 | try: 63 | # print("trader: ", trader) 64 | trader_collection = await get_database("traders") 65 | result = await trader_collection.update_one( 66 | {"_id": ObjectId(trader.traderId)}, 67 | {"$set": trader.model_dump()} 68 | ) 69 | # print("result: ", result) 70 | return {"message": "Trader updated successfully"} 71 | except Exception as e: 72 | raise HTTPException(status_code=500, detail=str(e)) 73 | 74 | class BrokerageTrader(BaseModel): 75 | traderId: str 76 | brokerageName: str 77 | API_KEY: str 78 | SECRET_KEY: str 79 | liveTrading: bool 80 | 81 | @router.post("/updateBrokerageTrader") 82 | async def update_brokerage_trader(trader: BrokerageTrader): 83 | try: 84 | trader_collection = await get_database("traders") 85 | print("trader: ", trader) 86 | result = await trader_collection.update_one( 87 | {"_id": ObjectId(trader.traderId)}, 88 | {"$set": {"brokerageName": trader.brokerageName, "API_KEY": trader.API_KEY, "SECRET_KEY": trader.SECRET_KEY, "liveTrading": trader.liveTrading}} 89 | ) 90 | return {"message": "Brokerage trader updated successfully"} 91 | except Exception as e: 92 | raise HTTPException(status_code=500, detail=str(e)) 93 | 94 | @router.post("/traderSignup") 95 | async def traderSignup(trader: TraderUpdate): 96 | try: 97 | 98 | trader_collection = await get_database("traders") 99 | existing_trader = await trader_collection.find_one({"email": trader.email}) 100 | if existing_trader: 101 | raise HTTPException( 102 | status_code=400, 103 | detail="User already exists" 104 | ) 105 | 106 | trader_dict = trader.model_dump() 107 | trader_dict["password"] = trader_dict["password"] # Store password directly 108 | trader_dict["user_id"] = str(ObjectId()) 109 | trader_dict["created_at"] = datetime.now().isoformat() 110 | trader_dict["status"] = "start" 111 | trader_dict["brokerageName"] = "" 112 | trader_dict["API_KEY"] = "" 113 | trader_dict["SECRET_KEY"] = "" 114 | trader_dict["role"] = "trader" 115 | 116 | result = await trader_collection.insert_one(trader_dict) 117 | return {"message": "Trader created successfully"} 118 | except Exception as e: 119 | raise HTTPException(status_code=500, detail=str(e)) 120 | class SignInRequest(BaseModel): 121 | email: str 122 | password: str 123 | 124 | @router.post("/signin") 125 | async def signin(request: Request, credentials: SignInRequest): 126 | try: 127 | trader_collection = await get_database("traders") 128 | # Find trader by email 129 | trader = await trader_collection.find_one({"email": credentials.email}) 130 | 131 | if not trader : 132 | raise HTTPException( 133 | status_code=401, 134 | detail="Incorrect email or password" 135 | ) 136 | 137 | if trader["status"] == "stop": 138 | raise HTTPException( 139 | status_code=401, 140 | detail="Account is currently disabled , contact Admin" 141 | ) 142 | 143 | # Check if password exists in trader document 144 | if 'password' not in trader: 145 | raise HTTPException( 146 | status_code=401, 147 | detail="No password set for this account" 148 | ) 149 | 150 | try: 151 | print("verifying password") 152 | # Direct password comparison 153 | if credentials.password != trader['password']: 154 | raise HTTPException( 155 | status_code=401, 156 | detail="Incorrect email or password" 157 | ) 158 | print("password verified") 159 | except Exception as password_error: 160 | print(f"Password verification error: {str(password_error)}") 161 | raise HTTPException( 162 | status_code=401, 163 | detail="Password verification failed" 164 | ) 165 | 166 | # Create JWT token 167 | auth_token = jwt.encode( 168 | { 169 | "email": trader['email'], 170 | "user_id": str(trader['_id']) 171 | }, 172 | os.getenv("SECRET"), 173 | algorithm="HS256" 174 | ) 175 | print("auth_token", auth_token) 176 | print("trader", trader['_id']) 177 | return { 178 | "authToken": auth_token, 179 | "user": { 180 | "email": trader['email'], 181 | "user_id": str(trader['_id']), 182 | "role": trader['role'] 183 | } 184 | } 185 | 186 | except Exception as e: 187 | print(f"Signin error: {str(e)}") 188 | raise HTTPException(status_code=401, detail=str(e)) 189 | 190 | 191 | @router.post("/verify") 192 | async def verify_token(request: Request, authorization: Optional[str] = Header(None)): 193 | try: 194 | trader_collection = await get_database("traders") 195 | if not authorization: 196 | raise HTTPException( 197 | status_code=401, 198 | detail="Authorization header missing" 199 | ) 200 | 201 | token = authorization.replace("Bearer ", "") 202 | 203 | try: 204 | # Verify and decode the JWT token 205 | payload = jwt.decode( 206 | token, 207 | os.getenv("SECRET"), 208 | algorithms=["HS256"] 209 | ) 210 | 211 | # Find trader by email from token 212 | trader = await trader_collection.find_one({"email": payload["email"]}) 213 | 214 | if not trader: 215 | raise HTTPException( 216 | status_code=401, 217 | detail="Invalid token" 218 | ) 219 | 220 | return { 221 | "valid": True, 222 | "user": { 223 | "email": trader["email"], 224 | "user_id": str(trader["_id"]) 225 | } 226 | } 227 | 228 | except jwt.ExpiredSignatureError: 229 | raise HTTPException( 230 | status_code=401, 231 | detail="Token has expired" 232 | ) 233 | except jwt.JWTError: 234 | raise HTTPException( 235 | status_code=401, 236 | detail="Invalid token" 237 | ) 238 | 239 | except Exception as e: 240 | raise HTTPException( 241 | status_code=401, 242 | detail=str(e) 243 | ) 244 | 245 | class ChangePasswordRequest(BaseModel): 246 | currentPassword: str 247 | newPassword: str 248 | confirmPassword: str 249 | userID: str 250 | 251 | @router.post("/changePassword") 252 | async def change_password(changePassword: ChangePasswordRequest): 253 | try: 254 | print("changePassword: ", changePassword) 255 | trader_collection = await get_database("traders") 256 | trader = await trader_collection.find_one({"password": changePassword.currentPassword, "_id": ObjectId(changePassword.userID)}) 257 | if not trader: 258 | return 404 259 | 260 | if changePassword.newPassword != changePassword.confirmPassword: 261 | return 402 262 | 263 | if changePassword.currentPassword == changePassword.newPassword: 264 | return 400 265 | 266 | 267 | await trader_collection.update_one( 268 | {"_id": ObjectId(changePassword.userID)}, 269 | {"$set": {"password": changePassword.newPassword}} 270 | ) 271 | return 200 272 | except Exception as e: 273 | raise HTTPException(status_code=500, detail=str(e)) 274 | 275 | -------------------------------------------------------------------------------- /api/routes/brokerage.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, HTTPException, Request, Header, Depends 2 | from ..models.trader import TraderCreate, Trader 3 | from bson import ObjectId 4 | from passlib.context import CryptContext # type: ignore 5 | from pydantic import BaseModel 6 | from typing import Optional 7 | from ..database import get_database 8 | from ..models.brokerage import BrokerageCreate, Brokerage 9 | 10 | router = APIRouter() 11 | 12 | # Add this near the top with other initializations 13 | 14 | @router.get("/getBrokerages") 15 | async def get_brokerages(): 16 | try: 17 | brokerage_collection = await get_database("brokerageCollection") 18 | brokerages = await brokerage_collection.find().to_list(1000) 19 | 20 | # Convert ObjectId to string for JSON serialization 21 | for brokerage in brokerages: 22 | brokerage["_id"] = str(brokerage["_id"]) 23 | 24 | return brokerages 25 | except Exception as e: 26 | print(f"Error fetching brokerages: {str(e)}") 27 | raise HTTPException(status_code=500, detail="Failed to fetch brokerages") 28 | 29 | class BrokerageTraderCreate(BaseModel): 30 | brokerageName: str 31 | API_KEY: str 32 | SECRET_KEY: str 33 | liveTrading: bool 34 | 35 | @router.post("/createBrokerageTrader") 36 | async def create_brokerage_trader(request: BrokerageTraderCreate): 37 | try: 38 | brokerage_collection = await get_database("brokerageCollection") 39 | result = await brokerage_collection.insert_one(request.model_dump()) 40 | return {"id": str(result.inserted_id)} 41 | except Exception as e: 42 | raise HTTPException(status_code=500, detail=str(e)) 43 | 44 | class KeyData(BaseModel): 45 | userId : str 46 | 47 | @router.post("/getkeyData") 48 | async def get_key_data(request: KeyData): 49 | try: 50 | traders = await get_database("traders") 51 | brokerage_collection = await get_database("brokerageCollection") 52 | trader = await traders.find_one({"_id": ObjectId(request.userId)}) 53 | brokerage = await brokerage_collection.find_one({"_id": ObjectId(trader["brokerageName"])}) 54 | 55 | if brokerage: 56 | api_key = brokerage["API_KEY"] 57 | api_secret = brokerage["SECRET_KEY"] 58 | liveMode = brokerage["liveTrading"] 59 | return {"apiKey": api_key, "apiSecret": api_secret ,"liveMode": liveMode} 60 | else: 61 | raise HTTPException(status_code=404, detail="Key data not found") 62 | except Exception as e: 63 | print(f"Error fetching key data: {str(e)}") 64 | 65 | @router.post("/create", response_model=dict) 66 | async def create_brokerage(brokerage: BrokerageCreate): 67 | try: 68 | print("brokerage",brokerage) 69 | brokerage_collection = await get_database("brokerageCollection") 70 | print("checked") 71 | 72 | # Create new brokerage 73 | brokerage_dict = brokerage.model_dump() 74 | result = await brokerage_collection.insert_one(brokerage_dict) 75 | return {"id": str(result.inserted_id)} 76 | 77 | except Exception as e: 78 | raise HTTPException(status_code=500, detail=str(e)) 79 | 80 | # # Create a model for the delete request 81 | class DeleteBrokerageRequest(BaseModel): 82 | brokerageId: str 83 | 84 | @router.post("/deleteBrokerage") 85 | async def delete_brokerage(request: DeleteBrokerageRequest): 86 | try: 87 | brokerage_collection = await get_database("brokerageCollection") 88 | 89 | # Convert string ID to ObjectId 90 | result = await brokerage_collection.delete_one({"_id": ObjectId(request.brokerageId)}) 91 | 92 | if result.deleted_count == 0: 93 | raise HTTPException(status_code=404, detail="Brokerage not found") 94 | 95 | return {"message": "Brokerage deleted successfully"} 96 | except Exception as e: 97 | print(f"Error deleting brokerage: {str(e)}") 98 | raise HTTPException(status_code=500, detail="Failed to delete brokerage") 99 | -------------------------------------------------------------------------------- /api/routes/trader.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, HTTPException 2 | from ..database import get_database 3 | from bson import ObjectId 4 | from pydantic import BaseModel 5 | from ..models.trader import Position 6 | import os 7 | from datetime import datetime 8 | import requests 9 | import json 10 | from dotenv import load_dotenv 11 | from ..routes.utils import parse_option_date, check_option_expiry 12 | import time 13 | import asyncio 14 | import ntplib 15 | from datetime import datetime, timedelta 16 | from zoneinfo import ZoneInfo 17 | 18 | load_dotenv() 19 | 20 | router = APIRouter() 21 | 22 | async def check_market_time(): 23 | try: 24 | # Get time from NTP server 25 | ntp_client = ntplib.NTPClient() 26 | response = ntp_client.request('pool.ntp.org') 27 | # Convert NTP time to datetime and set timezone to ET 28 | current_time = datetime.fromtimestamp(response.tx_time, ZoneInfo("America/New_York")) 29 | except Exception as e: 30 | print(f"Error getting NTP time: {e}") 31 | # Fallback to local time if NTP fails 32 | current_time = datetime.now(ZoneInfo("America/New_York")) 33 | 34 | print("current_time: ", current_time) 35 | 36 | # Check if it's a weekday (0 = Monday, 6 = Sunday) 37 | if current_time.weekday() >= 5: # Saturday or Sunday 38 | return False 39 | 40 | # Create time objects for market open and close 41 | market_open = current_time.replace(hour=9, minute=30, second=0, microsecond=0) 42 | market_close = current_time.replace(hour=16, minute=0, second=0, microsecond=0) 43 | 44 | # Check if current time is within market hours 45 | is_market_open = market_open <= current_time <= market_close 46 | return is_market_open 47 | 48 | 49 | @router.get("/getTraders") 50 | async def get_traders(): 51 | try: 52 | trader_collection = await get_database("traders") 53 | traders = await trader_collection.find({"role": "trader"}).to_list(1000) 54 | if not traders: 55 | print("No traders found with role 'trader'") 56 | traders = [] 57 | 58 | # Convert ObjectId to string for JSON serialization 59 | for trader in traders: 60 | trader["_id"] = str(trader["_id"]) 61 | if "user_id" in trader: 62 | trader["user_id"] = str(trader["user_id"]) 63 | 64 | return traders 65 | except Exception as e: 66 | print(f"Error fetching traders: {e}") 67 | traders = [] 68 | return traders 69 | 70 | @router.get("/getAnalysts") 71 | async def get_analysts(): 72 | try: 73 | analyst_collection = await get_database("analyst") 74 | analysts = await analyst_collection.find().to_list(1000) 75 | 76 | # Convert ObjectId to string for JSON serialization 77 | for analyst in analysts: 78 | analyst["_id"] = str(analyst["_id"]) 79 | 80 | return analysts 81 | except Exception as e: 82 | print(f"Error fetching analysts: {str(e)}") 83 | raise HTTPException(status_code=500, detail="Failed to fetch analysts") 84 | 85 | class UpdateBrokerage(BaseModel): 86 | traderId: str 87 | brokerageName : str 88 | 89 | 90 | @router.post("/updateBrokerage") 91 | async def update_brokerage(brokerage: UpdateBrokerage): 92 | try: 93 | trader_collection = await get_database("traders") 94 | result = await trader_collection.update_one( 95 | {"_id": ObjectId(brokerage.traderId)}, 96 | {"$set": {"brokerageName": brokerage.brokerageName}} 97 | ) 98 | return {"message": "Brokerage updated successfully"} 99 | except Exception as e: 100 | print(f"Error updating brokerage: {str(e)}") 101 | raise HTTPException(status_code=500, detail="Failed to update brokerage") 102 | 103 | class UpdateAnalyst(BaseModel): 104 | traderId: str 105 | analystId: str 106 | analystNumber : int 107 | 108 | @router.post("/updateAnalyst") 109 | async def update_analyst(analyst: UpdateAnalyst): 110 | try: 111 | print("analyst: ", analyst) 112 | trader_collection = await get_database("traders") 113 | result = await trader_collection.update_one( 114 | {"_id": ObjectId(analyst.traderId)}, 115 | {"$set": {"analyst"+str(analyst.analystNumber): analyst.analystId}} 116 | ) 117 | return {"message": "Analyst updated successfully"} 118 | except Exception as e: 119 | print(f"Error updating analyst: {str(e)}") 120 | raise HTTPException(status_code=500, detail="Failed to update analyst") 121 | 122 | class DeleteTrader(BaseModel): 123 | traderId: str 124 | 125 | @router.post("/deleteTrader") 126 | async def delete_trader(trader: DeleteTrader): 127 | try: 128 | trader_collection = await get_database("traders") 129 | result = await trader_collection.delete_one({"_id": ObjectId(trader.traderId)}) 130 | return {"message": "Trader deleted successfully"} 131 | except Exception as e: 132 | print(f"Error deleting trader: {str(e)}") 133 | raise HTTPException(status_code=500, detail="Failed to delete trader") 134 | 135 | class Get_trader_analyst_class(BaseModel): 136 | traderId: str 137 | 138 | @router.post("/getTraderAnalysts") 139 | async def get_trader_analysts(trader: Get_trader_analyst_class): 140 | try: 141 | # print("traderId: ", trader) 142 | # print("traderAnalyst") 143 | 144 | trader_collection = await get_database("traders") 145 | trader = await trader_collection.find_one({"_id": ObjectId(trader.traderId)}) 146 | # print("trader: ", trader) 147 | if trader: 148 | trader["_id"] = str(trader["_id"]) 149 | return trader 150 | except Exception as e: 151 | print(f"Error getting trader analysts: {str(e)}") 152 | raise HTTPException(status_code=500, detail="Failed to get trader analysts") 153 | 154 | @router.post("/addPosition") 155 | async def add_position(position: Position): 156 | try: 157 | trader_collection = await get_database("traders") 158 | trader = await trader_collection.find_one({"_id": ObjectId(position.userID)}) 159 | brokerage_collection = await get_database("brokerageCollection") 160 | brokerage = await brokerage_collection.find_one({"_id": ObjectId(trader["brokerageName"])}) 161 | alpaca_api_key = brokerage["API_KEY"] 162 | alpaca_secret_key = brokerage["SECRET_KEY"] 163 | check_live_trading = brokerage["liveTrading"] 164 | paper_url = os.getenv("PAPER_URL") 165 | trader_amount = 0 166 | amount = 0 167 | 168 | # print("trader: ", trader) 169 | 170 | if trader: 171 | trader_id = trader["_id"] 172 | trader_amount = trader["amount"] 173 | # print("trader_amount: ", trader_amount) 174 | if trader_amount < position.entryPrice * 100: 175 | raise HTTPException(status_code=404, detail="Insufficient balance") 176 | else : 177 | amount = int(trader_amount / (position.entryPrice * 100)) 178 | # print("amount: ", amount) 179 | else: 180 | return 404 181 | raise HTTPException(status_code=404, detail="User not found") 182 | position.amount = amount 183 | position_collection = await get_database("positions") 184 | 185 | if check_live_trading == True: 186 | url = "https://api.alpaca.markets/v2/orders" 187 | else: 188 | url = f"{paper_url}/v2/orders" 189 | payload = { 190 | "type": "market", 191 | "time_in_force": "day", 192 | "symbol": position.orderSymbol, 193 | "qty": position.amount, 194 | "side": "buy", 195 | } 196 | if(alpaca_api_key == "" or alpaca_secret_key == ""): 197 | return 429 198 | headers = { 199 | "accept": "application/json", 200 | "content-type": "application/json", 201 | "APCA-API-KEY-ID": alpaca_api_key, 202 | "APCA-API-SECRET-KEY": alpaca_secret_key 203 | } 204 | 205 | check_url = f"https://data.alpaca.markets/v1beta1/options/snapshots?symbols={position.orderSymbol}&feed=indicative&limit=100" 206 | 207 | response = requests.get(check_url, headers=headers) 208 | response_json = response.json() 209 | bidPrice = response_json["snapshots"][position.orderSymbol]["latestQuote"]["ap"] 210 | askPrice = response_json["snapshots"][position.orderSymbol]["latestQuote"]["bp"] 211 | 212 | if abs(bidPrice - askPrice) < position.entryPrice * 0.04: 213 | position.entryPrice = bidPrice 214 | else : 215 | position.entryPrice = askPrice 216 | 217 | times = 3 218 | while times > 0: 219 | if position.entryPrice - bidPrice > position.entryPrice * (-0.02): 220 | response = requests.get(check_url, headers=headers) 221 | response_json = response.json() 222 | # print(response_json) 223 | bidPrice = response_json["snapshots"][position.orderSymbol]["latestQuote"]["ap"] 224 | print("bidPrice: ", bidPrice) 225 | else : 226 | break; 227 | if times == 0: 228 | return 201 229 | else : 230 | times -= 1 231 | time.sleep(5) 232 | print("times: ", times) 233 | position.entryPrice = bidPrice 234 | response = requests.post(url, json=payload, headers=headers) 235 | print("response: ", response.status_code) 236 | if response.status_code == 422: 237 | return 422 238 | if response.status_code == 403: 239 | return 455 240 | tradingId = response.json()["id"] 241 | # print("response: ", response.status_code) 242 | 243 | if response.status_code == 200 and tradingId != "": 244 | await asyncio.sleep(2) 245 | if check_live_trading == True: 246 | url2 = "https://api.alpaca.markets/v2/orders?status=all&symbols="+position.orderSymbol 247 | else: 248 | url2 = "https://paper-api.alpaca.markets/v2/orders?status=all&symbols="+position.orderSymbol 249 | 250 | response2 = requests.get(url2, headers=headers) 251 | for order in response2.json(): 252 | # print("*") 253 | if order["id"] == tradingId: 254 | # print("--------------------" , tradingId) 255 | price = order["filled_avg_price"] 256 | buy_quantity = order["filled_qty"] 257 | entrytimestamp = order["filled_at"] 258 | asset_id = order["asset_id"] 259 | entry_price = price # This will now update the global variable 260 | # print("*********************" , price ," entrytimestamp " , entrytimestamp) 261 | 262 | print("buy order is excuted" , entry_price) 263 | # print("history data" , history_data) 264 | position_dict = {} 265 | position_dict["symbol"] = position.symbol 266 | position_dict["orderSymbol"] = position.orderSymbol 267 | position_dict["quantity"] = buy_quantity 268 | position_dict["analyst"] = position.analyst 269 | position_dict["side"] = position.side 270 | position_dict["orderType"] = position.orderType 271 | position_dict["timeInForce"] = position.timeInForce 272 | position_dict["date"] = position.date 273 | position_dict["entryPrice"] = entry_price 274 | position_dict["childType"] = position.childType 275 | position_dict["userID"] = position.userID 276 | position_dict["amount"] = position.amount 277 | position_dict["soldAmount"] = 0 278 | position_dict["exitDate"] = position.exitDate 279 | position_dict["strikePrice"] = position.strikePrice 280 | position_dict["tradingId"] = tradingId 281 | position_dict["asset_id"] = asset_id 282 | position_dict["status"] = "open" 283 | position_dict["closePrice"] = 0 284 | position_dict["entryDate"] = entrytimestamp 285 | position_dict["created_at"] = datetime.now().isoformat() 286 | 287 | await position_collection.insert_one(position_dict) 288 | break; 289 | 290 | # Convert position to dict and add current time 291 | 292 | 293 | return 200 294 | else: 295 | print(f"Unexpected status code: {response.status_code}") 296 | # return {"message": "Failed to add position. Please check the market time"} 297 | return 422 298 | # print("result: ", result) 299 | return {"message": "Position added successfully"} 300 | except Exception as e: 301 | print(f"Error adding position: {str(e)}") 302 | raise HTTPException(status_code=500, detail="Failed to add position") 303 | 304 | # # get Traders Data 305 | @router.get("/getTraderData") 306 | async def get_trader_data(): 307 | try: 308 | # Get analysts data 309 | analyst_collection = await get_database("analyst") 310 | analysts = await analyst_collection.find().to_list(1000) 311 | 312 | # Get positions data 313 | position_collection = await get_database("positions") 314 | positions = await position_collection.find().to_list(1000) 315 | 316 | # Convert ObjectId to string for JSON serialization 317 | for analyst in analysts: 318 | analyst["_id"] = str(analyst["_id"]) 319 | 320 | for position in positions: 321 | position["_id"] = str(position["_id"]) 322 | # Convert datetime to string if it exists 323 | if "created_at" in position: 324 | position["created_at"] = position["created_at"].isoformat() 325 | 326 | # Return combined data 327 | return { 328 | "analysts": analysts, 329 | "positions": positions 330 | } 331 | except Exception as e: 332 | print(f"Error fetching trader data: {str(e)}") 333 | raise HTTPException(status_code=500, detail="Failed to fetch trader data") 334 | 335 | @router.get("/getOpenPositions") 336 | async def get_options_position(): 337 | result = await get_position_status("open") 338 | return result 339 | 340 | class TraderClosePositions(BaseModel): 341 | traderId: str 342 | 343 | @router.post("/getTraderClosePositions") 344 | async def get_trader_close_positions(traderId: TraderClosePositions): 345 | # print("traderId: ", traderId.traderId) 346 | result = await get_position_status_by_traderId("closed", traderId.traderId) 347 | return result 348 | 349 | class TraderOpenPositions(BaseModel): 350 | traderId: str 351 | 352 | @router.post("/getTraderOpenPositions") 353 | async def get_trader_open_positions(traderId: TraderOpenPositions): 354 | result = await get_position_status_by_traderId("open", traderId.traderId) 355 | return result 356 | 357 | async def get_position_status_by_traderId(position, traderId): 358 | try: 359 | position_collection = await get_database("positions") 360 | trader_collection = await get_database("traders") 361 | brokerage_collection = await get_database("brokerageCollection") 362 | trader = await trader_collection.find_one({"_id": ObjectId(traderId)}) 363 | brokerage = await brokerage_collection.find_one({"_id": ObjectId(trader["brokerageName"])}) 364 | 365 | alpaca_api_key = brokerage["API_KEY"] 366 | alpaca_secret_key = brokerage["SECRET_KEY"] 367 | check_live_trading = brokerage["liveTrading"] 368 | 369 | positions = await position_collection.find({"status": position, "userID": traderId}).to_list(1000) 370 | if not positions: 371 | # print("No open positions found") 372 | positions = [] 373 | 374 | 375 | for position in positions: 376 | position["_id"] = str(position["_id"]) 377 | # Convert datetime to string if it exists 378 | if "created_at" in position: 379 | current_time = datetime.now() 380 | # Convert ISO format string to datetime object 381 | created_at = datetime.fromisoformat(position["created_at"].replace('Z', '+00:00')) 382 | # Calculate difference in minutes 383 | difference = int((current_time - created_at).total_seconds() / 60) 384 | # print("difference in minutes: ", difference) 385 | position["timeDifference"] = difference 386 | if position['orderSymbol'] != '' and position['status'] == "open": 387 | 388 | headers = { 389 | "accept": "application/json", 390 | "content-type": "application/json", 391 | "APCA-API-KEY-ID": alpaca_api_key, 392 | "APCA-API-SECRET-KEY": alpaca_secret_key 393 | } 394 | 395 | 396 | if check_live_trading == True: 397 | url = "https://api.alpaca.markets/v2/positions/" + position['orderSymbol'] 398 | else: 399 | url = "https://paper-api.alpaca.markets/v2/positions/" + position['orderSymbol'] 400 | 401 | response = requests.get(url, headers=headers) 402 | 403 | position['currentPrice'] = response.json()['current_price'] 404 | return { 405 | "positions": positions 406 | } 407 | except Exception as e: 408 | print(f"Error fetching trader data: {str(e)}") 409 | raise HTTPException(status_code=500, detail="Failed to fetch trader data") 410 | 411 | @router.get("/getClosePositions") 412 | async def get_closed_positions(): 413 | result = await get_position_status_closed("closed") 414 | return result 415 | 416 | 417 | async def get_position_status_closed(position): 418 | try: 419 | # Get positions data 420 | position_collection = await get_database("positions") 421 | positions = await position_collection.find( 422 | {"status": position} 423 | ).to_list(1000) 424 | 425 | print("positions: ", positions) 426 | 427 | if not positions: 428 | print("No open positions found") 429 | positions = [] 430 | for position in positions: 431 | position["_id"] = str(position["_id"]) 432 | 433 | # Convert datetime to string if it exists 434 | if "created_at" in position: 435 | position["created_at"] = position["created_at"] 436 | if position['orderSymbol'] != '' and position['status'] == "open": 437 | month, date = parse_option_date(position['orderSymbol']) 438 | is_valid_option = check_option_expiry(month, date) 439 | if not is_valid_option: 440 | await position_collection.update_one( 441 | {"_id": ObjectId(position["_id"])}, 442 | {"$set": {"status": "closed"}} 443 | ) 444 | break 445 | 446 | return { 447 | "positions": positions 448 | } 449 | except Exception as e: 450 | print(f"Error fetching trader data: {str(e)}") 451 | raise HTTPException(status_code=500, detail="Failed to fetch trader data") 452 | 453 | 454 | async def get_position_status(position): 455 | try: 456 | # Get positions data 457 | position_collection = await get_database("positions") 458 | positions = await position_collection.find( 459 | {"status": position} 460 | ).to_list(1000) 461 | 462 | print("positions: ", positions) 463 | trader_collection = await get_database("traders") 464 | 465 | if not positions: 466 | print("No open positions found") 467 | positions = [] 468 | for position in positions: 469 | position["_id"] = str(position["_id"]) 470 | trader = await trader_collection.find_one({"_id": ObjectId(position.get("userID"))}) 471 | brokerage_collection = await get_database("brokerageCollection") 472 | brokerage = await brokerage_collection.find_one({"_id": ObjectId(trader["brokerageName"])}) 473 | 474 | alpaca_api_key = brokerage["API_KEY"] 475 | alpaca_secret_key = brokerage["SECRET_KEY"] 476 | check_live_trading = brokerage["liveTrading"] 477 | 478 | # Convert datetime to string if it exists 479 | if "created_at" in position: 480 | position["created_at"] = position["created_at"] 481 | if position['orderSymbol'] != '' and position['status'] == "open": 482 | month, date = parse_option_date(position['orderSymbol']) 483 | is_valid_option = check_option_expiry(month, date) 484 | if not is_valid_option: 485 | await position_collection.update_one( 486 | {"_id": ObjectId(position["_id"])}, 487 | {"$set": {"status": "closed"}} 488 | ) 489 | break 490 | 491 | headers = { 492 | "accept": "application/json", 493 | "content-type": "application/json", 494 | "APCA-API-KEY-ID": alpaca_api_key, 495 | "APCA-API-SECRET-KEY": alpaca_secret_key 496 | } 497 | 498 | if check_live_trading == True: 499 | url = "https://api.alpaca.markets/v2/positions/" + position['orderSymbol'] 500 | else: 501 | url = "https://paper-api.alpaca.markets/v2/positions/" + position['orderSymbol'] 502 | 503 | response = requests.get(url, headers=headers) 504 | 505 | position['currentPrice'] = response.json()['current_price'] 506 | 507 | # Return combined data 508 | return { 509 | "positions": positions 510 | } 511 | except Exception as e: 512 | print(f"Error fetching trader data: {str(e)}") 513 | raise HTTPException(status_code=500, detail="Failed to fetch trader data") 514 | 515 | def get_ask_price(response_data, symbol): 516 | try: 517 | # First, make sure response_data is parsed from JSON if it's a string 518 | if isinstance(response_data, str): 519 | response_data = json.loads(response_data) 520 | 521 | ask_price = float(response_data["quotes"][symbol]["ap"]) 522 | return ask_price 523 | except json.JSONDecodeError as e: 524 | print(f"Error parsing JSON: {e}") 525 | ask_price = 0.0 526 | except (KeyError, TypeError) as e: 527 | print(f"Error accessing data: {e}") 528 | ask_price = 0.0 529 | 530 | def get_bid_price(response_data, symbol): 531 | try: 532 | # First, make sure response_data is parsed from JSON if it's a string 533 | if isinstance(response_data, str): 534 | response_data = json.loads(response_data) 535 | 536 | bid_price = float(response_data["quotes"][symbol]["bp"]) 537 | print("bid_price: ", bid_price) 538 | return bid_price 539 | except json.JSONDecodeError as e: 540 | print(f"Error parsing JSON: {e}") 541 | bid_price = 0.0 542 | 543 | class SellAll(BaseModel): 544 | id: str 545 | amount : int 546 | 547 | @router.post("/sellAmount") 548 | async def sell_all(sellAll: SellAll): 549 | try: 550 | position_collection = await get_database("positions") 551 | try: 552 | position = await position_collection.find_one({"_id": ObjectId(sellAll.id)}) 553 | orderSymbol = "" 554 | if position: 555 | orderSymbol = position["orderSymbol"] 556 | else: 557 | raise ValueError(f"No position found with ID: {sellAll.id}") 558 | # print("orderSymbol: ", orderSymbol) 559 | # print("position: ", position) 560 | 561 | position_amount = position.get("amount") 562 | position_soldAmount = position.get("soldAmount") 563 | userID = position.get("userID") 564 | 565 | trader_collection = await get_database("traders") 566 | brokerage_collection = await get_database("brokerageCollection") 567 | trader = await trader_collection.find_one({"_id": ObjectId(userID)}) 568 | brokerage = await brokerage_collection.find_one({"_id": ObjectId(trader["brokerageName"])}) 569 | 570 | alpaca_api_key = brokerage["API_KEY"] 571 | alpaca_secret_key = brokerage["SECRET_KEY"] 572 | check_live_trading = brokerage["liveTrading"] 573 | 574 | paper_url = os.getenv("PAPER_URL") 575 | 576 | payload = { 577 | "type": "market", 578 | "time_in_force": "day", 579 | "symbol": orderSymbol, 580 | "qty": sellAll.amount, 581 | "side": "sell", 582 | } 583 | headers = { 584 | "accept": "application/json", 585 | "content-type": "application/json", 586 | "APCA-API-KEY-ID": alpaca_api_key, 587 | "APCA-API-SECRET-KEY": alpaca_secret_key 588 | } 589 | if check_live_trading == True: 590 | url = "https://api.alpaca.markets/v2/orders" 591 | else: 592 | url = f"{paper_url}/v2/orders" 593 | 594 | response = requests.post(url, json=payload, headers=headers) 595 | print("response: ", response.json()) 596 | tradingId = response.json()["id"] 597 | 598 | if(response.status_code == 200): 599 | await asyncio.sleep(2) 600 | if check_live_trading == True: 601 | url = "https://api.alpaca.markets/v2/orders?status=all&symbols="+orderSymbol 602 | else: 603 | url = "https://paper-api.alpaca.markets/v2/orders?status=all&symbols="+orderSymbol 604 | response = requests.get(url, headers=headers) 605 | 606 | price = 0 607 | for order in response.json(): 608 | if order["id"] == tradingId: 609 | closePrice = order["filled_avg_price"] 610 | 611 | exitTimestamp = order["filled_at"] 612 | 613 | if (position_soldAmount + sellAll.amount) < position_amount: 614 | await position_collection.update_one( 615 | {"_id": ObjectId(sellAll.id)}, 616 | {"$set": { "closePrice": closePrice, "soldAmount": position_soldAmount + sellAll.amount}} 617 | ) 618 | elif (position_soldAmount + sellAll.amount) == position_amount or (position_soldAmount + sellAll.amount) > position_amount: 619 | await position_collection.update_one( 620 | {"_id": ObjectId(sellAll.id)}, 621 | {"$set": {"status": "closed", "closePrice": closePrice, "soldAmount": position_amount, "exitDate": exitTimestamp}} 622 | ) 623 | return {"message": "Sell order processed successfully", "status": "success", "exitPrice": price} 624 | break; 625 | 626 | return 200 627 | else: 628 | return 422 629 | except Exception as e: 630 | print(f"Error updating position: {e}") 631 | raise HTTPException(status_code=500, detail="Failed to update position") 632 | 633 | except Exception as e: 634 | print(f"Error selling all positions: {str(e)}") 635 | raise HTTPException(status_code=500, detail="Failed to sell all positions") 636 | 637 | 638 | class StartStopTrader(BaseModel): 639 | id: str 640 | 641 | @router.post("/startStopTrader") 642 | async def start_stop_trader(startStopTrader: StartStopTrader): 643 | try: 644 | trader_collection = await get_database("traders") 645 | trader = await trader_collection.find_one({"_id": ObjectId(startStopTrader.id)}) 646 | if trader: 647 | trader_status = trader["status"] 648 | if trader_status == "stop": 649 | result = await trader_collection.update_one( 650 | {"_id": ObjectId(startStopTrader.id)}, 651 | {"$set": {"status": "start"}} 652 | ) 653 | else: 654 | result = await trader_collection.update_one( 655 | {"_id": ObjectId(startStopTrader.id)}, 656 | {"$set": {"status": "stop"}} 657 | ) 658 | return {"message": "Trader status updated successfully"} 659 | except Exception as e: 660 | print(f"Error updating trader status: {str(e)}") 661 | raise HTTPException(status_code=500, detail="Failed to update trader status") 662 | 663 | class StartStopAnalyst(BaseModel): 664 | id: str 665 | 666 | @router.post("/startStopAnalyst") 667 | async def start_stop_analyst(startStopAnalyst: StartStopAnalyst): 668 | try: 669 | analyst_collection = await get_database("analyst") 670 | analyst = await analyst_collection.find_one({"_id": ObjectId(startStopAnalyst.id)}) 671 | if analyst: 672 | analyst_status = analyst["status"] 673 | if analyst_status == "stop": 674 | result = await analyst_collection.update_one( 675 | {"_id": ObjectId(startStopAnalyst.id)}, 676 | {"$set": {"status": "start"}} 677 | ) 678 | else: 679 | result = await analyst_collection.update_one( 680 | {"_id": ObjectId(startStopAnalyst.id)}, 681 | {"$set": {"status": "stop"}} 682 | ) 683 | return {"message": "Analyst status updated successfully"} 684 | except Exception as e: 685 | print(f"Error updating analyst status: {str(e)}") 686 | raise HTTPException(status_code=500, detail="Failed to update analyst status") 687 | -------------------------------------------------------------------------------- /api/routes/utils.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timezone 2 | from zoneinfo import ZoneInfo 3 | 4 | def parse_option_date(symbol): 5 | # Find where the date part starts (first digit after letters) 6 | date_start = 0 7 | for i, char in enumerate(symbol): 8 | if char.isdigit(): 9 | date_start = i 10 | break 11 | 12 | # Extract the date portion (always 6 digits: YYMMDD) 13 | date_portion = symbol[date_start:date_start+6] 14 | 15 | # Get month and date 16 | month = date_portion[2:4] # 3rd and 4th characters 17 | date = date_portion[4:6] # 5th and 6th characters 18 | 19 | return month, date 20 | 21 | def check_option_expiry(month, date): 22 | # Get current date in ET 23 | current_et = datetime.now(ZoneInfo("America/New_York")) 24 | current_month = int(current_et.strftime("%m")) 25 | current_date = int(current_et.strftime("%d")) 26 | 27 | # Convert input month and date to integers 28 | option_month = int(month) 29 | option_date = int(date) 30 | 31 | print(f"Checking expiry - Current: {current_month}/{current_date}, Option: {option_month}/{option_date}") 32 | 33 | # Compare dates 34 | if current_month > option_month: 35 | # print("Option expired: Current month is later than option month") 36 | return False 37 | elif current_month < option_month: 38 | # print("Option valid: Current month is earlier than option month") 39 | return True 40 | else: # Same month 41 | if current_date > option_date: 42 | # print("Option expired: Same month but current date is later") 43 | return False 44 | else: 45 | # print("Option valid: Same month and current date is not later") 46 | return True 47 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.104.1 2 | uvicorn==0.24.0 3 | 4 | python-dotenv==1.0.0 5 | passlib==1.7.4 6 | bcrypt==4.0.1 7 | python-jose[cryptography]==3.3.0 8 | python-multipart==0.0.6 9 | pyjwt==2.8.0 10 | mangum==0.17.0 11 | dnspython==2.4.2 12 | pytest==7.4.3 13 | httpx==0.25.1 14 | pymongo>=4.6.0 15 | motor>=3.3.0 16 | 17 | requests == 2.32.3 18 | apscheduler 19 | ntplib 20 | 21 | # tradestation 22 | # ts-api -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "api/index.py", 6 | "use": "@vercel/python" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/(.*)", 12 | "dest": "api/index.py" 13 | } 14 | ] 15 | } 16 | --------------------------------------------------------------------------------