Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Hummingbot plugin #1570

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -364,3 +364,8 @@ CRONOSZKEVM_PRIVATE_KEY=

# Fuel Ecosystem (FuelVM)
FUEL_WALLET_PRIVATE_KEY=

# Hummingbot Configuration
HUMMINGBOT_API_URL= # Hummingbot REST API URL (default: http://localhost:15888)
HUMMINGBOT_WS_URL= # Hummingbot WebSocket URL (default: ws://localhost:8060)
HUMMINGBOT_API_KEY= # Hummingbot API Key
53 changes: 53 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: CI

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'

- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8

- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

- name: Setup pnpm cache
uses: actions/cache@v3
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Clean install dependencies
run: |
pnpm install --frozen-lockfile

- name: Clean build artifacts
run: |
find . -type d -name "dist" -exec rm -rf {} +
find . -type f -name "tsconfig.tsbuildinfo" -exec rm -f {} +

- name: Build
run: pnpm run build

- name: Test
run: pnpm run test
4 changes: 4 additions & 0 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { zksyncEraPlugin } from "@elizaos/plugin-zksync-era";
import { cronosZkEVMPlugin } from "@elizaos/plugin-cronoszkevm";
import { abstractPlugin } from "@elizaos/plugin-abstract";
import { avalanchePlugin } from "@elizaos/plugin-avalanche";
import { hummingbotPlugin } from "@elizaos/plugin-hummingbot";
import Database from "better-sqlite3";
import fs from "fs";
import path from "path";
Expand Down Expand Up @@ -600,6 +601,9 @@ export async function createAgent(
getSecret(character, "AVALANCHE_PRIVATE_KEY")
? avalanchePlugin
: null,
getSecret(character, "HUMMINGBOT_API_KEY")
? hummingbotPlugin
: null,
].filter(Boolean),
providers: [],
actions: [],
Expand Down
99 changes: 99 additions & 0 deletions packages/client-slack/src/slackHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { EventHandler } from './events'; // Import the EventHandler class
import { WebClient } from '@slack/web-api';
import { MessageManager } from './messages';
import { IAgentRuntime } from '@ai16z/eliza';
import { GitHubClientInterface } from '@ai16z/client-github';

// Function to handle Slack events
export async function handleSlackEvents(event: any, runtime: IAgentRuntime) {
// Instantiate the EventHandler with the necessary configuration and client
const slackConfig = {
appId: process.env.SLACK_APP_ID,
clientId: process.env.SLACK_CLIENT_ID,
clientSecret: process.env.SLACK_CLIENT_SECRET,
signingSecret: process.env.SLACK_SIGNING_SECRET,
verificationToken: process.env.SLACK_VERIFICATION_TOKEN,
botToken: process.env.SLACK_BOT_TOKEN,
botId: process.env.SLACK_BOT_ID
};
const slackClient = new WebClient(process.env.SLACK_API_TOKEN);
const messageManager = new MessageManager(slackClient, runtime, slackConfig.botId);

// Create event handler - it will automatically set up event listeners
const eventHandler = new EventHandler(slackConfig, slackClient, messageManager);

// The event handler will process events through its internal listeners
// No need to call handleEvent directly
}

// Function to handle Slack commands
async function handleSlackCommand(event: any, runtime: IAgentRuntime) {
const text = event.text.toLowerCase().trim();

// Check if the user is asking about GitHub
if (text.includes("github")) {
const missingConfigs = [];
if (!runtime.getSetting("GITHUB_OWNER")) missingConfigs.push("GITHUB_OWNER");
if (!runtime.getSetting("GITHUB_REPO")) missingConfigs.push("GITHUB_REPO");
if (!runtime.getSetting("GITHUB_API_TOKEN")) missingConfigs.push("GITHUB_API_TOKEN");

if (missingConfigs.length > 0) {
// Ask the user to provide missing configuration
await runtime.clients.slack.sendMessage(event.channel, `I noticed you're interested in GitHub. The following configurations are missing: ${missingConfigs.join(", ")}. These configurations need to be set in your environment variables or configuration file.`);

// Notify user about how to set up configurations
await runtime.clients.slack.sendMessage(event.channel, "Please set these configurations in your environment variables or configuration file before proceeding.");

// Return early since we can't proceed without configurations
return;
} else {
await runtime.clients.slack.sendMessage(event.channel, "GitHub is already configured.");

// Initialize GitHub client
const githubClient = await GitHubClientInterface.start(runtime);
if (githubClient) {
runtime.clients.github = githubClient;
await runtime.clients.slack.sendMessage(event.channel, "GitHub client configured successfully!");
}
}
} else if (text.startsWith("!github clone")) {
const githubClient = await GitHubClientInterface.start(runtime, true);
await githubClient.initialize();
await runtime.clients.slack.sendMessage(event.channel, "Repository cloned successfully!");
} else if (text.startsWith("!github list repos")) {
// Example command to list repositories
const githubClient = runtime.clients.github;
if (githubClient) {
const repos = await githubClient.listRepositories();
await runtime.clients.slack.sendMessage(event.channel, `Repositories: ${repos.join(", ")}`);
} else {
await runtime.clients.slack.sendMessage(event.channel, "GitHub client is not configured.");
}
}
}

// Function to wait for a response from a specific channel
function createResponsePromise(channel: string, runtime: IAgentRuntime): Promise<string> {
return new Promise((resolve) => {
const messageHandler = (event: any) => {
if (event.channel === channel) {
// Remove the event listener once we get a response
runtime.clients.slack.removeMessageListener(messageHandler);
resolve(event.text);
}
};

// Add the message listener
runtime.clients.slack.addMessageListener(messageHandler);
});
}

async function promptSlackUser(channel: string, prompt: string, runtime: IAgentRuntime): Promise<string> {
// Send a message to the Slack channel
await runtime.clients.slack.sendMessage(channel, prompt);

// Wait for and return the user's response
return await createResponsePromise(channel, runtime);
}

// Ensure this function is called when Slack events are received
180 changes: 180 additions & 0 deletions packages/plugin-hummingbot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Hummingbot Plugin for Eliza

A powerful plugin that integrates Hummingbot's market making capabilities with Eliza trading agents. This plugin enables Eliza to perform automated market making and trading operations using Hummingbot's infrastructure.

## Features

- Real-time market data streaming via WebSocket
- Simple market making strategy with configurable parameters
- Inventory skew management
- Order lifecycle management (create, cancel, track)
- Rate limiting and error handling
- Automatic reconnection and recovery
- Multi-level order book support

## Installation

```bash
npm install @eliza/plugin-hummingbot
```

## Environment Variables

The plugin uses the following environment variables:
```
HUMMINGBOT_API_URL=http://localhost:15888 # Hummingbot REST API URL
HUMMINGBOT_WS_URL=ws://localhost:8060 # Hummingbot WebSocket URL
HUMMINGBOT_API_KEY=your-api-key # Hummingbot API Key
```

## Usage in Eliza Character

Add the plugin to your character configuration:

```json
{
"plugins": ["@eliza/plugin-hummingbot"],
"settings": {
"HUMMINGBOT_CONFIG": {
"instance": {
"url": "${HUMMINGBOT_API_URL}",
"wsUrl": "${HUMMINGBOT_WS_URL}",
"apiKey": "${HUMMINGBOT_API_KEY}",
"instanceId": "eli-agent"
},
"defaultStrategy": {
"exchange": "binance",
"tradingPair": "BTC-USDT",
"orderAmount": 0.001,
"orderLevels": 2,
"maxOrderAge": 1800,
"inventorySkewEnabled": true,
"inventoryTargetBase": 50,
"bidSpread": 0.2,
"askSpread": 0.2,
"minSpread": 0.1,
"maxSpread": 0.5,
"priceSource": "current_market",
"orderRefreshTime": 60
}
}
}
}
```

> **Note:** The `url` and `apiKey` fields in the instance configuration are required. The plugin will throw an error if either of these values is missing or undefined.

## Quick Start

```typescript
import { HummingbotPlugin } from '@eliza/plugin-hummingbot';
import { SimpleMarketMaking } from '@eliza/plugin-hummingbot/strategies';

// Initialize the plugin
const plugin = new HummingbotPlugin({
instance: {
url: process.env.HUMMINGBOT_API_URL, // Default Hummingbot REST API port
wsUrl: process.env.HUMMINGBOT_WS_URL, // Default Hummingbot WebSocket port
apiKey: process.env.HUMMINGBOT_API_KEY,
instanceId: 'instance-1'
}
});

// Initialize plugin
await plugin.init();

// Configure market making strategy
const config = {
exchange: "binance",
tradingPair: "BTC-USDT",
orderAmount: 0.001, // Base order size in BTC
orderLevels: 2, // Number of orders on each side
maxOrderAge: 1800, // Maximum order age in seconds
inventorySkewEnabled: true,
inventoryTargetBase: 50, // Target base asset percentage
inventoryRangeMultiplier: 1.5,
bidSpread: 0.2, // 0.2% spread for bids
askSpread: 0.2, // 0.2% spread for asks
minSpread: 0.1, // Minimum allowed spread
maxSpread: 0.5, // Maximum allowed spread
priceSource: 'current_market',
minimumSpreadEnabled: true,
pingPongEnabled: false,
orderRefreshTime: 60 // Refresh orders every 60 seconds
};

// Create and start the strategy
const strategy = new SimpleMarketMaking(plugin, config);
const stopStrategy = await strategy.start();

// Handle shutdown gracefully
process.on('SIGINT', async () => {
console.log('Shutting down strategy...');
await stopStrategy();
process.exit(0);
});
```

## Configuration Guide

### Plugin Configuration

| Parameter | Type | Description | Required |
|-----------|--------|-------------|----------|
| url | string | Hummingbot REST API URL | Yes |
| wsUrl | string | Hummingbot WebSocket URL | Yes |
| apiKey | string | API key for authentication | Yes |
| instanceId| string | Unique identifier for the instance | Yes |

### Market Making Strategy Configuration

| Parameter | Type | Description | Required | Default |
|-----------|------|-------------|----------|---------|
| exchange | string | Exchange to trade on | Yes | - |
| tradingPair | string | Trading pair (e.g., "BTC-USDT") | Yes | - |
| orderAmount | number | Base order size | Yes | - |
| orderLevels | number | Number of orders on each side | No | 1 |
| maxOrderAge | number | Maximum order age in seconds | No | 1800 |
| inventorySkewEnabled | boolean | Enable inventory skew | No | false |
| inventoryTargetBase | number | Target base asset % | No | 50 |
| bidSpread | number | Bid spread percentage | Yes | - |
| askSpread | number | Ask spread percentage | Yes | - |
| minSpread | number | Minimum spread percentage | No | 0.1 |
| maxSpread | number | Maximum spread percentage | No | 0.5 |
| priceSource | string | Price source for orders | No | 'current_market' |
| orderRefreshTime | number | Order refresh interval (seconds) | No | 60 |

## API Reference

### HummingbotPlugin

#### Methods

- `init(): Promise<void>` - Initialize the plugin
- `subscribeToMarketData(exchange: string, symbol: string, callback: (data: MarketData) => void): Promise<() => void>` - Subscribe to market data
- `placeOrder(params: OrderParams): Promise<string>` - Place a new order
- `cancelOrder(exchange: string, orderId: string): Promise<boolean>` - Cancel an order
- `getOrderStatus(exchange: string, orderId: string): Promise<any>` - Get order status

### SimpleMarketMaking Strategy

#### Methods

- `start(): Promise<() => void>` - Start the market making strategy
- `updateBalances(): Promise<void>` - Update portfolio balances
- `cancelAllOrders(): Promise<void>` - Cancel all active orders

## Error Handling

The plugin implements comprehensive error handling:

- Rate limit handling with automatic retries
- WebSocket connection management with automatic reconnection
- Order validation and error reporting
- Balance checking before order placement

## Prerequisites

- Running Hummingbot instance
- Valid API credentials
- Sufficient balance on the target exchange
10 changes: 10 additions & 0 deletions packages/plugin-hummingbot/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.test.ts'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
};
Loading
Loading