moomoo-api-mcp 0.1.6__py3-none-any.whl
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.
- moomoo_api_mcp-0.1.6.dist-info/METADATA +457 -0
- moomoo_api_mcp-0.1.6.dist-info/RECORD +17 -0
- moomoo_api_mcp-0.1.6.dist-info/WHEEL +4 -0
- moomoo_api_mcp-0.1.6.dist-info/entry_points.txt +2 -0
- moomoo_api_mcp-0.1.6.dist-info/licenses/LICENSE +201 -0
- moomoo_mcp/__init__.py +0 -0
- moomoo_mcp/resources/__init__.py +0 -0
- moomoo_mcp/server.py +117 -0
- moomoo_mcp/services/__init__.py +0 -0
- moomoo_mcp/services/base_service.py +39 -0
- moomoo_mcp/services/market_data_service.py +219 -0
- moomoo_mcp/services/trade_service.py +691 -0
- moomoo_mcp/tools/__init__.py +0 -0
- moomoo_mcp/tools/account.py +296 -0
- moomoo_mcp/tools/market_data.py +212 -0
- moomoo_mcp/tools/system.py +20 -0
- moomoo_mcp/tools/trading.py +328 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""Account tools for trading account information retrieval."""
|
|
2
|
+
|
|
3
|
+
from mcp.server.fastmcp import Context
|
|
4
|
+
from mcp.server.session import ServerSession
|
|
5
|
+
|
|
6
|
+
from moomoo_mcp.server import AppContext, mcp
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@mcp.tool()
|
|
10
|
+
async def get_accounts(
|
|
11
|
+
ctx: Context[ServerSession, AppContext]
|
|
12
|
+
) -> list[dict]:
|
|
13
|
+
"""Get list of trading accounts.
|
|
14
|
+
|
|
15
|
+
Returns list of account dictionaries with acc_id, trd_env (REAL/SIMULATE), etc.
|
|
16
|
+
|
|
17
|
+
IMPORTANT: This returns both REAL and SIMULATE accounts. To access REAL account
|
|
18
|
+
data via other tools (get_assets, get_positions, etc.), you must first call
|
|
19
|
+
unlock_trade with the trading password.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
List of account dictionaries containing acc_id, trd_env, and other metadata.
|
|
23
|
+
"""
|
|
24
|
+
trade_service = ctx.request_context.lifespan_context.trade_service
|
|
25
|
+
accounts = trade_service.get_accounts()
|
|
26
|
+
await ctx.info(f"Retrieved {len(accounts)} accounts")
|
|
27
|
+
return accounts
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@mcp.tool()
|
|
31
|
+
async def get_account_summary(
|
|
32
|
+
ctx: Context[ServerSession, AppContext],
|
|
33
|
+
trd_env: str = "REAL",
|
|
34
|
+
acc_id: int = 0,
|
|
35
|
+
) -> dict:
|
|
36
|
+
"""Get complete account summary including assets and positions in one call.
|
|
37
|
+
|
|
38
|
+
This is the recommended tool for getting a full view of an account's status.
|
|
39
|
+
It combines get_assets and get_positions into a single response.
|
|
40
|
+
|
|
41
|
+
IMPORTANT FOR AI AGENTS:
|
|
42
|
+
- Default is REAL account. You MUST notify the user clearly that you are
|
|
43
|
+
accessing their REAL trading account before proceeding.
|
|
44
|
+
- For REAL accounts, you must call unlock_trade first with the trading password.
|
|
45
|
+
- If user wants SIMULATE account, they must explicitly request it.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
trd_env: Trading environment. 'REAL' (default, requires unlock_trade first)
|
|
49
|
+
or 'SIMULATE' (no unlock needed, for testing).
|
|
50
|
+
acc_id: Account ID. Must be obtained from get_accounts().
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Dictionary with 'assets' (cash, market_val, etc.) and 'positions' (list of holdings).
|
|
54
|
+
"""
|
|
55
|
+
trade_service = ctx.request_context.lifespan_context.trade_service
|
|
56
|
+
|
|
57
|
+
assets = trade_service.get_assets(trd_env=trd_env, acc_id=acc_id)
|
|
58
|
+
positions = trade_service.get_positions(trd_env=trd_env, acc_id=acc_id)
|
|
59
|
+
|
|
60
|
+
await ctx.info(f"Retrieved summary for {trd_env} account: {len(positions)} positions")
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
"assets": assets,
|
|
64
|
+
"positions": positions,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@mcp.tool()
|
|
69
|
+
async def get_assets(
|
|
70
|
+
ctx: Context[ServerSession, AppContext],
|
|
71
|
+
trd_env: str = "REAL",
|
|
72
|
+
acc_id: int = 0,
|
|
73
|
+
refresh_cache: bool = False,
|
|
74
|
+
currency: str | None = None,
|
|
75
|
+
) -> dict:
|
|
76
|
+
"""Get account assets including cash, market value, buying power.
|
|
77
|
+
|
|
78
|
+
IMPORTANT FOR AI AGENTS:
|
|
79
|
+
- Default is REAL account. You MUST notify the user clearly that you are
|
|
80
|
+
accessing their REAL trading account before proceeding.
|
|
81
|
+
- For REAL accounts, you must call unlock_trade first with the trading password.
|
|
82
|
+
- If user wants SIMULATE account, they must explicitly request it.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
trd_env: Trading environment. 'REAL' (default, requires unlock_trade first)
|
|
86
|
+
or 'SIMULATE' (no unlock needed, for testing).
|
|
87
|
+
acc_id: Account ID. Must be obtained from get_accounts().
|
|
88
|
+
refresh_cache: Whether to refresh the cache.
|
|
89
|
+
currency: Filter by currency (e.g., 'HKD', 'USD'). Leave None for default.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Dictionary with asset information including cash, market_val, total_assets, etc.
|
|
93
|
+
"""
|
|
94
|
+
trade_service = ctx.request_context.lifespan_context.trade_service
|
|
95
|
+
assets = trade_service.get_assets(
|
|
96
|
+
trd_env=trd_env,
|
|
97
|
+
acc_id=acc_id,
|
|
98
|
+
refresh_cache=refresh_cache,
|
|
99
|
+
currency=currency,
|
|
100
|
+
)
|
|
101
|
+
await ctx.info(f"Retrieved assets for {trd_env} account")
|
|
102
|
+
return assets
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@mcp.tool()
|
|
106
|
+
async def get_positions(
|
|
107
|
+
ctx: Context[ServerSession, AppContext],
|
|
108
|
+
code: str = "",
|
|
109
|
+
market: str = "",
|
|
110
|
+
pl_ratio_min: float | None = None,
|
|
111
|
+
pl_ratio_max: float | None = None,
|
|
112
|
+
trd_env: str = "REAL",
|
|
113
|
+
acc_id: int = 0,
|
|
114
|
+
refresh_cache: bool = False,
|
|
115
|
+
) -> list[dict]:
|
|
116
|
+
"""Get current positions.
|
|
117
|
+
|
|
118
|
+
IMPORTANT FOR AI AGENTS:
|
|
119
|
+
- Default is REAL account. You MUST notify the user clearly that you are
|
|
120
|
+
accessing their REAL trading account before proceeding.
|
|
121
|
+
- For REAL accounts, you must call unlock_trade first with the trading password.
|
|
122
|
+
- If user wants SIMULATE account, they must explicitly request it.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
code: Filter by stock code (e.g., 'US.AAPL').
|
|
126
|
+
market: Filter by market (e.g., 'US', 'HK', 'CN', 'SG', 'JP').
|
|
127
|
+
pl_ratio_min: Minimum profit/loss ratio filter.
|
|
128
|
+
pl_ratio_max: Maximum profit/loss ratio filter.
|
|
129
|
+
trd_env: Trading environment. 'REAL' (default, requires unlock_trade first)
|
|
130
|
+
or 'SIMULATE' (no unlock needed, for testing).
|
|
131
|
+
acc_id: Account ID. Must be obtained from get_accounts().
|
|
132
|
+
refresh_cache: Whether to refresh cache.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
List of position dictionaries with code, qty, cost_price, market_val, pl_ratio, etc.
|
|
136
|
+
"""
|
|
137
|
+
trade_service = ctx.request_context.lifespan_context.trade_service
|
|
138
|
+
positions = trade_service.get_positions(
|
|
139
|
+
code=code,
|
|
140
|
+
market=market,
|
|
141
|
+
pl_ratio_min=pl_ratio_min,
|
|
142
|
+
pl_ratio_max=pl_ratio_max,
|
|
143
|
+
trd_env=trd_env,
|
|
144
|
+
acc_id=acc_id,
|
|
145
|
+
refresh_cache=refresh_cache,
|
|
146
|
+
)
|
|
147
|
+
await ctx.info(f"Retrieved {len(positions)} positions from {trd_env} account")
|
|
148
|
+
return positions
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@mcp.tool()
|
|
152
|
+
async def get_max_tradable(
|
|
153
|
+
ctx: Context[ServerSession, AppContext],
|
|
154
|
+
order_type: str,
|
|
155
|
+
code: str,
|
|
156
|
+
price: float,
|
|
157
|
+
order_id: str = "",
|
|
158
|
+
adjust_limit: float = 0,
|
|
159
|
+
trd_env: str = "REAL",
|
|
160
|
+
acc_id: int = 0,
|
|
161
|
+
) -> dict:
|
|
162
|
+
"""Get maximum tradable quantity for a stock.
|
|
163
|
+
|
|
164
|
+
IMPORTANT FOR AI AGENTS:
|
|
165
|
+
- Default is REAL account. You MUST notify the user clearly that you are
|
|
166
|
+
accessing their REAL trading account before proceeding.
|
|
167
|
+
- For REAL accounts, you must call unlock_trade first with the trading password.
|
|
168
|
+
- If user wants SIMULATE account, they must explicitly request it.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
order_type: Order type (e.g., 'NORMAL', 'LIMIT', 'MARKET').
|
|
172
|
+
code: Stock code (e.g., 'US.AAPL').
|
|
173
|
+
price: Target price for the order.
|
|
174
|
+
order_id: Optional order ID for modification scenarios.
|
|
175
|
+
adjust_limit: Adjust limit percentage.
|
|
176
|
+
trd_env: Trading environment. 'REAL' (default, requires unlock_trade first)
|
|
177
|
+
or 'SIMULATE' (no unlock needed, for testing).
|
|
178
|
+
acc_id: Account ID. Must be obtained from get_accounts().
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Dictionary with max_cash_buy, max_cash_and_margin_buy, max_position_sell, etc.
|
|
182
|
+
"""
|
|
183
|
+
trade_service = ctx.request_context.lifespan_context.trade_service
|
|
184
|
+
max_qty = trade_service.get_max_tradable(
|
|
185
|
+
order_type=order_type,
|
|
186
|
+
code=code,
|
|
187
|
+
price=price,
|
|
188
|
+
order_id=order_id,
|
|
189
|
+
adjust_limit=adjust_limit,
|
|
190
|
+
trd_env=trd_env,
|
|
191
|
+
acc_id=acc_id,
|
|
192
|
+
)
|
|
193
|
+
await ctx.info(f"Retrieved max tradable for {code} in {trd_env} account")
|
|
194
|
+
return max_qty
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@mcp.tool()
|
|
198
|
+
async def get_margin_ratio(
|
|
199
|
+
ctx: Context[ServerSession, AppContext],
|
|
200
|
+
code_list: list[str],
|
|
201
|
+
) -> list[dict]:
|
|
202
|
+
"""Get margin ratio for stocks.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
code_list: List of stock codes (e.g., ['US.AAPL', 'US.TSLA']).
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
List of margin ratio dictionaries.
|
|
209
|
+
"""
|
|
210
|
+
trade_service = ctx.request_context.lifespan_context.trade_service
|
|
211
|
+
ratios = trade_service.get_margin_ratio(code_list=code_list)
|
|
212
|
+
await ctx.info(f"Retrieved margin ratios for {len(code_list)} stocks")
|
|
213
|
+
return ratios
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@mcp.tool()
|
|
217
|
+
async def get_cash_flow(
|
|
218
|
+
ctx: Context[ServerSession, AppContext],
|
|
219
|
+
clearing_date: str = "",
|
|
220
|
+
trd_env: str = "REAL",
|
|
221
|
+
acc_id: int = 0,
|
|
222
|
+
) -> list[dict]:
|
|
223
|
+
"""Get account cash flow history.
|
|
224
|
+
|
|
225
|
+
IMPORTANT FOR AI AGENTS:
|
|
226
|
+
- Default is REAL account. You MUST notify the user clearly that you are
|
|
227
|
+
accessing their REAL trading account before proceeding.
|
|
228
|
+
- For REAL accounts, you must call unlock_trade first with the trading password.
|
|
229
|
+
- If user wants SIMULATE account, they must explicitly request it.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
clearing_date: Filter by clearing date (YYYY-MM-DD format).
|
|
233
|
+
trd_env: Trading environment. 'REAL' (default, requires unlock_trade first)
|
|
234
|
+
or 'SIMULATE' (no unlock needed, for testing).
|
|
235
|
+
acc_id: Account ID. Must be obtained from get_accounts().
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
List of cash flow record dictionaries.
|
|
239
|
+
"""
|
|
240
|
+
trade_service = ctx.request_context.lifespan_context.trade_service
|
|
241
|
+
cash_flows = trade_service.get_cash_flow(
|
|
242
|
+
clearing_date=clearing_date,
|
|
243
|
+
trd_env=trd_env,
|
|
244
|
+
acc_id=acc_id,
|
|
245
|
+
)
|
|
246
|
+
await ctx.info(f"Retrieved {len(cash_flows)} cash flow records from {trd_env} account")
|
|
247
|
+
return cash_flows
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@mcp.tool()
|
|
252
|
+
async def unlock_trade(
|
|
253
|
+
ctx: Context[ServerSession, AppContext],
|
|
254
|
+
password: str | None = None,
|
|
255
|
+
password_md5: str | None = None,
|
|
256
|
+
) -> dict:
|
|
257
|
+
"""Unlock trade to access REAL account data.
|
|
258
|
+
|
|
259
|
+
IMPORTANT: You MUST call this tool before accessing REAL account data via other
|
|
260
|
+
tools (get_assets, get_positions, get_max_tradable, get_cash_flow with trd_env='REAL').
|
|
261
|
+
|
|
262
|
+
This is NOT required for SIMULATE accounts - they work without unlocking.
|
|
263
|
+
|
|
264
|
+
Workflow for accessing REAL account data:
|
|
265
|
+
1. First call unlock_trade(). It will try to use environment variables first.
|
|
266
|
+
- Checks MOOMOO_TRADE_PASSWORD (plain text)
|
|
267
|
+
- Checks MOOMOO_TRADE_PASSWORD_MD5 (md5 hash)
|
|
268
|
+
2. If that fails or if you want to provide credentials explicitly, call
|
|
269
|
+
unlock_trade(password='your_trading_password').
|
|
270
|
+
3. Then call other tools with trd_env='REAL'.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
password: Plain text trade password (the password you set in Moomoo app).
|
|
274
|
+
If not provided, will look for MOOMOO_TRADE_PASSWORD env var.
|
|
275
|
+
password_md5: MD5 hash of trade password (alternative to password).
|
|
276
|
+
If not provided, will look for MOOMOO_TRADE_PASSWORD_MD5 env var.
|
|
277
|
+
Provide either password or password_md5, not both.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
Success status dictionary with {'status': 'unlocked'}.
|
|
281
|
+
|
|
282
|
+
Note:
|
|
283
|
+
The unlock state is maintained for the session. You only need to call this
|
|
284
|
+
once per session to access REAL account data.
|
|
285
|
+
"""
|
|
286
|
+
import os
|
|
287
|
+
|
|
288
|
+
# If no args provided, try env vars
|
|
289
|
+
if not password and not password_md5:
|
|
290
|
+
password = os.environ.get("MOOMOO_TRADE_PASSWORD")
|
|
291
|
+
password_md5 = os.environ.get("MOOMOO_TRADE_PASSWORD_MD5")
|
|
292
|
+
|
|
293
|
+
trade_service = ctx.request_context.lifespan_context.trade_service
|
|
294
|
+
trade_service.unlock_trade(password=password, password_md5=password_md5)
|
|
295
|
+
await ctx.info("Trade unlocked successfully - REAL account data is now accessible")
|
|
296
|
+
return {"status": "unlocked", "message": "You can now access REAL account data by setting trd_env='REAL' in other tools"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
|
|
2
|
+
"""Market data tools for retrieving stock quotes, K-lines, snapshots, and order book."""
|
|
3
|
+
|
|
4
|
+
from mcp.server.fastmcp import Context
|
|
5
|
+
from mcp.server.session import ServerSession
|
|
6
|
+
|
|
7
|
+
from moomoo_mcp.server import AppContext, mcp
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@mcp.tool()
|
|
11
|
+
async def get_stock_quote(
|
|
12
|
+
ctx: Context[ServerSession, AppContext],
|
|
13
|
+
codes: list[str],
|
|
14
|
+
) -> list[dict]:
|
|
15
|
+
"""Get real-time stock quotes for specified codes.
|
|
16
|
+
|
|
17
|
+
Returns current price, open, high, low, volume, and other quote data.
|
|
18
|
+
Automatically subscribes to the stocks before fetching quotes.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
codes: List of stock codes (e.g., ['US.AAPL', 'HK.00700']).
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
List of quote dictionaries containing:
|
|
25
|
+
- code: Stock code
|
|
26
|
+
- last_price: Latest price
|
|
27
|
+
- open_price: Open price
|
|
28
|
+
- high_price: High price
|
|
29
|
+
- low_price: Low price
|
|
30
|
+
- prev_close_price: Previous close
|
|
31
|
+
- volume: Trading volume
|
|
32
|
+
- turnover: Turnover amount
|
|
33
|
+
- And other quote fields
|
|
34
|
+
"""
|
|
35
|
+
market_data_service = ctx.request_context.lifespan_context.market_data_service
|
|
36
|
+
quotes = market_data_service.get_stock_quote(codes)
|
|
37
|
+
await ctx.info(f"Retrieved quotes for {len(codes)} stocks")
|
|
38
|
+
return quotes
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@mcp.tool()
|
|
42
|
+
async def get_historical_klines(
|
|
43
|
+
ctx: Context[ServerSession, AppContext],
|
|
44
|
+
code: str,
|
|
45
|
+
ktype: str = "K_DAY",
|
|
46
|
+
start: str | None = None,
|
|
47
|
+
end: str | None = None,
|
|
48
|
+
max_count: int = 100,
|
|
49
|
+
autype: str = "QFQ",
|
|
50
|
+
) -> list[dict]:
|
|
51
|
+
"""Get historical candlestick (K-line) data for a stock.
|
|
52
|
+
|
|
53
|
+
Returns OHLCV (Open, High, Low, Close, Volume) data for technical analysis.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
code: Stock code (e.g., 'US.AAPL').
|
|
57
|
+
ktype: K-line type. Options:
|
|
58
|
+
- K_1M: 1 minute
|
|
59
|
+
- K_3M: 3 minutes
|
|
60
|
+
- K_5M: 5 minutes
|
|
61
|
+
- K_15M: 15 minutes
|
|
62
|
+
- K_30M: 30 minutes
|
|
63
|
+
- K_60M: 60 minutes
|
|
64
|
+
- K_DAY: Daily (default)
|
|
65
|
+
- K_WEEK: Weekly
|
|
66
|
+
- K_MON: Monthly
|
|
67
|
+
- K_QUARTER: Quarterly
|
|
68
|
+
- K_YEAR: Yearly
|
|
69
|
+
start: Start date in YYYY-MM-DD format. Defaults to 365 days before end.
|
|
70
|
+
end: End date in YYYY-MM-DD format. Defaults to today.
|
|
71
|
+
max_count: Maximum number of candles to return (default 100, max 1000).
|
|
72
|
+
autype: Adjustment type for splits/dividends:
|
|
73
|
+
- QFQ: Forward adjustment (default)
|
|
74
|
+
- HFQ: Backward adjustment
|
|
75
|
+
- NONE: No adjustment
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
List of K-line dictionaries containing:
|
|
79
|
+
- time_key: Candlestick timestamp
|
|
80
|
+
- open: Open price
|
|
81
|
+
- high: High price
|
|
82
|
+
- low: Low price
|
|
83
|
+
- close: Close price
|
|
84
|
+
- volume: Volume
|
|
85
|
+
- turnover: Turnover
|
|
86
|
+
- change_rate: Price change rate
|
|
87
|
+
"""
|
|
88
|
+
market_data_service = ctx.request_context.lifespan_context.market_data_service
|
|
89
|
+
klines = market_data_service.get_historical_klines(
|
|
90
|
+
code=code,
|
|
91
|
+
ktype=ktype,
|
|
92
|
+
start=start,
|
|
93
|
+
end=end,
|
|
94
|
+
max_count=max_count,
|
|
95
|
+
autype=autype,
|
|
96
|
+
)
|
|
97
|
+
await ctx.info(f"Retrieved {len(klines)} K-lines for {code}")
|
|
98
|
+
return klines
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@mcp.tool()
|
|
102
|
+
async def get_market_snapshot(
|
|
103
|
+
ctx: Context[ServerSession, AppContext],
|
|
104
|
+
codes: list[str],
|
|
105
|
+
) -> list[dict]:
|
|
106
|
+
"""Get market snapshot for multiple stocks efficiently.
|
|
107
|
+
|
|
108
|
+
This is ideal for checking current status of a watchlist without subscription.
|
|
109
|
+
Returns comprehensive market data including price, volume, and fundamentals.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
codes: List of stock codes (up to 400). E.g., ['US.AAPL', 'US.TSLA', 'HK.00700'].
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
List of snapshot dictionaries containing:
|
|
116
|
+
- code: Stock code
|
|
117
|
+
- name: Stock name
|
|
118
|
+
- last_price: Latest price
|
|
119
|
+
- open_price: Open price
|
|
120
|
+
- high_price: High price
|
|
121
|
+
- low_price: Low price
|
|
122
|
+
- prev_close_price: Previous close
|
|
123
|
+
- volume: Trading volume
|
|
124
|
+
- turnover: Turnover amount
|
|
125
|
+
- turnover_rate: Turnover rate (%)
|
|
126
|
+
- pe_ratio: P/E ratio
|
|
127
|
+
- pb_ratio: P/B ratio
|
|
128
|
+
- And many more fields
|
|
129
|
+
"""
|
|
130
|
+
market_data_service = ctx.request_context.lifespan_context.market_data_service
|
|
131
|
+
snapshots = market_data_service.get_market_snapshot(codes)
|
|
132
|
+
await ctx.info(f"Retrieved snapshots for {len(codes)} stocks")
|
|
133
|
+
return snapshots
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@mcp.tool()
|
|
137
|
+
async def get_order_book(
|
|
138
|
+
ctx: Context[ServerSession, AppContext],
|
|
139
|
+
code: str,
|
|
140
|
+
num: int = 10,
|
|
141
|
+
) -> dict:
|
|
142
|
+
"""Get order book (market depth) showing bid/ask price levels.
|
|
143
|
+
|
|
144
|
+
Returns the top N bid and ask levels with prices and volumes.
|
|
145
|
+
Useful for analyzing liquidity and market sentiment.
|
|
146
|
+
Automatically subscribes to the stock before fetching order book.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
code: Stock code (e.g., 'HK.00700', 'US.AAPL').
|
|
150
|
+
num: Number of price levels to return (default 10).
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Dictionary containing:
|
|
154
|
+
- code: Stock code
|
|
155
|
+
- Bid: List of bid levels, each as (price, volume, order_count, details)
|
|
156
|
+
- Ask: List of ask levels, each as (price, volume, order_count, details)
|
|
157
|
+
"""
|
|
158
|
+
market_data_service = ctx.request_context.lifespan_context.market_data_service
|
|
159
|
+
order_book = market_data_service.get_order_book(code, num=num)
|
|
160
|
+
await ctx.info(f"Retrieved order book for {code} with {num} levels")
|
|
161
|
+
return order_book
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@mcp.tool()
|
|
165
|
+
async def get_user_security_group(
|
|
166
|
+
ctx: Context[ServerSession, AppContext],
|
|
167
|
+
group_type: int = 0,
|
|
168
|
+
) -> list[dict]:
|
|
169
|
+
"""Get list of user-defined security groups (watchlists).
|
|
170
|
+
|
|
171
|
+
Returns the user's custom watchlist groups from the Moomoo app.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
group_type: Type of groups to return. Options:
|
|
175
|
+
- 0: All groups (default)
|
|
176
|
+
- 1: Custom groups only
|
|
177
|
+
- 2: System groups only
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
List of group dictionaries containing:
|
|
181
|
+
- group_name: Name of the group
|
|
182
|
+
- group_id: Unique identifier for the group
|
|
183
|
+
"""
|
|
184
|
+
market_data_service = ctx.request_context.lifespan_context.market_data_service
|
|
185
|
+
groups = market_data_service.get_user_security_group(group_type=group_type)
|
|
186
|
+
await ctx.info(f"Retrieved {len(groups)} security groups")
|
|
187
|
+
return groups
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@mcp.tool()
|
|
191
|
+
async def get_user_security(
|
|
192
|
+
ctx: Context[ServerSession, AppContext],
|
|
193
|
+
group_name: str,
|
|
194
|
+
) -> list[dict]:
|
|
195
|
+
"""Get list of securities in a specific user-defined group (watchlist).
|
|
196
|
+
|
|
197
|
+
Returns all stocks/securities that the user has added to a specific watchlist.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
group_name: Name of the security group (e.g., 'Favorites', 'Tech').
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
List of security dictionaries containing:
|
|
204
|
+
- code: Stock code (e.g., 'US.AAPL')
|
|
205
|
+
- name: Stock name
|
|
206
|
+
- lot_size: Lot size for trading
|
|
207
|
+
- stock_type: Type of security
|
|
208
|
+
"""
|
|
209
|
+
market_data_service = ctx.request_context.lifespan_context.market_data_service
|
|
210
|
+
securities = market_data_service.get_user_security(group_name)
|
|
211
|
+
await ctx.info(f"Retrieved {len(securities)} securities from group '{group_name}'")
|
|
212
|
+
return securities
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from mcp.server.fastmcp import Context
|
|
2
|
+
from mcp.server.session import ServerSession
|
|
3
|
+
|
|
4
|
+
from moomoo_mcp.server import AppContext, mcp
|
|
5
|
+
|
|
6
|
+
@mcp.tool()
|
|
7
|
+
async def check_health(
|
|
8
|
+
ctx: Context[ServerSession, AppContext]
|
|
9
|
+
) -> dict[str, str]:
|
|
10
|
+
"""Check connectivity to Moomoo and MCP server health.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
Dictionary containing status and connection info.
|
|
14
|
+
"""
|
|
15
|
+
moomoo_service = ctx.request_context.lifespan_context.moomoo_service
|
|
16
|
+
status = moomoo_service.check_health()
|
|
17
|
+
|
|
18
|
+
await ctx.info(f"Health check status: {status.get('status')}")
|
|
19
|
+
|
|
20
|
+
return status
|