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.
@@ -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