import { z } from 'zod';
import type { DroydClient } from '../client.js';
// =============================================================================
// Shared Schemas
// =============================================================================
const legTypeEnum = z.enum([
'market_buy',
'limit_order',
'stop_loss',
'take_profit',
'quant_buy',
'quant_sell',
]);
const legSchema = z.object({
type: legTypeEnum.describe('Leg type'),
amountUSD: z.number().min(1).describe('Amount in USD (minimum $1)'),
triggerPercent: z
.number()
.optional()
.describe('Trigger percentage (required for all types except market_buy)'),
positionPercent: z
.number()
.min(0)
.max(1)
.optional()
.default(1)
.describe('Portion of position (0-1, default: 1 = 100%)'),
});
// =============================================================================
// Open Trade
// =============================================================================
export const openTradeSchema = z.object({
project_id: z
.number()
.optional()
.describe('Project ID to trade (use this OR contract_address)'),
contract_address: z
.string()
.optional()
.describe('Contract address to trade (use this OR project_id)'),
chain: z
.string()
.optional()
.default('solana')
.describe('Blockchain (required if using contract_address)'),
legs: z
.array(legSchema)
.min(1)
.max(10)
.describe('Array of trade legs (1-10)'),
rationale: z
.string()
.optional()
.describe('Rationale for the trade'),
});
export const openTradeTool = {
name: 'droyd_open_trade',
description: `Open a new trading position with flexible leg configurations.
**Leg Types & Trigger Interpretation:**
- **market_buy** - Immediate buy at market price (no trigger needed)
- **limit_order** - Buy when price drops by triggerPercent (0.05 = buy at 5% below current)
- **stop_loss** - Sell when price drops by triggerPercent (0.10 = sell at 10% below entry)
- **take_profit** - Sell when price rises by triggerPercent (0.20 = sell at 20% above entry)
- **quant_buy** - Buy when momentum score reaches triggerPercent (e.g., 15)
- **quant_sell** - Sell when momentum score reaches triggerPercent (e.g., -10)
**Examples:**
- Simple buy: { "project_id": 123, "legs": [{ "type": "market_buy", "amountUSD": 100 }] }
- With stop loss: { "project_id": 123, "legs": [
{ "type": "market_buy", "amountUSD": 100 },
{ "type": "stop_loss", "amountUSD": 100, "triggerPercent": 0.15 }
]}
- Scaled take profits: { "project_id": 123, "legs": [
{ "type": "market_buy", "amountUSD": 100 },
{ "type": "stop_loss", "amountUSD": 100, "triggerPercent": 0.10 },
{ "type": "take_profit", "amountUSD": 50, "triggerPercent": 0.25, "positionPercent": 0.5 },
{ "type": "take_profit", "amountUSD": 50, "triggerPercent": 0.50, "positionPercent": 0.5 }
]}`,
inputSchema: {
type: 'object' as const,
properties: {
project_id: {
type: 'number',
description: 'Project ID to trade (use this OR contract_address)',
},
contract_address: {
type: 'string',
description: 'Contract address to trade (use this OR project_id)',
},
chain: {
type: 'string',
description: 'Blockchain (required if using contract_address)',
default: 'solana',
},
legs: {
type: 'array',
items: {
type: 'object',
properties: {
type: {
type: 'string',
enum: ['market_buy', 'limit_order', 'stop_loss', 'take_profit', 'quant_buy', 'quant_sell'],
description: 'Leg type',
},
amountUSD: {
type: 'number',
description: 'Amount in USD (minimum $1)',
minimum: 1,
},
triggerPercent: {
type: 'number',
description: 'Trigger percentage (required except for market_buy)',
},
positionPercent: {
type: 'number',
description: 'Portion of position (0-1)',
minimum: 0,
maximum: 1,
default: 1,
},
},
required: ['type', 'amountUSD'],
},
description: 'Trade legs (1-10)',
minItems: 1,
maxItems: 10,
},
rationale: {
type: 'string',
description: 'Rationale for the trade',
},
},
required: ['legs'],
},
};
export async function handleOpenTrade(
client: DroydClient,
args: Record<string, unknown>
) {
const parsed = openTradeSchema.parse(args);
return client.openTrade(parsed);
}
// =============================================================================
// Manage Trade
// =============================================================================
const legModificationSchema = z.object({
leg_action: z.enum(['add', 'update', 'remove']).describe('Action to take'),
leg_id: z.number().optional().describe('Leg ID (required for update/remove)'),
type: legTypeEnum.optional().describe('Leg type (required for add)'),
amountUSD: z.number().optional().describe('Amount in USD (required for add)'),
triggerPercent: z.number().optional().describe('Trigger percentage'),
positionPercent: z.number().min(0).max(1).optional().describe('Position portion'),
});
export const manageTradeSchema = z.object({
strategy_id: z.number().describe('Strategy ID to manage'),
action: z
.enum(['close', 'buy', 'sell', 'update'])
.describe('Action to perform'),
amountUSD: z
.number()
.optional()
.describe('Amount in USD (required for buy action)'),
portfolio_percent: z
.number()
.min(0)
.max(100)
.optional()
.describe('Portfolio percent for buy (0-100)'),
sellPercent: z
.number()
.min(0)
.max(1)
.optional()
.describe('Portion to sell 0-1 (required for sell action)'),
project_id: z
.number()
.optional()
.describe('Override project for buy/sell'),
legs: z
.array(legModificationSchema)
.optional()
.describe('Leg modifications (required for update action)'),
});
export const manageTradeTool = {
name: 'droyd_manage_trade',
description: `Manage existing trading positions. Close positions, execute additional buys/sells, or modify strategy legs.
**Actions:**
- **close** - Exit entire position at market
- **buy** - Add to existing position (requires amountUSD)
- **sell** - Partial exit (requires sellPercent, e.g., 0.5 = sell 50%)
- **update** - Modify stop losses and take profits
**Examples:**
- Close position: { "strategy_id": 789, "action": "close" }
- Partial sell: { "strategy_id": 789, "action": "sell", "sellPercent": 0.25 }
- Add buy: { "strategy_id": 789, "action": "buy", "amountUSD": 50 }
- Add stop loss: { "strategy_id": 789, "action": "update", "legs": [
{ "leg_action": "add", "type": "stop_loss", "amountUSD": 100, "triggerPercent": 0.10 }
]}
- Update existing leg: { "strategy_id": 789, "action": "update", "legs": [
{ "leg_action": "update", "leg_id": 123, "triggerPercent": 0.15 }
]}
- Remove leg: { "strategy_id": 789, "action": "update", "legs": [
{ "leg_action": "remove", "leg_id": 456 }
]}`,
inputSchema: {
type: 'object' as const,
properties: {
strategy_id: {
type: 'number',
description: 'Strategy ID to manage',
},
action: {
type: 'string',
enum: ['close', 'buy', 'sell', 'update'],
description: 'Action to perform',
},
amountUSD: {
type: 'number',
description: 'Amount in USD (required for buy action)',
},
portfolio_percent: {
type: 'number',
description: 'Portfolio percent for buy (0-100)',
minimum: 0,
maximum: 100,
},
sellPercent: {
type: 'number',
description: 'Portion to sell 0-1 (required for sell action)',
minimum: 0,
maximum: 1,
},
project_id: {
type: 'number',
description: 'Override project for buy/sell',
},
legs: {
type: 'array',
items: {
type: 'object',
properties: {
leg_action: {
type: 'string',
enum: ['add', 'update', 'remove'],
description: 'Action to take',
},
leg_id: {
type: 'number',
description: 'Leg ID (required for update/remove)',
},
type: {
type: 'string',
enum: ['market_buy', 'limit_order', 'stop_loss', 'take_profit', 'quant_buy', 'quant_sell'],
description: 'Leg type (required for add)',
},
amountUSD: {
type: 'number',
description: 'Amount in USD (required for add)',
},
triggerPercent: {
type: 'number',
description: 'Trigger percentage',
},
positionPercent: {
type: 'number',
description: 'Position portion (0-1)',
minimum: 0,
maximum: 1,
},
},
required: ['leg_action'],
},
description: 'Leg modifications (required for update action)',
},
},
required: ['strategy_id', 'action'],
},
};
export async function handleManageTrade(
client: DroydClient,
args: Record<string, unknown>
) {
const parsed = manageTradeSchema.parse(args);
return client.manageTrade(parsed);
}
// =============================================================================
// Get Positions
// =============================================================================
export const getPositionsSchema = z.object({
leg_status: z
.enum(['active', 'all'])
.optional()
.default('active')
.describe('Filter: "active" for pending legs only, "all" to include executed'),
});
export const getPositionsTool = {
name: 'droyd_get_positions',
description: `Retrieve active trading positions and wallet holdings.
**Returns:**
- All active strategies with their legs (stop losses, take profits, etc.)
- Executed swaps history
- P&L summary (realized, unrealized, total)
- Wallet holdings with current values
- Overall portfolio summary
Use leg_status="all" to include executed/completed legs in the response.
**Examples:**
- Active only: { "leg_status": "active" }
- Include history: { "leg_status": "all" }`,
inputSchema: {
type: 'object' as const,
properties: {
leg_status: {
type: 'string',
enum: ['active', 'all'],
description: 'Filter: "active" for pending legs only, "all" to include executed',
default: 'active',
},
},
required: [],
},
};
export async function handleGetPositions(
client: DroydClient,
args: Record<string, unknown>
) {
const parsed = getPositionsSchema.parse(args);
return client.getPositions(parsed.leg_status);
}