Anindhya commited on
Commit
01f0e50
·
verified ·
1 Parent(s): 038795b

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ accounts.db filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,12 +1,6 @@
1
  ---
2
- title: MCP TradingAgents
3
- emoji: 📈
4
- colorFrom: gray
5
- colorTo: blue
6
- sdk: gradio
7
- sdk_version: 5.43.1
8
  app_file: app.py
9
- pinned: false
 
10
  ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: MCP_TradingAgents
 
 
 
 
 
3
  app_file: app.py
4
+ sdk: gradio
5
+ sdk_version: 5.42.0
6
  ---
 
 
__pycache__/accounts_client.cpython-312.pyc ADDED
Binary file (5.29 kB). View file
 
__pycache__/traders.cpython-312.pyc ADDED
Binary file (7.95 kB). View file
 
__pycache__/trading_floor.cpython-312.pyc ADDED
Binary file (2.71 kB). View file
 
__pycache__/util.cpython-312.pyc ADDED
Binary file (1.27 kB). View file
 
accounts.db ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1dd78c0461a4717cb42ffcbe55365a3a585948eda79e17e3a69fc65f663d03dd
3
+ size 241664
accounts.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ import json
3
+ from dotenv import load_dotenv
4
+ from datetime import datetime
5
+ from market import get_share_price
6
+ from database import write_account, read_account, write_log
7
+
8
+ load_dotenv(override=True)
9
+
10
+ INITIAL_BALANCE = 10_000.0
11
+ SPREAD = 0.002
12
+
13
+
14
+ class Transaction(BaseModel):
15
+ symbol: str
16
+ quantity: int
17
+ price: float
18
+ timestamp: str
19
+ rationale: str
20
+
21
+ def total(self) -> float:
22
+ return self.quantity * self.price
23
+
24
+ def __repr__(self):
25
+ return f"{abs(self.quantity)} shares of {self.symbol} at {self.price} each."
26
+
27
+
28
+ class Account(BaseModel):
29
+ name: str
30
+ balance: float
31
+ strategy: str
32
+ holdings: dict[str, int]
33
+ transactions: list[Transaction]
34
+ portfolio_value_time_series: list[tuple[str, float]]
35
+
36
+ @classmethod
37
+ def get(cls, name: str):
38
+ fields = read_account(name.lower())
39
+ if not fields:
40
+ fields = {
41
+ "name": name.lower(),
42
+ "balance": INITIAL_BALANCE,
43
+ "strategy": "",
44
+ "holdings": {},
45
+ "transactions": [],
46
+ "portfolio_value_time_series": []
47
+ }
48
+ write_account(name, fields)
49
+ return cls(**fields)
50
+
51
+
52
+ def save(self):
53
+ write_account(self.name.lower(), self.model_dump())
54
+
55
+ def reset(self, strategy: str):
56
+ self.balance = INITIAL_BALANCE
57
+ self.strategy = strategy
58
+ self.holdings = {}
59
+ self.transactions = []
60
+ self.portfolio_value_time_series = []
61
+ self.save()
62
+
63
+ def deposit(self, amount: float):
64
+ """ Deposit funds into the account. """
65
+ if amount <= 0:
66
+ raise ValueError("Deposit amount must be positive.")
67
+ self.balance += amount
68
+ print(f"Deposited ${amount}. New balance: ${self.balance}")
69
+ self.save()
70
+
71
+ def withdraw(self, amount: float):
72
+ """ Withdraw funds from the account, ensuring it doesn't go negative. """
73
+ if amount > self.balance:
74
+ raise ValueError("Insufficient funds for withdrawal.")
75
+ self.balance -= amount
76
+ print(f"Withdrew ${amount}. New balance: ${self.balance}")
77
+ self.save()
78
+
79
+ def buy_shares(self, symbol: str, quantity: int, rationale: str) -> str:
80
+ """ Buy shares of a stock if sufficient funds are available. """
81
+ price = get_share_price(symbol)
82
+ buy_price = price * (1 + SPREAD)
83
+ total_cost = buy_price * quantity
84
+
85
+ if total_cost > self.balance:
86
+ raise ValueError("Insufficient funds to buy shares.")
87
+ elif price==0:
88
+ raise ValueError(f"Unrecognized symbol {symbol}")
89
+
90
+ # Update holdings
91
+ self.holdings[symbol] = self.holdings.get(symbol, 0) + quantity
92
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
93
+ # Record transaction
94
+ transaction = Transaction(symbol=symbol, quantity=quantity, price=buy_price, timestamp=timestamp, rationale=rationale)
95
+ self.transactions.append(transaction)
96
+
97
+ # Update balance
98
+ self.balance -= total_cost
99
+ self.save()
100
+ write_log(self.name, "account", f"Bought {quantity} of {symbol}")
101
+ return "Completed. Latest details:\n" + self.report()
102
+
103
+ def sell_shares(self, symbol: str, quantity: int, rationale: str) -> str:
104
+ """ Sell shares of a stock if the user has enough shares. """
105
+ if self.holdings.get(symbol, 0) < quantity:
106
+ raise ValueError(f"Cannot sell {quantity} shares of {symbol}. Not enough shares held.")
107
+
108
+ price = get_share_price(symbol)
109
+ sell_price = price * (1 - SPREAD)
110
+ total_proceeds = sell_price * quantity
111
+
112
+ # Update holdings
113
+ self.holdings[symbol] -= quantity
114
+
115
+ # If shares are completely sold, remove from holdings
116
+ if self.holdings[symbol] == 0:
117
+ del self.holdings[symbol]
118
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
119
+ # Record transaction
120
+ transaction = Transaction(symbol=symbol, quantity=-quantity, price=sell_price, timestamp=timestamp, rationale=rationale) # negative quantity for sell
121
+ self.transactions.append(transaction)
122
+
123
+ # Update balance
124
+ self.balance += total_proceeds
125
+ self.save()
126
+ write_log(self.name, "account", f"Sold {quantity} of {symbol}")
127
+ return "Completed. Latest details:\n" + self.report()
128
+
129
+ def calculate_portfolio_value(self):
130
+ """ Calculate the total value of the user's portfolio. """
131
+ total_value = self.balance
132
+ for symbol, quantity in self.holdings.items():
133
+ total_value += get_share_price(symbol) * quantity
134
+ return total_value
135
+
136
+ def calculate_profit_loss(self, portfolio_value: float):
137
+ """ Calculate profit or loss from the initial spend. """
138
+ initial_spend = sum(transaction.total() for transaction in self.transactions)
139
+ return portfolio_value - initial_spend - self.balance
140
+
141
+ def get_holdings(self):
142
+ """ Report the current holdings of the user. """
143
+ return self.holdings
144
+
145
+ def get_profit_loss(self):
146
+ """ Report the user's profit or loss at any point in time. """
147
+ return self.calculate_profit_loss()
148
+
149
+ def list_transactions(self):
150
+ """ List all transactions made by the user. """
151
+ return [transaction.model_dump() for transaction in self.transactions]
152
+
153
+ def report(self) -> str:
154
+ """ Return a json string representing the account. """
155
+ portfolio_value = self.calculate_portfolio_value()
156
+ self.portfolio_value_time_series.append((datetime.now().strftime("%Y-%m-%d %H:%M:%S"), portfolio_value))
157
+ self.save()
158
+ pnl = self.calculate_profit_loss(portfolio_value)
159
+ data = self.model_dump()
160
+ data["total_portfolio_value"] = portfolio_value
161
+ data["total_profit_loss"] = pnl
162
+ write_log(self.name, "account", f"Retrieved account details")
163
+ return json.dumps(data)
164
+
165
+ def get_strategy(self) -> str:
166
+ """ Return the strategy of the account """
167
+ write_log(self.name, "account", f"Retrieved strategy")
168
+ return self.strategy
169
+
170
+ def change_strategy(self, strategy: str) -> str:
171
+ """ At your discretion, if you choose to, call this to change your investment strategy for the future """
172
+ self.strategy = strategy
173
+ self.save()
174
+ write_log(self.name, "account", f"Changed strategy")
175
+ return "Changed strategy"
176
+
177
+ # Example of usage:
178
+ if __name__ == "__main__":
179
+ account = Account("John Doe")
180
+ account.deposit(1000)
181
+ account.buy_shares("AAPL", 5)
182
+ account.sell_shares("AAPL", 2)
183
+ print(f"Current Holdings: {account.get_holdings()}")
184
+ print(f"Total Portfolio Value: {account.calculate_portfolio_value()}")
185
+ print(f"Profit/Loss: {account.get_profit_loss()}")
186
+ print(f"Transactions: {account.list_transactions()}")
accounts_client.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import mcp
2
+ from mcp.client.stdio import stdio_client
3
+ from mcp import StdioServerParameters
4
+ from agents import FunctionTool
5
+ import json
6
+
7
+ params = StdioServerParameters(command="uv", args=["run", "accounts_server.py"], env=None)
8
+
9
+
10
+ async def list_accounts_tools():
11
+ async with stdio_client(params) as streams:
12
+ async with mcp.ClientSession(*streams) as session:
13
+ await session.initialize()
14
+ tools_result = await session.list_tools()
15
+ return tools_result.tools
16
+
17
+ async def call_accounts_tool(tool_name, tool_args):
18
+ async with stdio_client(params) as streams:
19
+ async with mcp.ClientSession(*streams) as session:
20
+ await session.initialize()
21
+ result = await session.call_tool(tool_name, tool_args)
22
+ return result
23
+
24
+ async def read_accounts_resource(name):
25
+ async with stdio_client(params) as streams:
26
+ async with mcp.ClientSession(*streams) as session:
27
+ await session.initialize()
28
+ result = await session.read_resource(f"accounts://accounts_server/{name}")
29
+ return result.contents[0].text
30
+
31
+ async def read_strategy_resource(name):
32
+ async with stdio_client(params) as streams:
33
+ async with mcp.ClientSession(*streams) as session:
34
+ await session.initialize()
35
+ result = await session.read_resource(f"accounts://strategy/{name}")
36
+ return result.contents[0].text
37
+
38
+ async def get_accounts_tools_openai():
39
+ openai_tools = []
40
+ for tool in await list_accounts_tools():
41
+ schema = {**tool.inputSchema, "additionalProperties": False}
42
+ openai_tool = FunctionTool(
43
+ name=tool.name,
44
+ description=tool.description,
45
+ params_json_schema=schema,
46
+ on_invoke_tool=lambda ctx, args, toolname=tool.name: call_accounts_tool(toolname, json.loads(args))
47
+
48
+ )
49
+ openai_tools.append(openai_tool)
50
+ return openai_tools
accounts_server.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from mcp.server.fastmcp import FastMCP
2
+ from accounts import Account
3
+
4
+ mcp = FastMCP("accounts_server")
5
+
6
+ @mcp.tool()
7
+ async def get_balance(name: str) -> float:
8
+ """Get the cash balance of the given account name.
9
+
10
+ Args:
11
+ name: The name of the account holder
12
+ """
13
+ return Account.get(name).balance
14
+
15
+ @mcp.tool()
16
+ async def get_holdings(name: str) -> dict[str, int]:
17
+ """Get the holdings of the given account name.
18
+
19
+ Args:
20
+ name: The name of the account holder
21
+ """
22
+ return Account.get(name).holdings
23
+
24
+ @mcp.tool()
25
+ async def buy_shares(name: str, symbol: str, quantity: int, rationale: str) -> float:
26
+ """Buy shares of a stock.
27
+
28
+ Args:
29
+ name: The name of the account holder
30
+ symbol: The symbol of the stock
31
+ quantity: The quantity of shares to buy
32
+ rationale: The rationale for the purchase and fit with the account's strategy
33
+ """
34
+ return Account.get(name).buy_shares(symbol, quantity, rationale)
35
+
36
+
37
+ @mcp.tool()
38
+ async def sell_shares(name: str, symbol: str, quantity: int, rationale: str) -> float:
39
+ """Sell shares of a stock.
40
+
41
+ Args:
42
+ name: The name of the account holder
43
+ symbol: The symbol of the stock
44
+ quantity: The quantity of shares to sell
45
+ rationale: The rationale for the sale and fit with the account's strategy
46
+ """
47
+ return Account.get(name).sell_shares(symbol, quantity, rationale)
48
+
49
+ @mcp.tool()
50
+ async def change_strategy(name: str, strategy: str) -> str:
51
+ """At your discretion, if you choose to, call this to change your investment strategy for the future.
52
+
53
+ Args:
54
+ name: The name of the account holder
55
+ strategy: The new strategy for the account
56
+ """
57
+ return Account.get(name).change_strategy(strategy)
58
+
59
+ @mcp.resource("accounts://accounts_server/{name}")
60
+ async def read_account_resource(name: str) -> str:
61
+ account = Account.get(name.lower())
62
+ return account.report()
63
+
64
+ @mcp.resource("accounts://strategy/{name}")
65
+ async def read_strategy_resource(name: str) -> str:
66
+ account = Account.get(name.lower())
67
+ return account.get_strategy()
68
+
69
+ if __name__ == "__main__":
70
+ mcp.run(transport='stdio')
app.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from util import css, js, Color
3
+ import pandas as pd
4
+ from trading_floor import names, lastnames, short_model_names
5
+ import plotly.express as px
6
+ from accounts import Account
7
+ from database import read_log
8
+
9
+ mapper = {
10
+ "trace": Color.WHITE,
11
+ "agent": Color.CYAN,
12
+ "function": Color.GREEN,
13
+ "generation": Color.YELLOW,
14
+ "response": Color.MAGENTA,
15
+ "account": Color.RED,
16
+ }
17
+
18
+
19
+ class Trader:
20
+ def __init__(self, name: str, lastname: str, model_name: str):
21
+ self.name = name
22
+ self.lastname = lastname
23
+ self.model_name = model_name
24
+ self.account = Account.get(name)
25
+
26
+ def reload(self):
27
+ self.account = Account.get(self.name)
28
+
29
+ def get_title(self) -> str:
30
+ return f"<div style='text-align: center;font-size:34px;'>{self.name}<span style='color:#ccc;font-size:24px;'> ({self.model_name}) - {self.lastname}</span></div>"
31
+
32
+ def get_strategy(self) -> str:
33
+ return self.account.get_strategy()
34
+
35
+ def get_portfolio_value_df(self) -> pd.DataFrame:
36
+ df = pd.DataFrame(self.account.portfolio_value_time_series, columns=["datetime", "value"])
37
+ df["datetime"] = pd.to_datetime(df["datetime"])
38
+ return df
39
+
40
+ def get_portfolio_value_chart(self):
41
+ df = self.get_portfolio_value_df()
42
+ fig = px.line(df, x="datetime", y="value")
43
+ margin = dict(l=40, r=20, t=20, b=40)
44
+ fig.update_layout(
45
+ height=300,
46
+ margin=margin,
47
+ xaxis_title=None,
48
+ yaxis_title=None,
49
+ paper_bgcolor="#bbb",
50
+ plot_bgcolor="#dde",
51
+ )
52
+ fig.update_xaxes(tickformat="%m/%d", tickangle=45, tickfont=dict(size=8))
53
+ fig.update_yaxes(tickfont=dict(size=8), tickformat=",.0f")
54
+ return fig
55
+
56
+ def get_holdings_df(self) -> pd.DataFrame:
57
+ """Convert holdings to DataFrame for display"""
58
+ holdings = self.account.get_holdings()
59
+ if not holdings:
60
+ return pd.DataFrame(columns=["Symbol", "Quantity"])
61
+
62
+ df = pd.DataFrame(
63
+ [{"Symbol": symbol, "Quantity": quantity} for symbol, quantity in holdings.items()]
64
+ )
65
+ return df
66
+
67
+ def get_transactions_df(self) -> pd.DataFrame:
68
+ """Convert transactions to DataFrame for display"""
69
+ transactions = self.account.list_transactions()
70
+ if not transactions:
71
+ return pd.DataFrame(columns=["Timestamp", "Symbol", "Quantity", "Price", "Rationale"])
72
+
73
+ return pd.DataFrame(transactions)
74
+
75
+ def get_portfolio_value(self) -> str:
76
+ """Calculate total portfolio value based on current prices"""
77
+ portfolio_value = self.account.calculate_portfolio_value() or 0.0
78
+ pnl = self.account.calculate_profit_loss(portfolio_value) or 0.0
79
+ color = "green" if pnl >= 0 else "red"
80
+ emoji = "⬆" if pnl >= 0 else "⬇"
81
+ return f"<div style='text-align: center;background-color:{color};'><span style='font-size:32px'>${portfolio_value:,.0f}</span><span style='font-size:24px'>&nbsp;&nbsp;&nbsp;{emoji}&nbsp;${pnl:,.0f}</span></div>"
82
+
83
+ def get_logs(self, previous=None) -> str:
84
+ logs = read_log(self.name, last_n=13)
85
+ response = ""
86
+ for log in logs:
87
+ timestamp, type, message = log
88
+ color = mapper.get(type, Color.WHITE).value
89
+ response += f"<span style='color:{color}'>{timestamp} : [{type}] {message}</span><br/>"
90
+ response = f"<div style='height:250px; overflow-y:auto;'>{response}</div>"
91
+ if response != previous:
92
+ return response
93
+ return gr.update()
94
+
95
+
96
+ class TraderView:
97
+ def __init__(self, trader: Trader):
98
+ self.trader = trader
99
+ self.portfolio_value = None
100
+ self.chart = None
101
+ self.holdings_table = None
102
+ self.transactions_table = None
103
+
104
+ def make_ui(self):
105
+ with gr.Column():
106
+ gr.HTML(self.trader.get_title())
107
+ with gr.Row():
108
+ self.portfolio_value = gr.HTML(self.trader.get_portfolio_value)
109
+ with gr.Row():
110
+ self.chart = gr.Plot(
111
+ self.trader.get_portfolio_value_chart, container=True, show_label=False
112
+ )
113
+ with gr.Row(variant="panel"):
114
+ self.log = gr.HTML(self.trader.get_logs)
115
+ with gr.Row():
116
+ self.holdings_table = gr.Dataframe(
117
+ value=self.trader.get_holdings_df,
118
+ label="Holdings",
119
+ headers=["Symbol", "Quantity"],
120
+ row_count=(5, "dynamic"),
121
+ col_count=2,
122
+ max_height=300,
123
+ elem_classes=["dataframe-fix-small"],
124
+ )
125
+ with gr.Row():
126
+ self.transactions_table = gr.Dataframe(
127
+ value=self.trader.get_transactions_df,
128
+ label="Recent Transactions",
129
+ headers=["Timestamp", "Symbol", "Quantity", "Price", "Rationale"],
130
+ row_count=(5, "dynamic"),
131
+ col_count=5,
132
+ max_height=300,
133
+ elem_classes=["dataframe-fix"],
134
+ )
135
+
136
+ timer = gr.Timer(value=120)
137
+ timer.tick(
138
+ fn=self.refresh,
139
+ inputs=[],
140
+ outputs=[
141
+ self.portfolio_value,
142
+ self.chart,
143
+ self.holdings_table,
144
+ self.transactions_table,
145
+ ],
146
+ show_progress="hidden",
147
+ queue=False,
148
+ )
149
+ log_timer = gr.Timer(value=0.5)
150
+ log_timer.tick(
151
+ fn=self.trader.get_logs,
152
+ inputs=[self.log],
153
+ outputs=[self.log],
154
+ show_progress="hidden",
155
+ queue=False,
156
+ )
157
+
158
+ def refresh(self):
159
+ self.trader.reload()
160
+ return (
161
+ self.trader.get_portfolio_value(),
162
+ self.trader.get_portfolio_value_chart(),
163
+ self.trader.get_holdings_df(),
164
+ self.trader.get_transactions_df(),
165
+ )
166
+
167
+
168
+ # Main UI construction
169
+ def create_ui():
170
+ """Create the main Gradio UI for the trading simulation"""
171
+
172
+ traders = [
173
+ Trader(trader_name, lastname, model_name)
174
+ for trader_name, lastname, model_name in zip(names, lastnames, short_model_names)
175
+ ]
176
+ trader_views = [TraderView(trader) for trader in traders]
177
+
178
+ with gr.Blocks(
179
+ title="Traders", css=css, js=js, theme=gr.themes.Default(primary_hue="sky"), fill_width=True
180
+ ) as ui:
181
+ with gr.Row():
182
+ for trader_view in trader_views:
183
+ trader_view.make_ui()
184
+
185
+ return ui
186
+
187
+
188
+ if __name__ == "__main__":
189
+ ui = create_ui()
190
+ ui.launch(inbrowser=True)
database.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+ import json
3
+ from datetime import datetime
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv(override=True)
7
+
8
+ DB = "accounts.db"
9
+
10
+
11
+ with sqlite3.connect(DB) as conn:
12
+ cursor = conn.cursor()
13
+ cursor.execute('CREATE TABLE IF NOT EXISTS accounts (name TEXT PRIMARY KEY, account TEXT)')
14
+ cursor.execute('''
15
+ CREATE TABLE IF NOT EXISTS logs (
16
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
17
+ name TEXT,
18
+ datetime DATETIME,
19
+ type TEXT,
20
+ message TEXT
21
+ )
22
+ ''')
23
+ cursor.execute('CREATE TABLE IF NOT EXISTS market (date TEXT PRIMARY KEY, data TEXT)')
24
+ conn.commit()
25
+
26
+ def write_account(name, account_dict):
27
+ json_data = json.dumps(account_dict)
28
+ with sqlite3.connect(DB) as conn:
29
+ cursor = conn.cursor()
30
+ cursor.execute('''
31
+ INSERT INTO accounts (name, account)
32
+ VALUES (?, ?)
33
+ ON CONFLICT(name) DO UPDATE SET account=excluded.account
34
+ ''', (name.lower(), json_data))
35
+ conn.commit()
36
+
37
+ def read_account(name):
38
+ with sqlite3.connect(DB) as conn:
39
+ cursor = conn.cursor()
40
+ cursor.execute('SELECT account FROM accounts WHERE name = ?', (name.lower(),))
41
+ row = cursor.fetchone()
42
+ return json.loads(row[0]) if row else None
43
+
44
+ def write_log(name: str, type: str, message: str):
45
+ """
46
+ Write a log entry to the logs table.
47
+
48
+ Args:
49
+ name (str): The name associated with the log
50
+ type (str): The type of log entry
51
+ message (str): The log message
52
+ """
53
+ now = datetime.now().isoformat()
54
+
55
+ with sqlite3.connect(DB) as conn:
56
+ cursor = conn.cursor()
57
+ cursor.execute('''
58
+ INSERT INTO logs (name, datetime, type, message)
59
+ VALUES (?, datetime('now'), ?, ?)
60
+ ''', (name.lower(), type, message))
61
+ conn.commit()
62
+
63
+ def read_log(name: str, last_n=10):
64
+ """
65
+ Read the most recent log entries for a given name.
66
+
67
+ Args:
68
+ name (str): The name to retrieve logs for
69
+ last_n (int): Number of most recent entries to retrieve
70
+
71
+ Returns:
72
+ list: A list of tuples containing (datetime, type, message)
73
+ """
74
+ with sqlite3.connect(DB) as conn:
75
+ cursor = conn.cursor()
76
+ cursor.execute('''
77
+ SELECT datetime, type, message FROM logs
78
+ WHERE name = ?
79
+ ORDER BY datetime DESC
80
+ LIMIT ?
81
+ ''', (name.lower(), last_n))
82
+
83
+ return reversed(cursor.fetchall())
84
+
85
+ def write_market(date: str, data: dict) -> None:
86
+ data_json = json.dumps(data)
87
+ with sqlite3.connect(DB) as conn:
88
+ cursor = conn.cursor()
89
+ cursor.execute('''
90
+ INSERT INTO market (date, data)
91
+ VALUES (?, ?)
92
+ ON CONFLICT(date) DO UPDATE SET data=excluded.data
93
+ ''', (date, data_json))
94
+ conn.commit()
95
+
96
+ def read_market(date: str) -> dict | None:
97
+ with sqlite3.connect(DB) as conn:
98
+ cursor = conn.cursor()
99
+ cursor.execute('SELECT data FROM market WHERE date = ?', (date,))
100
+ row = cursor.fetchone()
101
+ return json.loads(row[0]) if row else None
market.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from polygon import RESTClient
2
+ from dotenv import load_dotenv
3
+ import os
4
+ from datetime import datetime
5
+ import random
6
+ from database import write_market, read_market
7
+ from functools import lru_cache
8
+ from datetime import timezone
9
+
10
+ load_dotenv(override=True)
11
+
12
+ polygon_api_key = os.getenv("POLYGON_API_KEY")
13
+ polygon_plan = os.getenv("POLYGON_PLAN")
14
+
15
+ is_paid_polygon = polygon_plan == "paid"
16
+ is_realtime_polygon = polygon_plan == "realtime"
17
+
18
+
19
+ def is_market_open() -> bool:
20
+ client = RESTClient(polygon_api_key)
21
+ market_status = client.get_market_status()
22
+ return market_status.market == "open"
23
+
24
+
25
+ def get_all_share_prices_polygon_eod() -> dict[str, float]:
26
+ """With much thanks to student Reema R. for fixing the timezone issue with this!"""
27
+ client = RESTClient(polygon_api_key)
28
+
29
+ probe = client.get_previous_close_agg("SPY")[0]
30
+ last_close = datetime.fromtimestamp(probe.timestamp / 1000, tz=timezone.utc).date()
31
+
32
+ results = client.get_grouped_daily_aggs(last_close, adjusted=True, include_otc=False)
33
+ return {result.ticker: result.close for result in results}
34
+
35
+
36
+ @lru_cache(maxsize=2)
37
+ def get_market_for_prior_date(today):
38
+ market_data = read_market(today)
39
+ if not market_data:
40
+ market_data = get_all_share_prices_polygon_eod()
41
+ write_market(today, market_data)
42
+ return market_data
43
+
44
+
45
+ def get_share_price_polygon_eod(symbol) -> float:
46
+ today = datetime.now().date().strftime("%Y-%m-%d")
47
+ market_data = get_market_for_prior_date(today)
48
+ return market_data.get(symbol, 0.0)
49
+
50
+
51
+ def get_share_price_polygon_min(symbol) -> float:
52
+ client = RESTClient(polygon_api_key)
53
+ result = client.get_snapshot_ticker("stocks", symbol)
54
+ return result.min.close or result.prev_day.close
55
+
56
+
57
+ def get_share_price_polygon(symbol) -> float:
58
+ if is_paid_polygon:
59
+ return get_share_price_polygon_min(symbol)
60
+ else:
61
+ return get_share_price_polygon_eod(symbol)
62
+
63
+
64
+ def get_share_price(symbol) -> float:
65
+ if polygon_api_key:
66
+ try:
67
+ return get_share_price_polygon(symbol)
68
+ except Exception as e:
69
+ print(f"Was not able to use the polygon API due to {e}; using a random number")
70
+ return float(random.randint(1, 100))
market_server.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from mcp.server.fastmcp import FastMCP
2
+ from market import get_share_price
3
+
4
+ mcp = FastMCP("market_server")
5
+
6
+ @mcp.tool()
7
+ async def lookup_share_price(symbol: str) -> float:
8
+ """This tool provides the current price of the given stock symbol.
9
+
10
+ Args:
11
+ symbol: the symbol of the stock
12
+ """
13
+ return get_share_price(symbol)
14
+
15
+ if __name__ == "__main__":
16
+ mcp.run(transport='stdio')
mcp_params.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from market import is_paid_polygon, is_realtime_polygon
4
+
5
+ load_dotenv(override=True)
6
+
7
+ brave_env = {"BRAVE_API_KEY": os.getenv("BRAVE_API_KEY")}
8
+ polygon_api_key = os.getenv("POLYGON_API_KEY")
9
+
10
+ # The MCP server for the Trader to read Market Data
11
+
12
+ if is_paid_polygon or is_realtime_polygon:
13
+ market_mcp = {
14
+ "command": "uvx",
15
+ "args": ["--from", "git+https://github.com/polygon-io/mcp_polygon@v0.1.0", "mcp_polygon"],
16
+ "env": {"POLYGON_API_KEY": polygon_api_key},
17
+ }
18
+ else:
19
+ market_mcp = {"command": "uv", "args": ["run", "market_server.py"]}
20
+
21
+
22
+ # The full set of MCP servers for the trader: Accounts, Push Notification and the Market
23
+
24
+ trader_mcp_server_params = [
25
+ {"command": "uv", "args": ["run", "accounts_server.py"]},
26
+ {"command": "uv", "args": ["run", "push_server.py"]},
27
+ market_mcp,
28
+ ]
29
+
30
+ # The full set of MCP servers for the researcher: Fetch, Brave Search and Memory
31
+
32
+
33
+ def researcher_mcp_server_params(name: str):
34
+ return [
35
+ {"command": "uvx", "args": ["mcp-server-fetch"]},
36
+ {
37
+ "command": "npx",
38
+ "args": ["-y", "@modelcontextprotocol/server-brave-search"],
39
+ "env": brave_env,
40
+ },
41
+ {
42
+ "command": "npx",
43
+ "args": ["-y", "mcp-memory-libsql"],
44
+ "env": {"LIBSQL_URL": f"file:./memory/{name}.db"},
45
+ },
46
+ ]
memory/Cathie.db ADDED
Binary file (57.3 kB). View file
 
memory/George.db ADDED
Binary file (57.3 kB). View file
 
memory/Ray.db ADDED
Binary file (57.3 kB). View file
 
memory/Warren.db ADDED
Binary file (57.3 kB). View file
 
memory/ed.db ADDED
Binary file (57.3 kB). View file
 
memory/memory.txt ADDED
File without changes
push_server.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ import requests
4
+ from pydantic import BaseModel, Field
5
+ from mcp.server.fastmcp import FastMCP
6
+
7
+ load_dotenv(override=True)
8
+
9
+ pushover_user = os.getenv("PUSHOVER_USER")
10
+ pushover_token = os.getenv("PUSHOVER_TOKEN")
11
+ pushover_url = "https://api.pushover.net/1/messages.json"
12
+
13
+
14
+ mcp = FastMCP("push_server")
15
+
16
+
17
+ class PushModelArgs(BaseModel):
18
+ message: str = Field(description="A brief message to push")
19
+
20
+
21
+ @mcp.tool()
22
+ def push(args: PushModelArgs):
23
+ """Send a push notification with this brief message"""
24
+ print(f"Push: {args.message}")
25
+ payload = {"user": pushover_user, "token": pushover_token, "message": args.message}
26
+ requests.post(pushover_url, data=payload)
27
+ return "Push notification sent"
28
+
29
+
30
+ if __name__ == "__main__":
31
+ mcp.run(transport="stdio")
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ openai
2
+ openai-agents
3
+ sendgrid
4
+ pydantic
5
+ requests
6
+ playwright
7
+ dotenv
8
+ python-dotenv
9
+ gradio
10
+ polygon
reset.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from accounts import Account
2
+
3
+ waren_strategy = """
4
+ You are Warren, and you are named in homage to your role model, Warren Buffett.
5
+ You are a value-oriented investor who prioritizes long-term wealth creation.
6
+ You identify high-quality companies trading below their intrinsic value.
7
+ You invest patiently and hold positions through market fluctuations,
8
+ relying on meticulous fundamental analysis, steady cash flows, strong management teams,
9
+ and competitive advantages. You rarely react to short-term market movements,
10
+ trusting your deep research and value-driven strategy.
11
+ """
12
+
13
+ george_strategy = """
14
+ You are George, and you are named in homage to your role model, George Soros.
15
+ You are an aggressive macro trader who actively seeks significant market
16
+ mispricings. You look for large-scale economic and
17
+ geopolitical events that create investment opportunities. Your approach is contrarian,
18
+ willing to bet boldly against prevailing market sentiment when your macroeconomic analysis
19
+ suggests a significant imbalance. You leverage careful timing and decisive action to
20
+ capitalize on rapid market shifts.
21
+ """
22
+
23
+ ray_strategy = """
24
+ You are Ray, and you are named in homage to your role model, Ray Dalio.
25
+ You apply a systematic, principles-based approach rooted in macroeconomic insights and diversification.
26
+ You invest broadly across asset classes, utilizing risk parity strategies to achieve balanced returns
27
+ in varying market environments. You pay close attention to macroeconomic indicators, central bank policies,
28
+ and economic cycles, adjusting your portfolio strategically to manage risk and preserve capital across diverse market conditions.
29
+ """
30
+
31
+ cathie_strategy = """
32
+ You are Cathie, and you are named in homage to your role model, Cathie Wood.
33
+ You aggressively pursue opportunities in disruptive innovation, particularly focusing on Crypto ETFs.
34
+ Your strategy is to identify and invest boldly in sectors poised to revolutionize the economy,
35
+ accepting higher volatility for potentially exceptional returns. You closely monitor technological breakthroughs,
36
+ regulatory changes, and market sentiment in crypto ETFs, ready to take bold positions
37
+ and actively manage your portfolio to capitalize on rapid growth trends.
38
+ You focus your trading on crypto ETFs.
39
+ """
40
+
41
+
42
+ def reset_traders():
43
+ Account.get("Warren").reset(waren_strategy)
44
+ Account.get("George").reset(george_strategy)
45
+ Account.get("Ray").reset(ray_strategy)
46
+ Account.get("Cathie").reset(cathie_strategy)
47
+
48
+
49
+ if __name__ == "__main__":
50
+ reset_traders()
templates.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ from market import is_paid_polygon, is_realtime_polygon
3
+
4
+ if is_realtime_polygon:
5
+ note = "You have access to realtime market data tools; use your get_last_trade tool for the latest trade price. You can also use tools for share information, trends and technical indicators and fundamentals."
6
+ elif is_paid_polygon:
7
+ note = "You have access to market data tools but without access to the trade or quote tools; use your get_snapshot_ticker tool to get the latest share price on a 15 min delay. You can also use tools for share information, trends and technical indicators and fundamentals."
8
+ else:
9
+ note = "You have access to end of day market data; use you get_share_price tool to get the share price as of the prior close."
10
+
11
+
12
+ def researcher_instructions():
13
+ return f"""You are a financial researcher. You are able to search the web for interesting financial news,
14
+ look for possible trading opportunities, and help with research.
15
+ Based on the request, you carry out necessary research and respond with your findings.
16
+ Take time to make multiple searches to get a comprehensive overview, and then summarize your findings.
17
+ If the web search tool raises an error due to rate limits, then use your other tool that fetches web pages instead.
18
+
19
+ Important: making use of your knowledge graph to retrieve and store information on companies, websites and market conditions:
20
+
21
+ Make use of your knowledge graph tools to store and recall entity information; use it to retrieve information that
22
+ you have worked on previously, and store new information about companies, stocks and market conditions.
23
+ Also use it to store web addresses that you find interesting so you can check them later.
24
+ Draw on your knowledge graph to build your expertise over time.
25
+
26
+ If there isn't a specific request, then just respond with investment opportunities based on searching latest news.
27
+ The current datetime is {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
28
+ """
29
+
30
+ def research_tool():
31
+ return "This tool researches online for news and opportunities, \
32
+ either based on your specific request to look into a certain stock, \
33
+ or generally for notable financial news and opportunities. \
34
+ Describe what kind of research you're looking for."
35
+
36
+ def trader_instructions(name: str):
37
+ return f"""
38
+ You are {name}, a trader on the stock market. Your account is under your name, {name}.
39
+ You actively manage your portfolio according to your strategy.
40
+ You have access to tools including a researcher to research online for news and opportunities, based on your request.
41
+ You also have tools to access to financial data for stocks. {note}
42
+ And you have tools to buy and sell stocks using your account name {name}.
43
+ You can use your entity tools as a persistent memory to store and recall information; you share
44
+ this memory with other traders and can benefit from the group's knowledge.
45
+ Use these tools to carry out research, make decisions, and execute trades.
46
+ After you've completed trading, send a push notification with a brief summary of activity, then reply with a 2-3 sentence appraisal.
47
+ Your goal is to maximize your profits according to your strategy.
48
+ """
49
+
50
+ def trade_message(name, strategy, account):
51
+ return f"""Based on your investment strategy, you should now look for new opportunities.
52
+ Use the research tool to find news and opportunities consistent with your strategy.
53
+ Do not use the 'get company news' tool; use the research tool instead.
54
+ Use the tools to research stock price and other company information. {note}
55
+ Finally, make you decision, then execute trades using the tools.
56
+ Your tools only allow you to trade equities, but you are able to use ETFs to take positions in other markets.
57
+ You do not need to rebalance your portfolio; you will be asked to do so later.
58
+ Just make trades based on your strategy as needed.
59
+ Your investment strategy:
60
+ {strategy}
61
+ Here is your current account:
62
+ {account}
63
+ Here is the current datetime:
64
+ {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
65
+ Now, carry out analysis, make your decision and execute trades. Your account name is {name}.
66
+ After you've executed your trades, send a push notification with a brief sumnmary of trades and the health of the portfolio, then
67
+ respond with a brief 2-3 sentence appraisal of your portfolio and its outlook.
68
+ """
69
+
70
+ def rebalance_message(name, strategy, account):
71
+ return f"""Based on your investment strategy, you should now examine your portfolio and decide if you need to rebalance.
72
+ Use the research tool to find news and opportunities affecting your existing portfolio.
73
+ Use the tools to research stock price and other company information affecting your existing portfolio. {note}
74
+ Finally, make you decision, then execute trades using the tools as needed.
75
+ You do not need to identify new investment opportunities at this time; you will be asked to do so later.
76
+ Just rebalance your portfolio based on your strategy as needed.
77
+ Your investment strategy:
78
+ {strategy}
79
+ You also have a tool to change your strategy if you wish; you can decide at any time that you would like to evolve or even switch your strategy.
80
+ Here is your current account:
81
+ {account}
82
+ Here is the current datetime:
83
+ {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
84
+ Now, carry out analysis, make your decision and execute trades. Your account name is {name}.
85
+ After you've executed your trades, send a push notification with a brief sumnmary of trades and the health of the portfolio, then
86
+ respond with a brief 2-3 sentence appraisal of your portfolio and its outlook."""
tracers.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from agents import TracingProcessor, Trace, Span
2
+ from database import write_log
3
+ import secrets
4
+ import string
5
+
6
+ ALPHANUM = string.ascii_lowercase + string.digits
7
+
8
+ def make_trace_id(tag: str) -> str:
9
+ """
10
+ Return a string of the form 'trace_<tag><random>',
11
+ where the total length after 'trace_' is 32 chars.
12
+ """
13
+ tag += "0"
14
+ pad_len = 32 - len(tag)
15
+ random_suffix = ''.join(secrets.choice(ALPHANUM) for _ in range(pad_len))
16
+ return f"trace_{tag}{random_suffix}"
17
+
18
+ class LogTracer(TracingProcessor):
19
+
20
+ def get_name(self, trace_or_span: Trace | Span) -> str | None:
21
+ trace_id = trace_or_span.trace_id
22
+ name = trace_id.split("_")[1]
23
+ if '0' in name:
24
+ return name.split("0")[0]
25
+ else:
26
+ return None
27
+
28
+ def on_trace_start(self, trace) -> None:
29
+ name = self.get_name(trace)
30
+ if name:
31
+ write_log(name, "trace", f"Started: {trace.name}")
32
+
33
+ def on_trace_end(self, trace) -> None:
34
+ name = self.get_name(trace)
35
+ if name:
36
+ write_log(name, "trace", f"Ended: {trace.name}")
37
+
38
+ def on_span_start(self, span) -> None:
39
+ name = self.get_name(span)
40
+ type = span.span_data.type if span.span_data else "span"
41
+ if name:
42
+ message = "Started"
43
+ if span.span_data:
44
+ if span.span_data.type:
45
+ message += f" {span.span_data.type}"
46
+ if hasattr(span.span_data, "name") and span.span_data.name:
47
+ message += f" {span.span_data.name}"
48
+ if hasattr(span.span_data, "server") and span.span_data.server:
49
+ message += f" {span.span_data.server}"
50
+ if span.error:
51
+ message += f" {span.error}"
52
+ write_log(name, type, message)
53
+
54
+ def on_span_end(self, span) -> None:
55
+ name = self.get_name(span)
56
+ type = span.span_data.type if span.span_data else "span"
57
+ if name:
58
+ message = "Ended"
59
+ if span.span_data:
60
+ if span.span_data.type:
61
+
62
+ message += f" {span.span_data.type}"
63
+ if hasattr(span.span_data, "name") and span.span_data.name:
64
+ message += f" {span.span_data.name}"
65
+ if hasattr(span.span_data, "server") and span.span_data.server:
66
+ message += f" {span.span_data.server}"
67
+ if span.error:
68
+ message += f" {span.error}"
69
+ write_log(name, type, message)
70
+
71
+ def force_flush(self) -> None:
72
+ pass
73
+
74
+ def shutdown(self) -> None:
75
+ pass
traders.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from contextlib import AsyncExitStack
2
+ from accounts_client import read_accounts_resource, read_strategy_resource
3
+ from tracers import make_trace_id
4
+ from agents import Agent, Tool, Runner, OpenAIChatCompletionsModel, trace
5
+ from openai import AsyncOpenAI
6
+ from dotenv import load_dotenv
7
+ import os
8
+ import json
9
+ from agents.mcp import MCPServerStdio
10
+ from templates import (
11
+ researcher_instructions,
12
+ trader_instructions,
13
+ trade_message,
14
+ rebalance_message,
15
+ research_tool,
16
+ )
17
+ from mcp_params import trader_mcp_server_params, researcher_mcp_server_params
18
+
19
+ load_dotenv(override=True)
20
+
21
+ deepseek_api_key = os.getenv("DEEPSEEK_API_KEY")
22
+ google_api_key = os.getenv("GOOGLE_API_KEY")
23
+ grok_api_key = os.getenv("GROK_API_KEY")
24
+ openrouter_api_key = os.getenv("OPENROUTER_API_KEY")
25
+
26
+ DEEPSEEK_BASE_URL = "https://api.deepseek.com/v1"
27
+ GROK_BASE_URL = "https://api.x.ai/v1"
28
+ GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/"
29
+ OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
30
+
31
+ MAX_TURNS = 30
32
+
33
+ openrouter_client = AsyncOpenAI(base_url=OPENROUTER_BASE_URL, api_key=openrouter_api_key)
34
+ deepseek_client = AsyncOpenAI(base_url=DEEPSEEK_BASE_URL, api_key=deepseek_api_key)
35
+ grok_client = AsyncOpenAI(base_url=GROK_BASE_URL, api_key=grok_api_key)
36
+ gemini_client = AsyncOpenAI(base_url=GEMINI_BASE_URL, api_key=google_api_key)
37
+
38
+
39
+ def get_model(model_name: str):
40
+ if "/" in model_name:
41
+ return OpenAIChatCompletionsModel(model=model_name, openai_client=openrouter_client)
42
+ elif "deepseek" in model_name:
43
+ return OpenAIChatCompletionsModel(model=model_name, openai_client=deepseek_client)
44
+ elif "grok" in model_name:
45
+ return OpenAIChatCompletionsModel(model=model_name, openai_client=grok_client)
46
+ elif "gemini" in model_name:
47
+ return OpenAIChatCompletionsModel(model=model_name, openai_client=gemini_client)
48
+ else:
49
+ return model_name
50
+
51
+
52
+ async def get_researcher(mcp_servers, model_name) -> Agent:
53
+ researcher = Agent(
54
+ name="Researcher",
55
+ instructions=researcher_instructions(),
56
+ model=get_model(model_name),
57
+ mcp_servers=mcp_servers,
58
+ )
59
+ return researcher
60
+
61
+
62
+ async def get_researcher_tool(mcp_servers, model_name) -> Tool:
63
+ researcher = await get_researcher(mcp_servers, model_name)
64
+ return researcher.as_tool(tool_name="Researcher", tool_description=research_tool())
65
+
66
+
67
+ class Trader:
68
+ def __init__(self, name: str, lastname="Trader", model_name="gpt-4o-mini"):
69
+ self.name = name
70
+ self.lastname = lastname
71
+ self.agent = None
72
+ self.model_name = model_name
73
+ self.do_trade = True
74
+
75
+ async def create_agent(self, trader_mcp_servers, researcher_mcp_servers) -> Agent:
76
+ tool = await get_researcher_tool(researcher_mcp_servers, self.model_name)
77
+ self.agent = Agent(
78
+ name=self.name,
79
+ instructions=trader_instructions(self.name),
80
+ model=get_model(self.model_name),
81
+ tools=[tool],
82
+ mcp_servers=trader_mcp_servers,
83
+ )
84
+ return self.agent
85
+
86
+ async def get_account_report(self) -> str:
87
+ account = await read_accounts_resource(self.name)
88
+ account_json = json.loads(account)
89
+ account_json.pop("portfolio_value_time_series", None)
90
+ return json.dumps(account_json)
91
+
92
+ async def run_agent(self, trader_mcp_servers, researcher_mcp_servers):
93
+ self.agent = await self.create_agent(trader_mcp_servers, researcher_mcp_servers)
94
+ account = await self.get_account_report()
95
+ strategy = await read_strategy_resource(self.name)
96
+ message = (
97
+ trade_message(self.name, strategy, account)
98
+ if self.do_trade
99
+ else rebalance_message(self.name, strategy, account)
100
+ )
101
+ await Runner.run(self.agent, message, max_turns=MAX_TURNS)
102
+
103
+ async def run_with_mcp_servers(self):
104
+ async with AsyncExitStack() as stack:
105
+ trader_mcp_servers = [
106
+ await stack.enter_async_context(
107
+ MCPServerStdio(params, client_session_timeout_seconds=120)
108
+ )
109
+ for params in trader_mcp_server_params
110
+ ]
111
+ async with AsyncExitStack() as stack:
112
+ researcher_mcp_servers = [
113
+ await stack.enter_async_context(
114
+ MCPServerStdio(params, client_session_timeout_seconds=120)
115
+ )
116
+ for params in researcher_mcp_server_params(self.name)
117
+ ]
118
+ await self.run_agent(trader_mcp_servers, researcher_mcp_servers)
119
+
120
+ async def run_with_trace(self):
121
+ trace_name = f"{self.name}-trading" if self.do_trade else f"{self.name}-rebalancing"
122
+ trace_id = make_trace_id(f"{self.name.lower()}")
123
+ with trace(trace_name, trace_id=trace_id):
124
+ await self.run_with_mcp_servers()
125
+
126
+ async def run(self):
127
+ try:
128
+ await self.run_with_trace()
129
+ except Exception as e:
130
+ print(f"Error running trader {self.name}: {e}")
131
+ self.do_trade = not self.do_trade
trading_floor.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from traders import Trader
2
+ from typing import List
3
+ import asyncio
4
+ from tracers import LogTracer
5
+ from agents import add_trace_processor
6
+ from market import is_market_open
7
+ from dotenv import load_dotenv
8
+ import os
9
+
10
+ load_dotenv(override=True)
11
+
12
+ RUN_EVERY_N_MINUTES = int(os.getenv("RUN_EVERY_N_MINUTES", "60"))
13
+ RUN_EVEN_WHEN_MARKET_IS_CLOSED = (
14
+ os.getenv("RUN_EVEN_WHEN_MARKET_IS_CLOSED", "false").strip().lower() == "true"
15
+ )
16
+ USE_MANY_MODELS = os.getenv("USE_MANY_MODELS", "false").strip().lower() == "true"
17
+
18
+ names = ["Warren", "George", "Ray", "Cathie"]
19
+ lastnames = ["Patience", "Bold", "Systematic", "Crypto"]
20
+
21
+ if USE_MANY_MODELS:
22
+ model_names = [
23
+ "gpt-4.1-mini",
24
+ "deepseek-chat",
25
+ "gemini-2.5-flash-preview-04-17",
26
+ "grok-3-mini-beta",
27
+ ]
28
+ short_model_names = ["GPT 4.1 Mini", "DeepSeek V3", "Gemini 2.5 Flash", "Grok 3 Mini"]
29
+ else:
30
+ model_names = ["gpt-4o-mini"] * 4
31
+ short_model_names = ["GPT 4o mini"] * 4
32
+
33
+
34
+ def create_traders() -> List[Trader]:
35
+ traders = []
36
+ for name, lastname, model_name in zip(names, lastnames, model_names):
37
+ traders.append(Trader(name, lastname, model_name))
38
+ return traders
39
+
40
+
41
+ async def run_every_n_minutes():
42
+ add_trace_processor(LogTracer())
43
+ traders = create_traders()
44
+ while True:
45
+ if RUN_EVEN_WHEN_MARKET_IS_CLOSED or is_market_open():
46
+ await asyncio.gather(*[trader.run() for trader in traders])
47
+ else:
48
+ print("Market is closed, skipping run")
49
+ await asyncio.sleep(RUN_EVERY_N_MINUTES * 60)
50
+
51
+
52
+ if __name__ == "__main__":
53
+ print(f"Starting scheduler to run every {RUN_EVERY_N_MINUTES} minutes")
54
+ asyncio.run(run_every_n_minutes())
util.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from enum import Enum
2
+
3
+ css = """
4
+ .positive-pnl {
5
+ color: green !important;
6
+ font-weight: bold;
7
+ }
8
+ .positive-bg {
9
+ background-color: green !important;
10
+ font-weight: bold;
11
+ }
12
+ .negative-bg {
13
+ background-color: red !important;
14
+ font-weight: bold;
15
+ }
16
+ .negative-pnl {
17
+ color: red !important;
18
+ font-weight: bold;
19
+ }
20
+ .dataframe-fix-small .table-wrap {
21
+ min-height: 150px;
22
+ max-height: 150px;
23
+ }
24
+ .dataframe-fix .table-wrap {
25
+ min-height: 200px;
26
+ max-height: 200px;
27
+ }
28
+ footer{display:none !important}
29
+ """
30
+
31
+
32
+ js = """
33
+ function refresh() {
34
+ const url = new URL(window.location);
35
+
36
+ if (url.searchParams.get('__theme') !== 'dark') {
37
+ url.searchParams.set('__theme', 'dark');
38
+ window.location.href = url.href;
39
+ }
40
+ }
41
+ """
42
+
43
+ class Color(Enum):
44
+ RED = "#dd0000"
45
+ GREEN = "#00dd00"
46
+ YELLOW = "#dddd00"
47
+ BLUE = "#0000ee"
48
+ MAGENTA = "#aa00dd"
49
+ CYAN = "#00dddd"
50
+ WHITE = "#87CEEB"