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: Xtreamly plugin for eliza #3572

Closed
wants to merge 6 commits into from
Closed
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
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ COINGECKO_PRO_API_KEY=
MORALIS_API_KEY=

# EVM
EVM_PRIVATE_KEY= # Add the "0x" prefix infront of your private key string
EVM_PRIVATE_KEY= # Add the "0x" prefix infront of your private key string
EVM_PROVIDER_URL=

# Zilliqa
Expand Down Expand Up @@ -981,3 +981,5 @@ COMPASS_BASE_RPC_URL=
DATA_API_KEY= # Your d.a.t.a API key
DATA_AUTH_TOKEN= # Your d.a.t.a auth token

# Xtreamly
XTREAMLY_API_KEY= # Get yours here: https://xtreamly.io/api
35 changes: 35 additions & 0 deletions packages/plugin-xtreamly/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Xtreamly Plugin for Eliza

Supercharge Your Trading with the Xtreamly Plugin for Eliza – Unlock the full potential of Xtreamly’s cutting-edge predictive analytics to create ultra-intelligent trading agents that anticipate market movements, optimize strategies, and drive superior results.
## Description

This plugin delivers advanced market volatility predictions, equipping trading agents with the intelligence needed to execute highly strategic and informed trades. By leveraging cutting-edge algorithms and real-time data analysis, it empowers traders to anticipate market movements with precision, ultimately enhancing decision-making and maximizing potential returns.

Read more about our volatility prediction model here: [AI Volatility by Xtreamly](https://github.com/Xtreamly-Team/xtreamly-agent/blob/main/documentation%2FAI%20Volatility%20by%20Xtreamly.pdf)

## Key Features

- **Real-time Data**: Access live market volatility predictions every 1 or 60 minutes for timely, data-driven decisions.
- **Historical Data**: Retrieve 1 or 60-minute historical market volatility predictions to analyze trends and refine your strategies.

## Supported Networks

- Ethereum
- Arbitrum

## Usage Examples

### Volatility State prediction

```plaintext
Query: "Retrieve Ethereum market volatility state."
```

This query will return Xtreamly's Volatility Model view on the current state on Ethereum market.

## Getting Started

To use this plugin, you'll need to understand Xtreamly's Volatility Model:

1. Visit [Xtreamly Docs](https://xtreamly.io/docs) to read about our modeling approach.
2. Visit [Xtreamly API](https://xtreamly.io/api) to understand Xtreamly API functionality.
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import { describe, expect, it, vi, beforeEach } from 'vitest';
import { retrieveVolatilityState } from '../../src/actions/retrieveVolatilityState';
import { composeContext, generateObject, generateText } from '@elizaos/core';

// Mock external dependencies
vi.mock('@elizaos/core', () => ({
elizaLogger: {
log: vi.fn(),
error: vi.fn(),
},
composeContext: vi.fn(),
generateObject: vi.fn(),
generateText: vi.fn(),
ModelClass: {
SMALL: 'small',
LARGE: 'large',
},
}));

// Mock fetch
const mockFetch = vi.fn();
global.fetch = mockFetch;

describe('retrieveVolatilityPrediction', () => {
let mockRuntime = null;
let mockMessage = null;
let mockState = null;
let mockCallback: () => void;

beforeEach(() => {
mockRuntime = {
character: {
settings: {
secrets: {
XTREAMLY_API_KEY: 'test-key',
},
},
},
composeState: vi.fn().mockResolvedValue({
agentId: 'test-agent',
roomId: 'test-room',
}),
updateRecentMessageState: vi.fn().mockImplementation((state) => Promise.resolve(state)),
};

mockMessage = {
content: {
text: 'Retrieve Ethereum market volatility state.',
},
};

mockState = {
agentId: 'test-agent',
roomId: 'test-room',
};

mockCallback = vi.fn();

// Reset all mocks
vi.clearAllMocks();
});

describe('validation', () => {
it('should validate successfully when API is up and running', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ status: 'ok' }),
});
const result = await retrieveVolatilityState.validate(mockRuntime, mockMessage);
expect(result).toBe(true);
});

it('should fail validation when API is down', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ status: 'down' }),
});
const result = await retrieveVolatilityState.validate(mockRuntime, mockMessage);
expect(result).toBe(false);
});
});

describe('handler', () => {
it('should handle valid volatility state request', async () => {
const mockQueryParams = {
object: {
symbol: 'ETH',
},
};

const mockVolatilityState = {
timestamp: 1739461544161,
timestamp_str: '2025-02-13T15:45:44.161976Z',
classification: 'highvol',
classification_description:
'ETH price in highly volatile short momentum, requiring protective measures and caution.',
};

const mockFormattedResponse = mockVolatilityState.classification_description;

mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(mockVolatilityState),
});

vi.mocked(composeContext).mockReturnValue('mock-context');
vi.mocked(generateObject).mockResolvedValue(mockQueryParams);
vi.mocked(generateText).mockResolvedValue(mockFormattedResponse);

await retrieveVolatilityState.handler(
mockRuntime,
mockMessage,
mockState,
undefined,
mockCallback
);

expect(composeContext).toHaveBeenCalled();
expect(generateObject).toHaveBeenCalled();
expect(mockCallback).toHaveBeenCalledWith({
text: mockFormattedResponse,
});
});

it('should handle invalid query parameters', async () => {
const mockInvalidQueryParams = {
object: {
// Missing required fields
},
};

vi.mocked(composeContext).mockReturnValue('mock-context');
vi.mocked(generateObject).mockResolvedValue(mockInvalidQueryParams);

await retrieveVolatilityState.handler(
mockRuntime,
mockMessage,
mockState,
undefined,
mockCallback
);

expect(mockCallback).toHaveBeenCalledWith(
{
text: 'Invalid query params. Please check the inputs.',
},
[]
);
expect(mockFetch).not.toHaveBeenCalled();
});

it('should handle API errors gracefully', async () => {
const mockQueryParams = {
object: {
symbol: 'ETH',
},
};

vi.mocked(composeContext).mockReturnValue('mock-context');
vi.mocked(generateObject).mockResolvedValue(mockQueryParams);
vi.mocked(mockFetch).mockRejectedValue('API Error');

await retrieveVolatilityState.handler(
mockRuntime,
mockMessage,
mockState,
undefined,
mockCallback
);

expect(mockCallback).toHaveBeenCalledWith({
text: '❌ An error occurred while retrieving Xtreamly volatility state. Please try again later.',
});
});
});

describe('action properties', () => {
it('should have correct action properties', () => {
expect(retrieveVolatilityState.name).toBe('RETRIEVE_VOLATILITY_STATE');
expect(retrieveVolatilityState.description).toBeDefined();
expect(retrieveVolatilityState.similes).toBeDefined();
expect(Array.isArray(retrieveVolatilityState.similes)).toBe(true);
expect(retrieveVolatilityState.examples).toBeDefined();
expect(Array.isArray(retrieveVolatilityState.examples)).toBe(true);
});
});
});
143 changes: 143 additions & 0 deletions packages/plugin-xtreamly/__tests__/libs/VolatilityAPI.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { describe, expect, it, vi, beforeEach } from 'vitest';
import { VolatilityAPI } from '../../src/libs/VolatilityAPI';
// import { XTREAMLY_API_URL_ENDPOINT } from '../../src/constants';
import { elizaLogger } from '@elizaos/core';
import { Horizons } from '../../src/libs/VolatilityPrediction';
import { XTREAMLY_API_URL, XtreamlyAPIPath } from '../../src/libs/XtreamlyAPI';

// Mock fetch
const mockFetch = vi.fn();
global.fetch = mockFetch;

describe('VolatilityAPI library', () => {
const fetchParams = {
method: 'GET',
headers: new Headers({
'x-api-key': 'test-key',
}),
};

beforeEach(() => {
process.env.XTREAMLY_API_KEY = 'test-key';
mockFetch.mockReset();
});

describe('XTREAMLY_API_KEY is not set', () => {
it('should throw an error', () => {
delete process.env.XTREAMLY_API_KEY;
expect(() => {
new VolatilityAPI();
}).toThrow(`
Missing environment variables: XTREAMLY_API_KEY.
Request your API key here: https://xtreamly.io/api
`);
});
});

describe('prediction', () => {
it('should get live volatility predictions', async () => {
const mockResponse = {
timestamp: 123123,
timestamp_str: '2024-02-02T00:00:00',
volatility: 0.111,
};
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(mockResponse),
});

const result = await new VolatilityAPI().prediction(Horizons.min60, 'ETH');

expect(mockFetch).toHaveBeenCalledWith(
`${XTREAMLY_API_URL}${XtreamlyAPIPath.volatility}?symbol=ETH&horizon=60min`,
fetchParams
);
expect(result).toBe(mockResponse);
});
});

describe('historical prediction', () => {
it('should get historical volatility predictions', async () => {
const mockResponse = [
{
timestamp: 123123,
timestamp_str: '2024-02-02T00:00:00',
volatility: 0.111,
},
];
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(mockResponse),
});

const endDate = Date.now();
const startDate = endDate - 10000;
const result = await new VolatilityAPI().historicalPrediction(
new Date(startDate),
new Date(endDate),
Horizons.min60,
'ETH'
);

expect(mockFetch).toHaveBeenCalledWith(
`${XTREAMLY_API_URL}${XtreamlyAPIPath.volatilityHistorical}?symbol=ETH&horizon=60min&start_date=${startDate}&end_date=${endDate}`,
fetchParams
);
expect(result).toBe(mockResponse);
});
});

describe('state', () => {
it('should get live state predictions', async () => {
const mockResponse = {
timestamp: 123123,
timestamp_str: '2024-02-02T00:00:00',
classification: 'midvol',
classification_description: 'This is midvol',
};
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(mockResponse),
});

const result = await new VolatilityAPI().state('ETH');

expect(mockFetch).toHaveBeenCalledWith(
`${XTREAMLY_API_URL}${XtreamlyAPIPath.state}?symbol=ETH`,
fetchParams
);
expect(result).toBe(mockResponse);
});
});

describe('historical state', () => {
it('should get historical state predictions', async () => {
const mockResponse = [
{
timestamp: 123123,
timestamp_str: '2024-02-02T00:00:00',
classification: 'midvol',
classification_description: 'This is midvol',
},
];
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(mockResponse),
});

const endDate = Date.now();
const startDate = endDate - 10000;
const result = await new VolatilityAPI().historicalState(
new Date(startDate),
new Date(endDate),
'ETH'
);

expect(mockFetch).toHaveBeenCalledWith(
`${XTREAMLY_API_URL}${XtreamlyAPIPath.stateHistorical}?symbol=ETH&start_date=${startDate}&end_date=${endDate}`,
fetchParams
);
expect(result).toBe(mockResponse);
});
});
});
Loading