post_order
Place market or limit orders for stocks in T-Invest accounts using a two-step confirmation process to verify trades before execution.
Instructions
Выставить биржевую заявку в Т-Инвестициях (требуется подтверждение: сначала вызовите без confirm, затем с confirm: true)
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| accountId | Yes | Идентификатор счёта (можно получить через get_accounts) | |
| ticker | Yes | Тикер инструмента | |
| direction | Yes | Направление: buy — покупка, sell — продажа | |
| quantity | Yes | Количество лотов (не более 10 000) | |
| orderType | Yes | Тип заявки: market — рыночная, limit — лимитная | |
| price | No | Цена для лимитной заявки (обязательна при orderType: limit) | |
| confirm | No | Передайте true для исполнения сделки. Без этого параметра возвращается только превью (если включено подтверждение). |
Implementation Reference
- src/tools/post-order.ts:50-120 (handler)The handler function that executes the order placement logic by calling the TInvestClient.
async ({ accountId, ticker, direction, quantity, orderType, price, confirm }) => { try { if (orderType === 'limit' && price === undefined) { return { content: [{ type: 'text' as const, text: 'Для лимитной заявки необходимо указать price.' }], isError: true, }; } const item = await resolveTickerToInstrument(client, ticker); if (!item) { return { content: [{ type: 'text' as const, text: `Инструмент "${ticker}" не найден.` }], isError: true, }; } const priceStr = orderType === 'limit' && price !== undefined ? `${price}` : 'рыночная'; const preview = [ `Заявка:`, ` Тикер: ${ticker}`, ` Направление: ${direction === 'buy' ? 'Покупка' : 'Продажа'}`, ` Тип: ${orderType === 'limit' ? 'Лимитная' : 'Рыночная'}`, ` Количество: ${quantity} лот(ов)`, ` Цена: ${priceStr}`, ` Счёт: ${accountId}`, ].join('\n'); if (requireConfirmation && confirm !== true) { return { content: [{ type: 'text' as const, text: `${preview}\n\nДля исполнения вызовите инструмент повторно с параметром confirm: true` }], }; } const body: Record<string, unknown> = { accountId, instrumentId: item.uid, quantity, direction: DIRECTION_MAP[direction], orderType: ORDER_TYPE_MAP[orderType], orderId: crypto.randomUUID(), }; if (orderType === 'limit' && price !== undefined) { body.price = toQuotation(price); } const resp = await client.post<PostOrderResponse>( API_PATHS.ORDERS.POST_ORDER, body, ); const status = ORDER_STATUS_LABELS[resp.executionReportStatus] ?? resp.executionReportStatus; const lines = [ `Заявка выставлена`, `ID заявки: ${resp.orderId}`, `Статус: ${status}`, `Лотов запрошено / исполнено: ${resp.lotsRequested} / ${resp.lotsExecuted}`, ]; if (resp.totalOrderAmount) lines.push(`Сумма: ${formatMoney(resp.totalOrderAmount)}`); if (resp.initialCommission) lines.push(`Комиссия: ${formatMoney(resp.initialCommission)}`); if (resp.message) lines.push(`Сообщение: ${resp.message}`); return { content: [{ type: 'text' as const, text: lines.join('\n') }] }; } catch (error) { return { content: [{ type: 'text' as const, text: `Ошибка: ${error instanceof Error ? error.message : String(error)}` }], isError: true, }; } }, - src/tools/post-order.ts:33-48 (schema)Zod schema for validating the post_order tool inputs.
{ accountId: z.string().describe('Идентификатор счёта (можно получить через get_accounts)'), ticker: z.string().describe('Тикер инструмента'), direction: z.enum(['buy', 'sell']).describe('Направление: buy — покупка, sell — продажа'), quantity: z.number().int().min(1).max(10_000).describe('Количество лотов (не более 10 000)'), orderType: z.enum(['market', 'limit']).describe('Тип заявки: market — рыночная, limit — лимитная'), price: z .number() .positive() .optional() .describe('Цена для лимитной заявки (обязательна при orderType: limit)'), confirm: z .boolean() .optional() .describe('Передайте true для исполнения сделки. Без этого параметра возвращается только превью (если включено подтверждение).'), }, - src/tools/post-order.ts:21-122 (registration)Registration function for the post_order tool within the McpServer.
export function registerPostOrder( server: McpServer, client: TInvestClient, requireConfirmation: boolean, ): void { const description = requireConfirmation ? 'Выставить биржевую заявку в Т-Инвестициях (требуется подтверждение: сначала вызовите без confirm, затем с confirm: true)' : 'Выставить биржевую заявку в Т-Инвестициях'; server.tool( 'post_order', description, { accountId: z.string().describe('Идентификатор счёта (можно получить через get_accounts)'), ticker: z.string().describe('Тикер инструмента'), direction: z.enum(['buy', 'sell']).describe('Направление: buy — покупка, sell — продажа'), quantity: z.number().int().min(1).max(10_000).describe('Количество лотов (не более 10 000)'), orderType: z.enum(['market', 'limit']).describe('Тип заявки: market — рыночная, limit — лимитная'), price: z .number() .positive() .optional() .describe('Цена для лимитной заявки (обязательна при orderType: limit)'), confirm: z .boolean() .optional() .describe('Передайте true для исполнения сделки. Без этого параметра возвращается только превью (если включено подтверждение).'), }, DESTRUCTIVE, async ({ accountId, ticker, direction, quantity, orderType, price, confirm }) => { try { if (orderType === 'limit' && price === undefined) { return { content: [{ type: 'text' as const, text: 'Для лимитной заявки необходимо указать price.' }], isError: true, }; } const item = await resolveTickerToInstrument(client, ticker); if (!item) { return { content: [{ type: 'text' as const, text: `Инструмент "${ticker}" не найден.` }], isError: true, }; } const priceStr = orderType === 'limit' && price !== undefined ? `${price}` : 'рыночная'; const preview = [ `Заявка:`, ` Тикер: ${ticker}`, ` Направление: ${direction === 'buy' ? 'Покупка' : 'Продажа'}`, ` Тип: ${orderType === 'limit' ? 'Лимитная' : 'Рыночная'}`, ` Количество: ${quantity} лот(ов)`, ` Цена: ${priceStr}`, ` Счёт: ${accountId}`, ].join('\n'); if (requireConfirmation && confirm !== true) { return { content: [{ type: 'text' as const, text: `${preview}\n\nДля исполнения вызовите инструмент повторно с параметром confirm: true` }], }; } const body: Record<string, unknown> = { accountId, instrumentId: item.uid, quantity, direction: DIRECTION_MAP[direction], orderType: ORDER_TYPE_MAP[orderType], orderId: crypto.randomUUID(), }; if (orderType === 'limit' && price !== undefined) { body.price = toQuotation(price); } const resp = await client.post<PostOrderResponse>( API_PATHS.ORDERS.POST_ORDER, body, ); const status = ORDER_STATUS_LABELS[resp.executionReportStatus] ?? resp.executionReportStatus; const lines = [ `Заявка выставлена`, `ID заявки: ${resp.orderId}`, `Статус: ${status}`, `Лотов запрошено / исполнено: ${resp.lotsRequested} / ${resp.lotsExecuted}`, ]; if (resp.totalOrderAmount) lines.push(`Сумма: ${formatMoney(resp.totalOrderAmount)}`); if (resp.initialCommission) lines.push(`Комиссия: ${formatMoney(resp.initialCommission)}`); if (resp.message) lines.push(`Сообщение: ${resp.message}`); return { content: [{ type: 'text' as const, text: lines.join('\n') }] }; } catch (error) { return { content: [{ type: 'text' as const, text: `Ошибка: ${error instanceof Error ? error.message : String(error)}` }], isError: true, }; } }, ); }