ollama-robin 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ollama_robin-0.1.0/.gitattributes +2 -0
- ollama_robin-0.1.0/.gitignore +17 -0
- ollama_robin-0.1.0/PKG-INFO +88 -0
- ollama_robin-0.1.0/README.md +72 -0
- ollama_robin-0.1.0/pyproject.toml +28 -0
- ollama_robin-0.1.0/requirements.txt +4 -0
- ollama_robin-0.1.0/src/ollama_robin/__init__.py +1 -0
- ollama_robin-0.1.0/src/ollama_robin/chat.py +139 -0
- ollama_robin-0.1.0/src/ollama_robin/tools/__init__.py +1 -0
- ollama_robin-0.1.0/src/ollama_robin/tools/formatter.py +22 -0
- ollama_robin-0.1.0/src/ollama_robin/tools/handlers.py +369 -0
- ollama_robin-0.1.0/src/ollama_robin/tools/schemas.py +367 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Local environment variables (Contains passwords and API keys)
|
|
2
|
+
.env
|
|
3
|
+
.env.local
|
|
4
|
+
.env.*
|
|
5
|
+
|
|
6
|
+
# Python Cache & Build
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.py[cod]
|
|
9
|
+
*$py.class
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
env/
|
|
14
|
+
|
|
15
|
+
# OS files
|
|
16
|
+
.DS_Store
|
|
17
|
+
Thumbs.db
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ollama-robin
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Terminal-based AI assistant powered by local Ollama with Exa search and Robinhood tools
|
|
5
|
+
Author-email: Isaiah <isaiahgwood@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Requires-Dist: exa-py>=2.13.2
|
|
12
|
+
Requires-Dist: ollama>=0.6.2
|
|
13
|
+
Requires-Dist: python-dotenv>=1.0.1
|
|
14
|
+
Requires-Dist: robin-stocks>=3.0.0
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Ollama Robinhood Chat 🤖📈
|
|
18
|
+
|
|
19
|
+
An interactive, terminal-based AI assistant powered by local Ollama (`gemma4:e4b`) with built-in tools for **Exa Web Search** and complete **Robinhood Trading & Portfolio Automation** via the `robin_stocks` SDK.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Key Features
|
|
24
|
+
|
|
25
|
+
- **Local AI Chat**: Chat natively with `gemma4:e4b` running on your local Ollama instance.
|
|
26
|
+
- **Web Search Tool**: Powered by Exa Search API to retrieve real-time news, information, and answers.
|
|
27
|
+
- **Robinhood Portfolio Analytics**: Fetch accounts profile details, portfolio values, cash balances, buying power, and open stock/options positions.
|
|
28
|
+
- **Equities & Options Trading**: Query real-time quotes, chains, historical charts, list watchlists, place market/limit orders, and cancel open orders.
|
|
29
|
+
- **Pre-Trade Simulation Reviews**: Safe order review tools calculate estimated costs, check buying power, and inspect asset tradability flags before committing any trades.
|
|
30
|
+
- **Smart Decimal Formatting**: Automatically sanitizes raw Robinhood API outputs (e.g., converting `$2.7100` to `$2.71`) for clean AI communication.
|
|
31
|
+
- **Interactive Multi-Factor Authentication (MFA)**: Prompts dynamically in the terminal to securely type in SMS or authenticator codes upon boot.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Project Structure
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
ollama-robin/
|
|
39
|
+
├── .env.example # Template env file for credentials
|
|
40
|
+
├── .gitignore # Prevents committing API keys / secrets
|
|
41
|
+
├── README.md # Guide & setup documentation
|
|
42
|
+
├── requirements.txt # Pinned python dependency packages
|
|
43
|
+
├── chat.py # Main interactive CLI chat loop
|
|
44
|
+
└── tools/
|
|
45
|
+
├── __init__.py
|
|
46
|
+
├── formatter.py # Recursive clean-up helper (2.7100 -> 2.71)
|
|
47
|
+
├── schemas.py # Declarative JSON schemas for Ollama tools
|
|
48
|
+
└── handlers.py # Python logic calling Exa and Robinhood APIs
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Installation & Setup
|
|
54
|
+
|
|
55
|
+
### 1. Prerequisites
|
|
56
|
+
- [Python 3.10+](https://www.python.org/)
|
|
57
|
+
- [Ollama](https://ollama.com/) running locally with `gemma4:e4b` pulled:
|
|
58
|
+
```bash
|
|
59
|
+
ollama pull gemma4:e4b
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 2. Install Python Dependencies
|
|
63
|
+
```bash
|
|
64
|
+
pip install ollama exa-py robin_stocks python-dotenv
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 3. Configure Environment Variables
|
|
68
|
+
Create a file named `.env.local` in the project root:
|
|
69
|
+
```env
|
|
70
|
+
# Exa API Key
|
|
71
|
+
EXA_API_KEY="your-exa-api-key-here"
|
|
72
|
+
|
|
73
|
+
# Robinhood Credentials
|
|
74
|
+
ROBINHOOD_USERNAME="your-login-email@example.com"
|
|
75
|
+
ROBINHOOD_PASSWORD="your-robinhood-password"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Running the Application
|
|
81
|
+
|
|
82
|
+
To start the interactive chat client:
|
|
83
|
+
```bash
|
|
84
|
+
python chat.py
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- **MFA Warning**: If your Robinhood account has MFA active, the script will pause and prompt you to input the code directly into the terminal window during startup.
|
|
88
|
+
- **Fallback Mode**: If you run without Robinhood credentials, the chat will automatically disable Robinhood tools and fall back to standard chat + Exa web search.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Ollama Robinhood Chat 🤖📈
|
|
2
|
+
|
|
3
|
+
An interactive, terminal-based AI assistant powered by local Ollama (`gemma4:e4b`) with built-in tools for **Exa Web Search** and complete **Robinhood Trading & Portfolio Automation** via the `robin_stocks` SDK.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Key Features
|
|
8
|
+
|
|
9
|
+
- **Local AI Chat**: Chat natively with `gemma4:e4b` running on your local Ollama instance.
|
|
10
|
+
- **Web Search Tool**: Powered by Exa Search API to retrieve real-time news, information, and answers.
|
|
11
|
+
- **Robinhood Portfolio Analytics**: Fetch accounts profile details, portfolio values, cash balances, buying power, and open stock/options positions.
|
|
12
|
+
- **Equities & Options Trading**: Query real-time quotes, chains, historical charts, list watchlists, place market/limit orders, and cancel open orders.
|
|
13
|
+
- **Pre-Trade Simulation Reviews**: Safe order review tools calculate estimated costs, check buying power, and inspect asset tradability flags before committing any trades.
|
|
14
|
+
- **Smart Decimal Formatting**: Automatically sanitizes raw Robinhood API outputs (e.g., converting `$2.7100` to `$2.71`) for clean AI communication.
|
|
15
|
+
- **Interactive Multi-Factor Authentication (MFA)**: Prompts dynamically in the terminal to securely type in SMS or authenticator codes upon boot.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Project Structure
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
ollama-robin/
|
|
23
|
+
├── .env.example # Template env file for credentials
|
|
24
|
+
├── .gitignore # Prevents committing API keys / secrets
|
|
25
|
+
├── README.md # Guide & setup documentation
|
|
26
|
+
├── requirements.txt # Pinned python dependency packages
|
|
27
|
+
├── chat.py # Main interactive CLI chat loop
|
|
28
|
+
└── tools/
|
|
29
|
+
├── __init__.py
|
|
30
|
+
├── formatter.py # Recursive clean-up helper (2.7100 -> 2.71)
|
|
31
|
+
├── schemas.py # Declarative JSON schemas for Ollama tools
|
|
32
|
+
└── handlers.py # Python logic calling Exa and Robinhood APIs
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Installation & Setup
|
|
38
|
+
|
|
39
|
+
### 1. Prerequisites
|
|
40
|
+
- [Python 3.10+](https://www.python.org/)
|
|
41
|
+
- [Ollama](https://ollama.com/) running locally with `gemma4:e4b` pulled:
|
|
42
|
+
```bash
|
|
43
|
+
ollama pull gemma4:e4b
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Install Python Dependencies
|
|
47
|
+
```bash
|
|
48
|
+
pip install ollama exa-py robin_stocks python-dotenv
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. Configure Environment Variables
|
|
52
|
+
Create a file named `.env.local` in the project root:
|
|
53
|
+
```env
|
|
54
|
+
# Exa API Key
|
|
55
|
+
EXA_API_KEY="your-exa-api-key-here"
|
|
56
|
+
|
|
57
|
+
# Robinhood Credentials
|
|
58
|
+
ROBINHOOD_USERNAME="your-login-email@example.com"
|
|
59
|
+
ROBINHOOD_PASSWORD="your-robinhood-password"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Running the Application
|
|
65
|
+
|
|
66
|
+
To start the interactive chat client:
|
|
67
|
+
```bash
|
|
68
|
+
python chat.py
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- **MFA Warning**: If your Robinhood account has MFA active, the script will pause and prompt you to input the code directly into the terminal window during startup.
|
|
72
|
+
- **Fallback Mode**: If you run without Robinhood credentials, the chat will automatically disable Robinhood tools and fall back to standard chat + Exa web search.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ollama-robin"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Terminal-based AI assistant powered by local Ollama with Exa search and Robinhood tools"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Isaiah", email = "isaiahgwood@gmail.com" }
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"ollama>=0.6.2",
|
|
22
|
+
"exa-py>=2.13.2",
|
|
23
|
+
"robin_stocks>=3.0.0",
|
|
24
|
+
"python-dotenv>=1.0.1",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
ollama-robin = "ollama_robin.chat:main"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Package initialization
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import ollama
|
|
4
|
+
from exa_py import Exa
|
|
5
|
+
import robin_stocks.robinhood as rh
|
|
6
|
+
from dotenv import load_dotenv
|
|
7
|
+
|
|
8
|
+
# Import our modular tools package
|
|
9
|
+
from ollama_robin.tools.schemas import tools
|
|
10
|
+
from ollama_robin.tools.handlers import handle_tool_call
|
|
11
|
+
|
|
12
|
+
# Load environment variables
|
|
13
|
+
load_dotenv()
|
|
14
|
+
load_dotenv('.env.local')
|
|
15
|
+
|
|
16
|
+
def main():
|
|
17
|
+
model = 'gemma4:e4b'
|
|
18
|
+
|
|
19
|
+
# Verify/get model list
|
|
20
|
+
try:
|
|
21
|
+
response = ollama.list()
|
|
22
|
+
models = [m.model for m in response.models]
|
|
23
|
+
if not models:
|
|
24
|
+
print("No models found. Please pull a model first.")
|
|
25
|
+
sys.exit(1)
|
|
26
|
+
if model not in models and len(models) > 0:
|
|
27
|
+
matching_models = [m for m in models if 'gemma4' in m]
|
|
28
|
+
model = matching_models[0] if matching_models else models[0]
|
|
29
|
+
except Exception as e:
|
|
30
|
+
print(f"Warning: Could not connect to Ollama. Error: {e}")
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
|
|
33
|
+
# Check for Exa API Key
|
|
34
|
+
exa_api_key = os.environ.get("EXA_API_KEY")
|
|
35
|
+
exa_client = None
|
|
36
|
+
if exa_api_key:
|
|
37
|
+
exa_client = Exa(api_key=exa_api_key)
|
|
38
|
+
print("Exa Search Tool enabled.")
|
|
39
|
+
else:
|
|
40
|
+
print("Warning: EXA_API_KEY not found in environment. Exa Search Tool is disabled.")
|
|
41
|
+
|
|
42
|
+
# Check for Robinhood login
|
|
43
|
+
rb_username = os.environ.get("ROBINHOOD_USERNAME")
|
|
44
|
+
rb_password = os.environ.get("ROBINHOOD_PASSWORD")
|
|
45
|
+
rb_enabled = False
|
|
46
|
+
|
|
47
|
+
if rb_username and rb_password:
|
|
48
|
+
try:
|
|
49
|
+
print("Attempting login to Robinhood...")
|
|
50
|
+
login_res = rh.login(username=rb_username, password=rb_password)
|
|
51
|
+
if login_res:
|
|
52
|
+
rb_enabled = True
|
|
53
|
+
print("Successfully logged into Robinhood.")
|
|
54
|
+
else:
|
|
55
|
+
print("Warning: Robinhood login failed.")
|
|
56
|
+
except Exception as e:
|
|
57
|
+
print(f"Warning: Could not login to Robinhood. Error: {e}")
|
|
58
|
+
else:
|
|
59
|
+
print("Warning: ROBINHOOD_USERNAME/ROBINHOOD_PASSWORD not set. Robinhood tools disabled.")
|
|
60
|
+
|
|
61
|
+
# Active tools list based on configuration
|
|
62
|
+
active_tools = []
|
|
63
|
+
if exa_client:
|
|
64
|
+
active_tools.append(tools[0]) # search_web
|
|
65
|
+
if rb_enabled:
|
|
66
|
+
active_tools.extend(tools[1:])
|
|
67
|
+
|
|
68
|
+
print(f"--- Chatting with model: {model} (type 'exit' or 'quit' to stop) ---")
|
|
69
|
+
messages = []
|
|
70
|
+
|
|
71
|
+
while True:
|
|
72
|
+
try:
|
|
73
|
+
user_input = input("\nYou: ").strip()
|
|
74
|
+
if not user_input:
|
|
75
|
+
continue
|
|
76
|
+
if user_input.lower() in ['exit', 'quit']:
|
|
77
|
+
print("Goodbye!")
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
messages.append({'role': 'user', 'content': user_input})
|
|
81
|
+
|
|
82
|
+
# Send message with active tools
|
|
83
|
+
response = ollama.chat(
|
|
84
|
+
model=model,
|
|
85
|
+
messages=messages,
|
|
86
|
+
tools=active_tools if active_tools else None
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
assistant_message = response.get('message', {})
|
|
90
|
+
tool_calls = assistant_message.get('tool_calls', [])
|
|
91
|
+
|
|
92
|
+
thinking = assistant_message.get('content', '')
|
|
93
|
+
if thinking:
|
|
94
|
+
print(f"\n{model} (thinking):\n{thinking}")
|
|
95
|
+
|
|
96
|
+
# If model requested tool execution
|
|
97
|
+
if tool_calls:
|
|
98
|
+
messages.append(assistant_message)
|
|
99
|
+
|
|
100
|
+
for tool_call in tool_calls:
|
|
101
|
+
func_name = tool_call.get('function', {}).get('name')
|
|
102
|
+
args = tool_call.get('function', {}).get('arguments', {})
|
|
103
|
+
|
|
104
|
+
tool_response = handle_tool_call(func_name, args, exa_client, rb_enabled)
|
|
105
|
+
|
|
106
|
+
messages.append({
|
|
107
|
+
'role': 'tool',
|
|
108
|
+
'name': func_name,
|
|
109
|
+
'content': tool_response
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
# Fetch final response after tools execution
|
|
113
|
+
print(f"{model}: ", end="", flush=True)
|
|
114
|
+
response_content = ""
|
|
115
|
+
|
|
116
|
+
# Stream the final response
|
|
117
|
+
stream = ollama.chat(model=model, messages=messages, stream=True)
|
|
118
|
+
for chunk in stream:
|
|
119
|
+
content = chunk['message']['content']
|
|
120
|
+
print(content, end="", flush=True)
|
|
121
|
+
response_content += content
|
|
122
|
+
print()
|
|
123
|
+
|
|
124
|
+
messages.append({'role': 'assistant', 'content': response_content})
|
|
125
|
+
|
|
126
|
+
else:
|
|
127
|
+
content = assistant_message.get('content', '')
|
|
128
|
+
if not thinking:
|
|
129
|
+
print(f"{model}: {content}")
|
|
130
|
+
messages.append(assistant_message)
|
|
131
|
+
|
|
132
|
+
except KeyboardInterrupt:
|
|
133
|
+
print("\nGoodbye!")
|
|
134
|
+
break
|
|
135
|
+
except Exception as e:
|
|
136
|
+
print(f"\nError: {e}")
|
|
137
|
+
|
|
138
|
+
if __name__ == '__main__':
|
|
139
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Tools package initialization
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
def format_numeric_values(data):
|
|
4
|
+
if isinstance(data, dict):
|
|
5
|
+
return {k: format_numeric_values(v) for k, v in data.items()}
|
|
6
|
+
elif isinstance(data, list):
|
|
7
|
+
return [format_numeric_values(v) for v in data]
|
|
8
|
+
elif isinstance(data, str):
|
|
9
|
+
try:
|
|
10
|
+
if '.' in data:
|
|
11
|
+
val = float(data)
|
|
12
|
+
if abs(val) >= 0.01:
|
|
13
|
+
return f"{val:.2f}"
|
|
14
|
+
except ValueError:
|
|
15
|
+
pass
|
|
16
|
+
elif isinstance(data, float):
|
|
17
|
+
if abs(data) >= 0.01:
|
|
18
|
+
return round(data, 2)
|
|
19
|
+
return data
|
|
20
|
+
|
|
21
|
+
def serialize(data):
|
|
22
|
+
return json.dumps(format_numeric_values(data), indent=2)
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import robin_stocks.robinhood as rh
|
|
3
|
+
from ollama_robin.tools.formatter import serialize
|
|
4
|
+
|
|
5
|
+
def handle_tool_call(func_name, args, exa_client, rb_enabled):
|
|
6
|
+
tool_response = ""
|
|
7
|
+
|
|
8
|
+
# 1. Search Web
|
|
9
|
+
if func_name == 'search_web' and exa_client:
|
|
10
|
+
query = args.get('query')
|
|
11
|
+
try:
|
|
12
|
+
search_results = exa_client.search(
|
|
13
|
+
query, type="auto", num_results=5, contents={"highlights": True}
|
|
14
|
+
)
|
|
15
|
+
formatted_results = []
|
|
16
|
+
for idx, result in enumerate(search_results.results, 1):
|
|
17
|
+
title = result.title or "Untitled"
|
|
18
|
+
url = result.url
|
|
19
|
+
highlight = "\n".join(result.highlights) if result.highlights else ""
|
|
20
|
+
formatted_results.append(f"[{idx}] {title}\nURL: {url}\nExcerpt: {highlight}\n")
|
|
21
|
+
tool_response = "\n".join(formatted_results) if formatted_results else "No results found."
|
|
22
|
+
except Exception as err:
|
|
23
|
+
tool_response = f"Error performing search: {err}"
|
|
24
|
+
|
|
25
|
+
# 2. Get Accounts
|
|
26
|
+
elif func_name == 'get_accounts' and rb_enabled:
|
|
27
|
+
try:
|
|
28
|
+
tool_response = serialize(rh.profiles.load_account_profile())
|
|
29
|
+
except Exception as err:
|
|
30
|
+
tool_response = f"Error: {err}"
|
|
31
|
+
|
|
32
|
+
# 3. Get Portfolio
|
|
33
|
+
elif func_name == 'get_portfolio' and rb_enabled:
|
|
34
|
+
try:
|
|
35
|
+
portfolio = rh.profiles.load_portfolio_profile()
|
|
36
|
+
basic = rh.profiles.load_basic_profile()
|
|
37
|
+
combined = {
|
|
38
|
+
"equity": portfolio.get("equity"),
|
|
39
|
+
"extended_hours_equity": portfolio.get("extended_hours_equity"),
|
|
40
|
+
"market_value": portfolio.get("market_value"),
|
|
41
|
+
"excess_margin": portfolio.get("excess_margin"),
|
|
42
|
+
"buying_power": portfolio.get("buying_power"),
|
|
43
|
+
"cash": portfolio.get("cash"),
|
|
44
|
+
"cash_available_for_withdrawal": portfolio.get("cash_available_for_withdrawal"),
|
|
45
|
+
"user_first_name": basic.get("first_name")
|
|
46
|
+
}
|
|
47
|
+
tool_response = serialize(combined)
|
|
48
|
+
except Exception as err:
|
|
49
|
+
tool_response = f"Error: {err}"
|
|
50
|
+
|
|
51
|
+
# 4. Get Equity Positions
|
|
52
|
+
elif func_name == 'get_equity_positions' and rb_enabled:
|
|
53
|
+
try:
|
|
54
|
+
positions = rh.get_open_stock_positions()
|
|
55
|
+
for pos in positions:
|
|
56
|
+
try:
|
|
57
|
+
pos['symbol'] = rh.get_symbol_by_url(pos['instrument'])
|
|
58
|
+
except:
|
|
59
|
+
pos['symbol'] = "UNKNOWN"
|
|
60
|
+
tool_response = serialize(positions)
|
|
61
|
+
except Exception as err:
|
|
62
|
+
tool_response = f"Error: {err}"
|
|
63
|
+
|
|
64
|
+
# 5. Get Equity Quotes
|
|
65
|
+
elif func_name == 'get_equity_quotes' and rb_enabled:
|
|
66
|
+
syms = [s.strip().upper() for s in args.get('symbols', '').split(',') if s.strip()]
|
|
67
|
+
try:
|
|
68
|
+
quotes = rh.get_quotes(syms)
|
|
69
|
+
tool_response = serialize(quotes)
|
|
70
|
+
except Exception as err:
|
|
71
|
+
tool_response = f"Error: {err}"
|
|
72
|
+
|
|
73
|
+
# 6. Get Equity Orders
|
|
74
|
+
elif func_name == 'get_equity_orders' and rb_enabled:
|
|
75
|
+
try:
|
|
76
|
+
orders = rh.get_all_stock_orders()[:15]
|
|
77
|
+
tool_response = serialize(orders)
|
|
78
|
+
except Exception as err:
|
|
79
|
+
tool_response = f"Error: {err}"
|
|
80
|
+
|
|
81
|
+
# 7. Get Equity Tradability
|
|
82
|
+
elif func_name == 'get_equity_tradability' and rb_enabled:
|
|
83
|
+
syms = [s.strip().upper() for s in args.get('symbols', '').split(',') if s.strip()]
|
|
84
|
+
try:
|
|
85
|
+
instruments = rh.get_instruments_by_symbols(syms)
|
|
86
|
+
tradability = []
|
|
87
|
+
for inst in instruments:
|
|
88
|
+
if inst:
|
|
89
|
+
tradability.append({
|
|
90
|
+
"symbol": inst.get("symbol"),
|
|
91
|
+
"tradeable": inst.get("tradeable"),
|
|
92
|
+
"fractional_tradability": inst.get("fractional_tradability"),
|
|
93
|
+
"state": inst.get("state")
|
|
94
|
+
})
|
|
95
|
+
tool_response = serialize(tradability)
|
|
96
|
+
except Exception as err:
|
|
97
|
+
tool_response = f"Error: {err}"
|
|
98
|
+
|
|
99
|
+
# 8. Review Equity Order
|
|
100
|
+
elif func_name == 'review_equity_order' and rb_enabled:
|
|
101
|
+
symbol = args.get('symbol').upper()
|
|
102
|
+
qty = float(args.get('quantity'))
|
|
103
|
+
side = args.get('side').lower()
|
|
104
|
+
o_type = args.get('type').lower()
|
|
105
|
+
limit_price = args.get('limit_price')
|
|
106
|
+
try:
|
|
107
|
+
quote = rh.get_quotes(symbol)[0]
|
|
108
|
+
last_price = float(quote.get('last_trade_price', 0)) if quote else 0
|
|
109
|
+
price = limit_price if o_type == 'limit' else last_price
|
|
110
|
+
est_cost = price * qty
|
|
111
|
+
portfolio = rh.profiles.load_portfolio_profile()
|
|
112
|
+
buying_power = float(portfolio.get('buying_power', 0))
|
|
113
|
+
warnings = []
|
|
114
|
+
if side == 'buy' and est_cost > buying_power:
|
|
115
|
+
warnings.append("WARNING: Estimated cost exceeds current buying power.")
|
|
116
|
+
inst = rh.get_instruments_by_symbols(symbol)[0]
|
|
117
|
+
if inst:
|
|
118
|
+
if not inst.get('tradeable'):
|
|
119
|
+
warnings.append("WARNING: Asset is currently not tradeable on Robinhood.")
|
|
120
|
+
if qty % 1 != 0 and not inst.get('fractional_tradability'):
|
|
121
|
+
warnings.append("WARNING: Fractional shares are not supported for this symbol.")
|
|
122
|
+
else:
|
|
123
|
+
warnings.append("WARNING: Ticker symbol could not be found.")
|
|
124
|
+
|
|
125
|
+
review = {
|
|
126
|
+
"symbol": symbol,
|
|
127
|
+
"side": side,
|
|
128
|
+
"quantity": qty,
|
|
129
|
+
"price_used": price,
|
|
130
|
+
"estimated_cost": est_cost,
|
|
131
|
+
"available_buying_power": buying_power,
|
|
132
|
+
"warnings": warnings if warnings else ["None. Order simulation looks good."]
|
|
133
|
+
}
|
|
134
|
+
tool_response = serialize(review)
|
|
135
|
+
except Exception as err:
|
|
136
|
+
tool_response = f"Error reviewing order: {err}"
|
|
137
|
+
|
|
138
|
+
# 9. Place Equity Order
|
|
139
|
+
elif func_name == 'place_equity_order' and rb_enabled:
|
|
140
|
+
symbol = args.get('symbol').upper()
|
|
141
|
+
qty = float(args.get('quantity'))
|
|
142
|
+
side = args.get('side').lower()
|
|
143
|
+
o_type = args.get('type').lower()
|
|
144
|
+
limit_price = args.get('limit_price')
|
|
145
|
+
try:
|
|
146
|
+
if side == 'buy':
|
|
147
|
+
if o_type == 'market':
|
|
148
|
+
res = rh.order_buy_market(symbol, qty)
|
|
149
|
+
else:
|
|
150
|
+
res = rh.order_buy_limit(symbol, qty, limit_price)
|
|
151
|
+
else:
|
|
152
|
+
if o_type == 'market':
|
|
153
|
+
res = rh.order_sell_market(symbol, qty)
|
|
154
|
+
else:
|
|
155
|
+
res = rh.order_sell_limit(symbol, qty, limit_price)
|
|
156
|
+
tool_response = serialize(res)
|
|
157
|
+
except Exception as err:
|
|
158
|
+
tool_response = f"Error placing order: {err}"
|
|
159
|
+
|
|
160
|
+
# 10. Cancel Equity Order
|
|
161
|
+
elif func_name == 'cancel_equity_order' and rb_enabled:
|
|
162
|
+
oid = args.get('order_id')
|
|
163
|
+
try:
|
|
164
|
+
res = rh.cancel_stock_order(oid)
|
|
165
|
+
tool_response = serialize(res)
|
|
166
|
+
except Exception as err:
|
|
167
|
+
tool_response = f"Error: {err}"
|
|
168
|
+
|
|
169
|
+
# 11. Search Symbol
|
|
170
|
+
elif func_name == 'search' and rb_enabled:
|
|
171
|
+
query = args.get('query')
|
|
172
|
+
try:
|
|
173
|
+
res = rh.find_instrument_data(query)
|
|
174
|
+
tool_response = serialize(res[:5])
|
|
175
|
+
except Exception as err:
|
|
176
|
+
tool_response = f"Error: {err}"
|
|
177
|
+
|
|
178
|
+
# 12. Add to Watchlist
|
|
179
|
+
elif func_name == 'add_to_watchlist' and rb_enabled:
|
|
180
|
+
syms = [s.strip().upper() for s in args.get('symbols', '').split(',') if s.strip()]
|
|
181
|
+
w_name = args.get('watchlist_name', 'Default')
|
|
182
|
+
try:
|
|
183
|
+
res = rh.post_symbols_to_watchlist(syms, w_name)
|
|
184
|
+
tool_response = serialize(res)
|
|
185
|
+
except Exception as err:
|
|
186
|
+
tool_response = f"Error: {err}"
|
|
187
|
+
|
|
188
|
+
# 13. Get Watchlists
|
|
189
|
+
elif func_name == 'get_watchlists' and rb_enabled:
|
|
190
|
+
try:
|
|
191
|
+
tool_response = serialize(rh.get_all_watchlists())
|
|
192
|
+
except Exception as err:
|
|
193
|
+
tool_response = f"Error: {err}"
|
|
194
|
+
|
|
195
|
+
# 14. Get Watchlist Items
|
|
196
|
+
elif func_name == 'get_watchlist_items' and rb_enabled:
|
|
197
|
+
w_name = args.get('watchlist_name')
|
|
198
|
+
try:
|
|
199
|
+
tool_response = serialize(rh.get_watchlist_by_name(w_name))
|
|
200
|
+
except Exception as err:
|
|
201
|
+
tool_response = f"Error: {err}"
|
|
202
|
+
|
|
203
|
+
# 15. Unfollow / Remove from Watchlist
|
|
204
|
+
elif func_name == 'unfollow_list' and rb_enabled:
|
|
205
|
+
syms = [s.strip().upper() for s in args.get('symbols', '').split(',') if s.strip()]
|
|
206
|
+
w_name = args.get('watchlist_name', 'Default')
|
|
207
|
+
try:
|
|
208
|
+
res = rh.delete_symbols_from_watchlist(syms, w_name)
|
|
209
|
+
tool_response = serialize(res)
|
|
210
|
+
except Exception as err:
|
|
211
|
+
tool_response = f"Error: {err}"
|
|
212
|
+
|
|
213
|
+
# 16. Get Popular Lists
|
|
214
|
+
elif func_name == 'get_popular_lists' and rb_enabled:
|
|
215
|
+
try:
|
|
216
|
+
tool_response = serialize(rh.get_top_100()[:20])
|
|
217
|
+
except Exception as err:
|
|
218
|
+
tool_response = f"Error: {err}"
|
|
219
|
+
|
|
220
|
+
# 17. Get Equity Historicals
|
|
221
|
+
elif func_name == 'get_equity_historicals' and rb_enabled:
|
|
222
|
+
syms = [s.strip().upper() for s in args.get('symbols', '').split(',') if s.strip()]
|
|
223
|
+
interval = args.get('interval', 'day')
|
|
224
|
+
span = args.get('span', 'year')
|
|
225
|
+
try:
|
|
226
|
+
res = rh.get_stock_historicals(inputSymbols=syms, interval=interval, span=span)
|
|
227
|
+
tool_response = serialize(res)
|
|
228
|
+
except Exception as err:
|
|
229
|
+
tool_response = f"Error: {err}"
|
|
230
|
+
|
|
231
|
+
# 18. Get Indexes
|
|
232
|
+
elif func_name == 'get_indexes' and rb_enabled:
|
|
233
|
+
tool_response = serialize([
|
|
234
|
+
{"index": "S&P 500", "tracked_by_etf": "SPY", "description": "Tracks 500 largest US companies"},
|
|
235
|
+
{"index": "Nasdaq 100", "tracked_by_etf": "QQQ", "description": "Tracks 100 non-financial tech giants"},
|
|
236
|
+
{"index": "Dow Jones 30", "tracked_by_etf": "DIA", "description": "Tracks 30 blue-chip US giants"}
|
|
237
|
+
])
|
|
238
|
+
|
|
239
|
+
# 19. Get Indexes Quotes
|
|
240
|
+
elif func_name == 'get_indexes_quotes' and rb_enabled:
|
|
241
|
+
try:
|
|
242
|
+
quotes = rh.get_quotes(["SPY", "QQQ", "DIA"])
|
|
243
|
+
formatted = []
|
|
244
|
+
for q in quotes:
|
|
245
|
+
if q:
|
|
246
|
+
formatted.append({
|
|
247
|
+
"index": "S&P 500 (SPY)" if q.get("symbol") == "SPY" else "Nasdaq-100 (QQQ)" if q.get("symbol") == "QQQ" else "Dow-30 (DIA)",
|
|
248
|
+
"price": q.get("last_trade_price"),
|
|
249
|
+
"prior_close": q.get("previous_close")
|
|
250
|
+
})
|
|
251
|
+
tool_response = serialize(formatted)
|
|
252
|
+
except Exception as err:
|
|
253
|
+
tool_response = f"Error: {err}"
|
|
254
|
+
|
|
255
|
+
# 20. Get Option Chains
|
|
256
|
+
elif func_name == 'get_option_chains' and rb_enabled:
|
|
257
|
+
symbol = args.get('symbol').upper()
|
|
258
|
+
try:
|
|
259
|
+
chains = rh.get_chains(symbol)
|
|
260
|
+
tool_response = serialize(chains)
|
|
261
|
+
except Exception as err:
|
|
262
|
+
tool_response = f"Error: {err}"
|
|
263
|
+
|
|
264
|
+
# 21. Get Option Instruments
|
|
265
|
+
elif func_name == 'get_option_instruments' and rb_enabled:
|
|
266
|
+
symbol = args.get('symbol').upper()
|
|
267
|
+
exp = args.get('expiration_date')
|
|
268
|
+
strike = args.get('strike_price')
|
|
269
|
+
o_type = args.get('option_type')
|
|
270
|
+
try:
|
|
271
|
+
res = rh.find_options_by_expiration(inputSymbols=symbol, expirationDate=exp, optionType=o_type)
|
|
272
|
+
if strike:
|
|
273
|
+
res = [o for o in res if float(o.get('strike_price', 0)) == strike]
|
|
274
|
+
tool_response = serialize(res[:10])
|
|
275
|
+
except Exception as err:
|
|
276
|
+
tool_response = f"Error: {err}"
|
|
277
|
+
|
|
278
|
+
# 22. Get Option Quotes
|
|
279
|
+
elif func_name == 'get_option_quotes' and rb_enabled:
|
|
280
|
+
symbol = args.get('symbol').upper()
|
|
281
|
+
exp = args.get('expiration_date')
|
|
282
|
+
strike = float(args.get('strike_price'))
|
|
283
|
+
o_type = args.get('option_type').lower()
|
|
284
|
+
try:
|
|
285
|
+
res = rh.get_option_market_data(symbol, exp, str(strike), o_type)
|
|
286
|
+
tool_response = serialize(res)
|
|
287
|
+
except Exception as err:
|
|
288
|
+
tool_response = f"Error: {err}"
|
|
289
|
+
|
|
290
|
+
# 23. Get Option Positions
|
|
291
|
+
elif func_name == 'get_option_positions' and rb_enabled:
|
|
292
|
+
try:
|
|
293
|
+
tool_response = serialize(rh.get_open_option_positions())
|
|
294
|
+
except Exception as err:
|
|
295
|
+
tool_response = f"Error: {err}"
|
|
296
|
+
|
|
297
|
+
# 24. Get Option Orders
|
|
298
|
+
elif func_name == 'get_option_orders' and rb_enabled:
|
|
299
|
+
try:
|
|
300
|
+
tool_response = serialize(rh.get_all_option_orders()[:10])
|
|
301
|
+
except Exception as err:
|
|
302
|
+
tool_response = f"Error: {err}"
|
|
303
|
+
|
|
304
|
+
# 25. Review Option Order
|
|
305
|
+
elif func_name == 'review_option_order' and rb_enabled:
|
|
306
|
+
symbol = args.get('symbol').upper()
|
|
307
|
+
exp = args.get('expiration_date')
|
|
308
|
+
strike = float(args.get('strike_price'))
|
|
309
|
+
o_type = args.get('option_type').lower()
|
|
310
|
+
qty = float(args.get('quantity'))
|
|
311
|
+
price = float(args.get('price'))
|
|
312
|
+
side = args.get('side').lower()
|
|
313
|
+
try:
|
|
314
|
+
market_data = rh.get_option_market_data(symbol, exp, str(strike), o_type)[0][0]
|
|
315
|
+
ask = float(market_data.get('ask_price', 0))
|
|
316
|
+
bid = float(market_data.get('bid_price', 0))
|
|
317
|
+
est_cost = price * qty * 100
|
|
318
|
+
portfolio = rh.profiles.load_portfolio_profile()
|
|
319
|
+
buying_power = float(portfolio.get('buying_power', 0))
|
|
320
|
+
warnings = []
|
|
321
|
+
if side == 'buy' and est_cost > buying_power:
|
|
322
|
+
warnings.append("WARNING: Estimated debit cost exceeds current buying power.")
|
|
323
|
+
|
|
324
|
+
review = {
|
|
325
|
+
"underlying": symbol,
|
|
326
|
+
"option": f"{strike}{o_type[0]} exp {exp}",
|
|
327
|
+
"side": side,
|
|
328
|
+
"quantity": qty,
|
|
329
|
+
"limit_price": price,
|
|
330
|
+
"ask_price": ask,
|
|
331
|
+
"bid_price": bid,
|
|
332
|
+
"estimated_cost": est_cost,
|
|
333
|
+
"available_buying_power": buying_power,
|
|
334
|
+
"warnings": warnings if warnings else ["None. Simulation looks good."]
|
|
335
|
+
}
|
|
336
|
+
tool_response = serialize(review)
|
|
337
|
+
except Exception as err:
|
|
338
|
+
tool_response = f"Error: {err}"
|
|
339
|
+
|
|
340
|
+
# 26. Place Option Order
|
|
341
|
+
elif func_name == 'place_option_order' and rb_enabled:
|
|
342
|
+
symbol = args.get('symbol').upper()
|
|
343
|
+
exp = args.get('expiration_date')
|
|
344
|
+
strike = float(args.get('strike_price'))
|
|
345
|
+
o_type = args.get('option_type').lower()
|
|
346
|
+
qty = int(args.get('quantity'))
|
|
347
|
+
price = float(args.get('price'))
|
|
348
|
+
side = args.get('side').lower()
|
|
349
|
+
try:
|
|
350
|
+
effect = "open" if side == 'buy' else "close"
|
|
351
|
+
cd = "debit" if side == 'buy' else "credit"
|
|
352
|
+
res = rh.order_buy_option_limit(
|
|
353
|
+
positionEffect=effect, creditOrDebit=cd, price=price, symbol=symbol,
|
|
354
|
+
quantity=qty, expirationDate=exp, strike=strike, optionType=o_type
|
|
355
|
+
)
|
|
356
|
+
tool_response = serialize(res)
|
|
357
|
+
except Exception as err:
|
|
358
|
+
tool_response = f"Error: {err}"
|
|
359
|
+
|
|
360
|
+
# 27. Cancel Option Order
|
|
361
|
+
elif func_name == 'cancel_option_order' and rb_enabled:
|
|
362
|
+
oid = args.get('order_id')
|
|
363
|
+
try:
|
|
364
|
+
res = rh.cancel_option_order(oid)
|
|
365
|
+
tool_response = serialize(res)
|
|
366
|
+
except Exception as err:
|
|
367
|
+
tool_response = f"Error: {err}"
|
|
368
|
+
|
|
369
|
+
return tool_response
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
# Declarative schemas for Exa and Robinhood tools
|
|
2
|
+
|
|
3
|
+
tools = [
|
|
4
|
+
# Exa search tool
|
|
5
|
+
{
|
|
6
|
+
'type': 'function',
|
|
7
|
+
'function': {
|
|
8
|
+
'name': 'search_web',
|
|
9
|
+
'description': 'Search the web using Exa to get up-to-date information, news, or answers.',
|
|
10
|
+
'parameters': {
|
|
11
|
+
'type': 'object',
|
|
12
|
+
'properties': {
|
|
13
|
+
'query': {
|
|
14
|
+
'type': 'string',
|
|
15
|
+
'description': 'The search query or question to ask.',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
'required': ['query'],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
# Portfolio & Accounts
|
|
23
|
+
{
|
|
24
|
+
'type': 'function',
|
|
25
|
+
'function': {
|
|
26
|
+
'name': 'get_accounts',
|
|
27
|
+
'description': 'View all your Robinhood accounts profile information.',
|
|
28
|
+
'parameters': {'type': 'object', 'properties': {}}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
'type': 'function',
|
|
33
|
+
'function': {
|
|
34
|
+
'name': 'get_portfolio',
|
|
35
|
+
'description': 'Get a snapshot of your portfolio including total value, buying power, and basic details.',
|
|
36
|
+
'parameters': {'type': 'object', 'properties': {}}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
'type': 'function',
|
|
41
|
+
'function': {
|
|
42
|
+
'name': 'get_equity_positions',
|
|
43
|
+
'description': 'View open equity (stock) positions with quantity and cost basis.',
|
|
44
|
+
'parameters': {'type': 'object', 'properties': {}}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
# Equities
|
|
48
|
+
{
|
|
49
|
+
'type': 'function',
|
|
50
|
+
'function': {
|
|
51
|
+
'name': 'get_equity_quotes',
|
|
52
|
+
'description': 'Get real-time equity quotes and prior close for up to 20 symbols.',
|
|
53
|
+
'parameters': {
|
|
54
|
+
'type': 'object',
|
|
55
|
+
'properties': {
|
|
56
|
+
'symbols': {
|
|
57
|
+
'type': 'string',
|
|
58
|
+
'description': 'Comma-separated tickers, e.g. "AAPL,MSFT"'
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
'required': ['symbols']
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
'type': 'function',
|
|
67
|
+
'function': {
|
|
68
|
+
'name': 'get_equity_orders',
|
|
69
|
+
'description': 'Get equity order status history.',
|
|
70
|
+
'parameters': {'type': 'object', 'properties': {}}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
'type': 'function',
|
|
75
|
+
'function': {
|
|
76
|
+
'name': 'get_equity_tradability',
|
|
77
|
+
'description': 'Check if a list of symbols can be traded and if they can be traded fractionally.',
|
|
78
|
+
'parameters': {
|
|
79
|
+
'type': 'object',
|
|
80
|
+
'properties': {
|
|
81
|
+
'symbols': {
|
|
82
|
+
'type': 'string',
|
|
83
|
+
'description': 'Comma-separated tickers, e.g. "AAPL,TSLA"'
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
'required': ['symbols']
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
'type': 'function',
|
|
92
|
+
'function': {
|
|
93
|
+
'name': 'review_equity_order',
|
|
94
|
+
'description': 'Simulate an equity order and get pre-trade warnings, estimated cost, and checks.',
|
|
95
|
+
'parameters': {
|
|
96
|
+
'type': 'object',
|
|
97
|
+
'properties': {
|
|
98
|
+
'symbol': {'type': 'string', 'description': 'The stock ticker, e.g. "AAPL"'},
|
|
99
|
+
'quantity': {'type': 'number', 'description': 'Number of shares'},
|
|
100
|
+
'side': {'type': 'string', 'enum': ['buy', 'sell'], 'description': 'Buy or sell'},
|
|
101
|
+
'type': {'type': 'string', 'enum': ['market', 'limit'], 'description': 'Order type'},
|
|
102
|
+
'limit_price': {'type': 'number', 'description': 'Limit price (required if type is limit)'}
|
|
103
|
+
},
|
|
104
|
+
'required': ['symbol', 'quantity', 'side', 'type']
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
'type': 'function',
|
|
110
|
+
'function': {
|
|
111
|
+
'name': 'place_equity_order',
|
|
112
|
+
'description': 'Place an actual equity order on Robinhood.',
|
|
113
|
+
'parameters': {
|
|
114
|
+
'type': 'object',
|
|
115
|
+
'properties': {
|
|
116
|
+
'symbol': {'type': 'string', 'description': 'The stock ticker, e.g. "AAPL"'},
|
|
117
|
+
'quantity': {'type': 'number', 'description': 'Number of shares'},
|
|
118
|
+
'side': {'type': 'string', 'enum': ['buy', 'sell'], 'description': 'Buy or sell'},
|
|
119
|
+
'type': {'type': 'string', 'enum': ['market', 'limit'], 'description': 'Order type'},
|
|
120
|
+
'limit_price': {'type': 'number', 'description': 'Limit price (required if type is limit)'}
|
|
121
|
+
},
|
|
122
|
+
'required': ['symbol', 'quantity', 'side', 'type']
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
'type': 'function',
|
|
128
|
+
'function': {
|
|
129
|
+
'name': 'cancel_equity_order',
|
|
130
|
+
'description': 'Cancel an open equity order by ID.',
|
|
131
|
+
'parameters': {
|
|
132
|
+
'type': 'object',
|
|
133
|
+
'properties': {
|
|
134
|
+
'order_id': {'type': 'string', 'description': 'The unique order ID'}
|
|
135
|
+
},
|
|
136
|
+
'required': ['order_id']
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
'type': 'function',
|
|
142
|
+
'function': {
|
|
143
|
+
'name': 'search',
|
|
144
|
+
'description': 'Find a company name or partial name to get its ticker symbol.',
|
|
145
|
+
'parameters': {
|
|
146
|
+
'type': 'object',
|
|
147
|
+
'properties': {
|
|
148
|
+
'query': {'type': 'string', 'description': 'Company name or search phrase'}
|
|
149
|
+
},
|
|
150
|
+
'required': ['query']
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
# Watchlist
|
|
155
|
+
{
|
|
156
|
+
'type': 'function',
|
|
157
|
+
'function': {
|
|
158
|
+
'name': 'add_to_watchlist',
|
|
159
|
+
'description': 'Add stock symbols to a watchlist.',
|
|
160
|
+
'parameters': {
|
|
161
|
+
'type': 'object',
|
|
162
|
+
'properties': {
|
|
163
|
+
'symbols': {'type': 'string', 'description': 'Comma-separated tickers to add'},
|
|
164
|
+
'watchlist_name': {'type': 'string', 'description': 'Watchlist name, default is "Default"'}
|
|
165
|
+
},
|
|
166
|
+
'required': ['symbols']
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
'type': 'function',
|
|
172
|
+
'function': {
|
|
173
|
+
'name': 'get_watchlists',
|
|
174
|
+
'description': 'List all watchlists on your account.',
|
|
175
|
+
'parameters': {'type': 'object', 'properties': {}}
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
'type': 'function',
|
|
180
|
+
'function': {
|
|
181
|
+
'name': 'get_watchlist_items',
|
|
182
|
+
'description': 'List symbols inside a specific watchlist.',
|
|
183
|
+
'parameters': {
|
|
184
|
+
'type': 'object',
|
|
185
|
+
'properties': {
|
|
186
|
+
'watchlist_name': {'type': 'string', 'description': 'Name of the watchlist'}
|
|
187
|
+
},
|
|
188
|
+
'required': ['watchlist_name']
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
'type': 'function',
|
|
194
|
+
'function': {
|
|
195
|
+
'name': 'unfollow_list',
|
|
196
|
+
'description': 'Remove symbols from a watchlist.',
|
|
197
|
+
'parameters': {
|
|
198
|
+
'type': 'object',
|
|
199
|
+
'properties': {
|
|
200
|
+
'symbols': {'type': 'string', 'description': 'Comma-separated tickers to remove'},
|
|
201
|
+
'watchlist_name': {'type': 'string', 'description': 'Watchlist name, default is "Default"'}
|
|
202
|
+
},
|
|
203
|
+
'required': ['symbols']
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
'type': 'function',
|
|
209
|
+
'function': {
|
|
210
|
+
'name': 'get_popular_lists',
|
|
211
|
+
'description': 'Discover popular Robinhood lists (100 most popular stocks).',
|
|
212
|
+
'parameters': {'type': 'object', 'properties': {}}
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
# Market Data
|
|
216
|
+
{
|
|
217
|
+
'type': 'function',
|
|
218
|
+
'function': {
|
|
219
|
+
'name': 'get_equity_historicals',
|
|
220
|
+
'description': 'Get OHLCV price bars across a time range.',
|
|
221
|
+
'parameters': {
|
|
222
|
+
'type': 'object',
|
|
223
|
+
'properties': {
|
|
224
|
+
'symbols': {'type': 'string', 'description': 'Comma-separated tickers, e.g. "AAPL,TSLA"'},
|
|
225
|
+
'interval': {'type': 'string', 'enum': ['5minute', '10minute', 'hour', 'day', 'week'], 'description': 'Interval of bars'},
|
|
226
|
+
'span': {'type': 'string', 'enum': ['day', 'week', 'month', '3month', 'year', '5year'], 'description': 'Time span'}
|
|
227
|
+
},
|
|
228
|
+
'required': ['symbols']
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
'type': 'function',
|
|
234
|
+
'function': {
|
|
235
|
+
'name': 'get_indexes',
|
|
236
|
+
'description': 'Look up market index symbols and descriptors (e.g. S&P 500, Nasdaq, Dow).',
|
|
237
|
+
'parameters': {'type': 'object', 'properties': {}}
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
'type': 'function',
|
|
242
|
+
'function': {
|
|
243
|
+
'name': 'get_indexes_quotes',
|
|
244
|
+
'description': 'Get real-time values of major indexes (via ETFs SPY, QQQ, DIA).',
|
|
245
|
+
'parameters': {'type': 'object', 'properties': {}}
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
# Options
|
|
249
|
+
{
|
|
250
|
+
'type': 'function',
|
|
251
|
+
'function': {
|
|
252
|
+
'name': 'get_option_chains',
|
|
253
|
+
'description': 'Load option chain info including available expiration dates.',
|
|
254
|
+
'parameters': {
|
|
255
|
+
'type': 'object',
|
|
256
|
+
'properties': {
|
|
257
|
+
'symbol': {'type': 'string', 'description': 'Underlying stock ticker'}
|
|
258
|
+
},
|
|
259
|
+
'required': ['symbol']
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
'type': 'function',
|
|
265
|
+
'function': {
|
|
266
|
+
'name': 'get_option_instruments',
|
|
267
|
+
'description': 'Load option contracts filtered by expiration and strike.',
|
|
268
|
+
'parameters': {
|
|
269
|
+
'type': 'object',
|
|
270
|
+
'properties': {
|
|
271
|
+
'symbol': {'type': 'string', 'description': 'Underlying stock ticker'},
|
|
272
|
+
'expiration_date': {'type': 'string', 'description': 'Expiration date (YYYY-MM-DD)'},
|
|
273
|
+
'strike_price': {'type': 'number', 'description': 'Strike price'},
|
|
274
|
+
'option_type': {'type': 'string', 'enum': ['call', 'put'], 'description': 'Call or put'}
|
|
275
|
+
},
|
|
276
|
+
'required': ['symbol', 'expiration_date']
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
'type': 'function',
|
|
282
|
+
'function': {
|
|
283
|
+
'name': 'get_option_quotes',
|
|
284
|
+
'description': 'Get real-time quotes for option contracts.',
|
|
285
|
+
'parameters': {
|
|
286
|
+
'type': 'object',
|
|
287
|
+
'properties': {
|
|
288
|
+
'symbol': {'type': 'string', 'description': 'Underlying stock ticker'},
|
|
289
|
+
'expiration_date': {'type': 'string', 'description': 'Expiration date (YYYY-MM-DD)'},
|
|
290
|
+
'strike_price': {'type': 'number', 'description': 'Strike price'},
|
|
291
|
+
'option_type': {'type': 'string', 'enum': ['call', 'put'], 'description': 'Call or put'}
|
|
292
|
+
},
|
|
293
|
+
'required': ['symbol', 'expiration_date', 'strike_price', 'option_type']
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
'type': 'function',
|
|
299
|
+
'function': {
|
|
300
|
+
'name': 'get_option_positions',
|
|
301
|
+
'description': 'View open or closed options positions.',
|
|
302
|
+
'parameters': {'type': 'object', 'properties': {}}
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
'type': 'function',
|
|
307
|
+
'function': {
|
|
308
|
+
'name': 'get_option_orders',
|
|
309
|
+
'description': 'Get options order history.',
|
|
310
|
+
'parameters': {'type': 'object', 'properties': {}}
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
'type': 'function',
|
|
315
|
+
'function': {
|
|
316
|
+
'name': 'review_option_order',
|
|
317
|
+
'description': 'Simulate an options order and get pre-trade warnings, bid/ask spreads, and buying power requirements.',
|
|
318
|
+
'parameters': {
|
|
319
|
+
'type': 'object',
|
|
320
|
+
'properties': {
|
|
321
|
+
'symbol': {'type': 'string', 'description': 'Underlying ticker'},
|
|
322
|
+
'expiration_date': {'type': 'string', 'description': 'Expiration date (YYYY-MM-DD)'},
|
|
323
|
+
'strike_price': {'type': 'number', 'description': 'Strike price'},
|
|
324
|
+
'option_type': {'type': 'string', 'enum': ['call', 'put']},
|
|
325
|
+
'quantity': {'type': 'number', 'description': 'Number of contracts'},
|
|
326
|
+
'price': {'type': 'number', 'description': 'Limit price per contract'},
|
|
327
|
+
'side': {'type': 'string', 'enum': ['buy', 'sell']}
|
|
328
|
+
},
|
|
329
|
+
'required': ['symbol', 'expiration_date', 'strike_price', 'option_type', 'quantity', 'price', 'side']
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
'type': 'function',
|
|
335
|
+
'function': {
|
|
336
|
+
'name': 'place_option_order',
|
|
337
|
+
'description': 'Place a real options limit order on Robinhood.',
|
|
338
|
+
'parameters': {
|
|
339
|
+
'type': 'object',
|
|
340
|
+
'properties': {
|
|
341
|
+
'symbol': {'type': 'string', 'description': 'Underlying ticker'},
|
|
342
|
+
'expiration_date': {'type': 'string', 'description': 'Expiration date (YYYY-MM-DD)'},
|
|
343
|
+
'strike_price': {'type': 'number', 'description': 'Strike price'},
|
|
344
|
+
'option_type': {'type': 'string', 'enum': ['call', 'put']},
|
|
345
|
+
'quantity': {'type': 'number', 'description': 'Number of contracts'},
|
|
346
|
+
'price': {'type': 'number', 'description': 'Limit price per contract'},
|
|
347
|
+
'side': {'type': 'string', 'enum': ['buy', 'sell']}
|
|
348
|
+
},
|
|
349
|
+
'required': ['symbol', 'expiration_date', 'strike_price', 'option_type', 'quantity', 'price', 'side']
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
'type': 'function',
|
|
355
|
+
'function': {
|
|
356
|
+
'name': 'cancel_option_order',
|
|
357
|
+
'description': 'Cancel an open options order by ID.',
|
|
358
|
+
'parameters': {
|
|
359
|
+
'type': 'object',
|
|
360
|
+
'properties': {
|
|
361
|
+
'order_id': {'type': 'string', 'description': 'The option order ID'}
|
|
362
|
+
},
|
|
363
|
+
'required': ['order_id']
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
]
|