@icecoins/plugin-cryptotrade
Version:
Plugin-cryptotrade plugin for ElizaOS
1,387 lines (1,345 loc) • 61.9 kB
JavaScript
// src/plugin.ts
import {
EventType as EventType9,
asUUID as asUUID9,
composePromptFromState as composePromptFromState5,
createUniqueUuid,
logger as logger11
} from "@elizaos/core";
// src/services/BinanceService.ts
import { Service, logger } from "@elizaos/core";
import { MainClient } from "binance";
var BinanceService = class _BinanceService extends Service {
static serviceType = "binance";
capabilityDescription = "This is a binance service which is attached to the agent through the starter plugin.";
constructor(runtime) {
super(runtime);
}
static async start(runtime) {
logger.info(`*** Starting binance service - MODIFIED: ${(/* @__PURE__ */ new Date()).toISOString()} ***`);
const service = new _BinanceService(runtime);
service.initConfigs();
service.initEnv();
return service;
}
static async stop(runtime) {
logger.info("*** TESTING DEV MODE - STOP MESSAGE CHANGED! ***");
const service = runtime.getService(_BinanceService.serviceType);
if (!service) {
throw new Error("Starter service not found");
}
service.stop();
}
CRYPT_BINANCE_API_KEY;
CRYPT_BINANCE_KEY;
client;
async stop() {
logger.info("*** THIRD CHANGE - TESTING FILE WATCHING! ***");
}
initConfigs() {
this.CRYPT_BINANCE_API_KEY = process.env.CRYPT_BINANCE_API_KEY;
this.CRYPT_BINANCE_KEY = process.env.CRYPT_BINANCE_KEY;
}
initEnv() {
this.client = new MainClient({
api_key: this.CRYPT_BINANCE_API_KEY,
api_secret: this.CRYPT_BINANCE_KEY,
testnet: true
// Connect to testnet environment
});
logger.info("*** Service Binance: Client Init done. ***");
}
async getAvgPrice(coin_symbol = "BTCUSDT") {
return new Promise(async (resolve, reject) => {
try {
const price = await this.client.getAvgPrice({ symbol: coin_symbol });
resolve(price);
} catch (error) {
reject(error);
}
});
}
async getTickerPrice(coin_symbol = "BTCUSDT") {
return new Promise(async (resolve, reject) => {
try {
const price = await this.client.getSymbolPriceTicker({ symbol: coin_symbol });
resolve(price);
} catch (error) {
reject(error);
}
});
}
async getDailyPrice(coin_symbol = "BTCUSDT") {
return new Promise(async (resolve, reject) => {
try {
if (!coin_symbol.includes("USDT")) {
coin_symbol = coin_symbol.toUpperCase().concat("USDT");
}
const price = await this.client.get24hrChangeStatistics({ symbol: coin_symbol, type: "MINI" });
resolve(price);
} catch (error) {
reject(error);
}
});
}
};
// src/actions/Common/ActionGetOffChainNewsData.ts
import {
EventType,
logger as logger3,
asUUID
} from "@elizaos/core";
// node_modules/uuid/dist/esm/stringify.js
var byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 256).toString(16).slice(1));
}
function unsafeStringify(arr, offset = 0) {
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
}
// node_modules/uuid/dist/esm/rng.js
import { randomFillSync } from "crypto";
var rnds8Pool = new Uint8Array(256);
var poolPtr = rnds8Pool.length;
function rng() {
if (poolPtr > rnds8Pool.length - 16) {
randomFillSync(rnds8Pool);
poolPtr = 0;
}
return rnds8Pool.slice(poolPtr, poolPtr += 16);
}
// node_modules/uuid/dist/esm/native.js
import { randomUUID } from "crypto";
var native_default = { randomUUID };
// node_modules/uuid/dist/esm/v4.js
function v4(options, buf, offset) {
if (native_default.randomUUID && !buf && !options) {
return native_default.randomUUID();
}
options = options || {};
const rnds = options.random ?? options.rng?.() ?? rng();
if (rnds.length < 16) {
throw new Error("Random bytes length must be >= 16");
}
rnds[6] = rnds[6] & 15 | 64;
rnds[8] = rnds[8] & 63 | 128;
if (buf) {
offset = offset || 0;
if (offset < 0 || offset + 16 > buf.length) {
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
for (let i = 0; i < 16; ++i) {
buf[offset + i] = rnds[i];
}
return buf;
}
return unsafeStringify(rnds);
}
var v4_default = v4;
// src/services/ApiService.ts
import {
logger as logger2,
ModelType,
Service as Service2
} from "@elizaos/core";
import * as fs from "fs";
import path from "path";
import * as readline from "readline";
// src/const/Const.ts
var LLM_produce_actions = false;
var starting_date = "2023-10-01";
var ending_date = "2023-12-01";
var bear_starting_date = "2023-04-12";
var bear_ending_date = "2023-06-16";
var sideways_starting_date = "2023-06-17";
var sideways_ending_date = "2023-08-28";
var bull_starting_date = "2023-10-01";
var bull_ending_date = "2023-12-01";
var LLM_retry_times = 10;
var manageTemplate_Intro = `
# You are a professional cryptoCurrency trader. If you received message from user, you should start you actions immediately. You are supposed to make a trade by executing actions in the following steps: 1."GET_PRICE" and "GET_NEWS" (these two actions should be take together, not single); 2."PROCESS_PRICE" and "PROCESS_NEW " (these two actions should be take together, not single); 3."MAKE_TRADE"; 4."REPLY".
# Task: Generate dialog with actions.
# Instructions: Write the next message for user.
"thought" should be a short description of what the agent is thinking about and planning.
"message" should be the next message for user which they will send to the conversation.
"actions" should be the next actions that agent will conduct, "actions" should include one or more actions.
# Possible response actions: "GET_PRICE", "GET_NEWS", "PROCESS_PRICE", "PROCESS_NEWS", "MAKE_TRADE", "REPLY", "IGNORE"
`;
var manageTemplate_Example = `
# Action Examples:
user: Please help me to make a decision of BTC trade, am I supposed to buy or sell?
agent: I'll conduct a research of BTC now. (actions: "GET_PRICE", "GET_NEWS")
agent: I've got the price and news of BTC, analysing. (actions: "PROCESS_PRICE", "PROCESS_NEWS")
agent: Analysis done, the price of BTC seems to be going down, we should sell part of them, about 20%. (actions: "MAKE_TRADE")
agent: Finally, reply the decision to user. The decision is: -0.2/1.0 of your BTC. (actions: "REPLY")
`;
var manageTemplate_Rules = `
# RULES:
RULE 1: User is asking the proposal to make a cryptoCurrency trade, you should begin to make a trade by executing actions in the following order above, and reply in the end;
RULE 2: When your are executing ations, they must be executed strictly in the order of steps;
RULE 3: You should decide next actions with the state of the steps'execution provided below, after actions in step before has been "DONE", execute actions in the next step;
RULE 4: User is talking about other things, or your are executing actions (eg: In step 2, "PROCESS_PRICE" done, but still waiting "PROCESS_NEWS"), set "actions" as "IGNORE".
RULE 5: The response must contain "thought", "message", and "actions".
`;
var manageTemplate_state = `
The state of the steps'execution:
`;
var manageTemplate_format = `
# Response format
# Response format should be formatted in a valid JSON block like this:
{
"thought": "<string>",
"message": "<string>",
"actions": ["<string>", "<string>", "<string>"]
}
# Your response should include the valid JSON block and nothing else.
# Response format end
`;
var manageTemplate_take_actions = `
# Choose your next actions within the [Possible response action] and the [RULES] mentioned before.
# Your response should be formatted in a valid JSON block like this:
{
"thought": "<string>",
"message": "<string>",
"actions": ["<string>", "<string>", "<string>"]
}
# Now, choose your next actions:
`;
var delim = '\n"""\n';
var STARTING_NET_WORTH = 1e6;
var STARTING_CASH_RATIO = 0.5;
var GAS_LIMITS = 21e3;
var GAS_PRICE = 70;
var GAS_FEE = GAS_LIMITS * GAS_PRICE * 1e-9;
var EX_RATE = 4e-3;
// src/services/ApiService.ts
async function postData(path2, data) {
var options = {
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
body: data
};
await fetch("http://127.0.0.1:8642/" + path2, options).then((response) => {
return response.json();
}).then((_data) => {
console.log(_data);
return _data;
}).catch((error) => {
console.error("Error:", error);
});
}
function ewma(data, span) {
const alpha = 2 / (span + 1);
const result = [];
let prevEwma = data[0];
result.push(prevEwma);
for (let i = 1; i < data.length; i++) {
const currentEwma = alpha * data[i] + (1 - alpha) * prevEwma;
result.push(currentEwma);
prevEwma = currentEwma;
}
return result;
}
var ApiService = class _ApiService extends Service2 {
static serviceType = "apiservice";
capabilityDescription = "This is a api service which is attached to the agent through the cryptotrade plugin.";
constructor(runtime) {
super(runtime);
}
static async start(runtime) {
logger2.info(`*** Starting api service -- : ${(/* @__PURE__ */ new Date()).toISOString()} ***`);
const service = new _ApiService(runtime);
service.initState();
service.initData();
service.initConfigs();
return service;
}
static async stop(runtime) {
logger2.info("*** TESTING DEV MODE - STOP MESSAGE CHANGED! ***");
const service = runtime.getService(_ApiService.serviceType);
if (!service) {
throw new Error("API service not found");
}
service.stop();
}
async stop() {
logger2.info("*** THIRD CHANGE - TESTING FILE WATCHING! ***");
}
async postOnChianAPI(_chain, _date) {
try {
const response = await postData("getOnChainData", { chain: _chain, date: _date });
return response.data;
} catch (error) {
console.error("CryptoTrade Server Error: ", error.message);
throw error;
}
}
async postNewsAPI(_chain, _date) {
try {
const response = await postData("getNewsData", { chain: _chain, date: _date });
return response.data;
} catch (error) {
console.error("CryptoTrade Server Error: ", error.message);
throw error;
}
}
step_state = { Executing: false, GET_PRICE: "UNDONE" };
step_data = { STEP: 0 };
is_action_executing = {};
price_data = [];
transaction_data = [];
news_data = [];
news_data_simplified = [];
record = [];
onChainDataLoaded = false;
offChainNewsLoaded = false;
abortAllTasks = false;
callbackInActions = true;
enableNewsSimplification = false;
useTransactionData = false;
cash;
coin_held;
starting_net_worth;
net_worth;
total_roi;
last_net_worth;
starting_price;
start_day;
end_day;
today_idx;
end_day_idx;
project_initialized = false;
CRYPT_STARTING_DAY;
CRYPT_ENDING_DAY;
CRYPT_STAGE;
dumpRecordPath;
initConfigs() {
if (process.env.CRYPT_STARTING_DAY) {
this.CRYPT_STARTING_DAY = process.env.CRYPT_STARTING_DAY;
}
if (process.env.CRYPT_ENDING_DAY) {
this.CRYPT_ENDING_DAY = process.env.CRYPT_ENDING_DAY;
}
if (process.env.CRYPT_STAGE) {
this.CRYPT_STAGE = process.env.CRYPT_STAGE;
}
if (process.env.CRYPT_CALLBACK_IN_ACTIONS) {
if (process.env.CRYPT_CALLBACK_IN_ACTIONS === "true") {
this.callbackInActions = true;
} else {
this.callbackInActions = false;
}
}
if (process.env.CRYPT_ENABLE_NEWS_SIMPLIFICATION) {
if (process.env.CRYPT_ENABLE_NEWS_SIMPLIFICATION === "true") {
this.enableNewsSimplification = true;
} else {
this.enableNewsSimplification = false;
}
}
if (process.env.CRYPT_USE_TRANSACTION) {
if (process.env.CRYPT_USE_TRANSACTION === "true") {
this.useTransactionData = true;
} else {
this.useTransactionData = false;
}
}
logger2.error(`Config init done:
this.CRYPT_STARTING_DAY: ${this.CRYPT_STARTING_DAY}
this.CRYPT_STAGE: ${this.CRYPT_STAGE}
this.callbackInActions: ${this.callbackInActions}
this.enableNewsSimplification: ${this.enableNewsSimplification}`);
}
initProject() {
this.starting_price = this.price_data[this.today_idx].value["open"];
this.starting_net_worth = STARTING_NET_WORTH;
this.cash = this.starting_net_worth * STARTING_CASH_RATIO;
this.coin_held = (this.starting_net_worth - this.cash) / this.starting_price;
this.last_net_worth = this.starting_net_worth;
this.project_initialized = true;
this.dumpRecordPath = `./data/local/record/${this.start_day}-${this.end_day}.json`;
}
initState() {
this.step_state["Executing"] = false;
this.step_state["GET_PRICE"] = "UNDONE";
this.step_state["GET_NEWS"] = "UNDONE";
this.step_state["PROCESS_PRICE"] = "UNDONE";
this.step_state["PROCESS_NEWS"] = "UNDONE";
this.step_state["SIMPLIFY_NEWS"] = "UNDONE";
this.step_state["PROCESS_REFLET"] = "UNDONE";
this.step_state["MAKE_TRADE"] = "UNDONE";
this.is_action_executing["GET_PRICE"] = false;
this.is_action_executing["GET_NEWS"] = false;
this.is_action_executing["PROCESS_PRICE"] = false;
this.is_action_executing["PROCESS_NEWS"] = false;
this.is_action_executing["SIMPLIFY_NEWS"] = false;
this.is_action_executing["PROCESS_REFLET"] = false;
this.is_action_executing["MAKE_TRADE"] = false;
}
initData() {
this.step_data["STEP"] = this.step_data["STEP"] + 1;
this.step_data["DATE"] = "";
this.step_data["ANALYSIS_REPORT_ON_CHAIN"] = "";
this.step_data["ANALYSIS_REPORT_NEWS"] = "";
this.step_data["ANALYSIS_REPORT_REFLECT"] = "";
this.step_data["TRADE_REASON"] = "";
this.step_data["TRADE_ACTION"] = "";
this.step_data["TRADE_ACTION_VALUE"] = 0;
this.step_data["TODAY_ROI"] = 0;
this.step_data["SIMPLIFIED_NEWS"] = [];
}
stepEnd() {
this.record.push(structuredClone(this.step_data));
this.initData();
this.initState();
}
appendRecord() {
fs.appendFileSync(this.dumpRecordPath, JSON.stringify(this.record[this.record.length - 1]) + ",\n");
}
updateState(Executing, GET_PRICE, GET_NEWS, PROCESS_PRICE, PROCESS_NEWS, SIMPLIFY_NEWS, PROCESS_REFLET, MAKE_TRADE) {
this.step_state["Executing"] = Executing;
this.step_state["GET_PRICE"] = GET_PRICE;
this.step_state["GET_NEWS"] = GET_NEWS;
this.step_state["PROCESS_PRICE"] = PROCESS_PRICE;
this.step_state["PROCESS_NEWS"] = PROCESS_NEWS;
this.step_state["SIMPLIFY_NEWS"] = SIMPLIFY_NEWS;
this.step_state["PROCESS_REFLET"] = PROCESS_REFLET;
this.step_state["MAKE_TRADE"] = MAKE_TRADE;
}
getState() {
return JSON.stringify({
Executing: this.step_state["Executing"],
GET_PRICE: this.step_state["GET_PRICE"],
GET_NEWS: this.step_state["GET_NEWS"],
PROCESS_PRICE: this.step_state["PROCESS_PRICE"],
SIMPLIFY_NEWS: this.step_state["SIMPLIFY_NEWS"],
PROCESS_NEWS: this.step_state["PROCESS_NEWS"],
PROCESS_REFLET: this.step_state["PROCESS_REFLET"],
MAKE_TRADE: this.step_state["MAKE_TRADE"]
});
}
async readLocalCsvFile(filePath, which_data, reverse = false) {
return new Promise((resolve, reject) => {
const stream = fs.createReadStream(filePath);
const rl = readline.createInterface({
input: stream,
crlfDelay: Infinity
});
let firstLine = true;
let labels = [];
rl.on("line", (line) => {
if (!firstLine) {
let data = line.split(",");
data[0] = data[0].substring(0, 10);
let values = {};
for (let i = 0; i < labels.length; i++) {
if (!("open" === labels[i])) {
values[labels[i]] = data[i];
} else {
values[labels[i]] = Number(data[i]);
}
}
switch (which_data) {
case "price":
this.price_data.push({ key: data[0], value: values });
break;
case "transaction":
this.transaction_data.push({ key: data[0], value: values });
break;
default:
break;
}
} else {
logger2.error("The first line of the csv file: " + line);
labels = line.split(",");
firstLine = false;
}
});
rl.on("close", () => {
if (reverse) {
switch (which_data) {
case "price":
this.price_data = this.price_data.reverse();
break;
case "transaction":
this.transaction_data = this.transaction_data.reverse();
break;
default:
break;
}
}
logger2.error("Process End");
resolve("Read CSV File Process End");
});
rl.on("error", (err) => {
reject(err);
});
});
}
async loadTransactionData(local = true) {
return new Promise(async (resolve, reject) => {
if (this.onChainDataLoaded) {
resolve("TRANSACTION DATA HAS LOADED, SKIP");
} else {
logger2.error("DATA: loadTransactionData start");
}
let res = "error";
try {
let values = {};
if (local) {
res = await this.readLocalCsvFile("./data/local/bitcoin_transaction_statistics.csv", "transaction", false);
logger2.error("loadTransactionData END");
} else {
values = await fetchFileFromWeb();
}
} catch (error) {
logger2.error("loadPriceData error: ", error);
reject(error);
}
this.onChainDataLoaded = true;
resolve(res);
});
}
async readFileByAbsPath(path2, local = true) {
return new Promise(async (resolve, reject) => {
try {
const data = await fs.readFileSync(path2, "utf-8");
resolve(data);
} catch (error) {
reject(error);
}
});
}
async loadNewsData(chain = "btc", force = false, local = true) {
return new Promise(async (resolve, reject) => {
if (!force && this.offChainNewsLoaded) {
resolve("News Data Has Loaded, SKIP");
}
let fileNames = [];
let dir = `./data/local/news/${chain}`;
const list = await fs.readdirSync(dir, "utf-8");
try {
for (const name of list) {
fileNames.push(name);
}
for (const name of fileNames) {
let news_str = await this.readFileByAbsPath(path.join(dir, name));
let raw_news_data = JSON.parse(news_str);
let format_news_data = [];
for (let i = 0; i < raw_news_data.length; i++) {
const article = JSON.parse(JSON.stringify({
title: raw_news_data[i].title,
time: raw_news_data[i].time,
content: raw_news_data[i].content
}));
format_news_data.push(article);
}
this.news_data.push({ date: name.split(".")[0], data: structuredClone(format_news_data) });
}
this.offChainNewsLoaded = true;
resolve("News data has been successfully loaded.");
} catch (error) {
reject(error);
throw new Error(error);
}
});
}
async loadPriceData(chain = "btc", force = false, local = true) {
return new Promise(async (resolve, reject) => {
if (!force && this.onChainDataLoaded) {
resolve("PRICE DATA HAS LOADED, SKIP");
} else {
logger2.error("DATA: loadPriceData start");
}
let res = "error";
try {
let values;
if (local) {
res = await this.readLocalCsvFile("./data/local/bitcoin_daily_price.csv", "price", true);
logger2.error("loadPriceData END");
let result = this.calculateMACD();
for (let i = 0; i < this.price_data.length; i++) {
this.price_data[i].value["ema12"] = result.ema12[i];
this.price_data[i].value["ema26"] = result.ema26[i];
this.price_data[i].value["macd"] = result.macd[i];
this.price_data[i].value["signalLine"] = result.signalLine[i];
}
logger2.error("MACD DATA CALC END");
} else {
values = await fetchFileFromWeb();
}
} catch (error) {
logger2.error("loadPriceData error: ", error);
reject(error);
}
resolve(res);
});
}
calculateMACD() {
const openPrices = this.price_data.map((d) => d.value["open"]);
const ema12 = ewma(openPrices, 12);
const ema26 = ewma(openPrices, 26);
const macd = ema12.map((val, idx) => val - ema26[idx]);
const signalLine = ewma(macd, 9);
return {
ema12,
ema26,
macd,
signalLine
};
}
getNextDay(dateString) {
let idx = this.price_data.findIndex((d) => d.key === dateString);
if (-1 != idx && idx + 1 < this.price_data.length) {
return this.price_data[idx + 1].key;
}
throw new Error("Invalid date");
}
getNextOpenPriceByDate(dateString) {
let idx = this.price_data.findIndex((d) => d.key == dateString);
if (-1 != idx && idx + 1 < this.price_data.length) {
return this.price_data[idx + 1].value["open"];
}
throw new Error("Invalid date");
}
getNextOpenPriceByDateIdx(idx) {
if (-1 != idx && idx + 1 < this.price_data.length) {
return this.price_data[idx + 1].value["open"];
}
throw new Error("Invalid date");
}
calculateROI() {
return new Promise((resolve, rejects) => {
let next_open_price = this.getNextOpenPriceByDateIdx(this.today_idx);
this.net_worth = this.cash + this.coin_held * next_open_price;
this.total_roi = this.net_worth / this.starting_net_worth - 1;
this.step_data["TOTAL_ROI"] = this.total_roi;
this.step_data["TODAY_ROI"] = this.net_worth / this.last_net_worth - 1;
this.last_net_worth = this.net_worth;
console.error(`[CRYPTOTRADE]: ***** calculateROI end *****`);
resolve(null);
});
}
executeTrade() {
return new Promise((resolve, rejects) => {
this.step_data["TRADE_ACTION"] = "hold";
let action_val = this.step_data["TRADE_ACTION_VALUE"];
let open_price = this.price_data[this.today_idx].value["open"];
if (-1 <= action_val && action_val < 0 && this.coin_held > 0) {
this.step_data["TRADE_ACTION"] = "sell";
let eth_diff = Math.abs(action_val) * this.coin_held;
let cash_diff = eth_diff * open_price;
this.coin_held -= eth_diff;
this.cash += cash_diff;
this.cash -= GAS_FEE * open_price + cash_diff * EX_RATE;
} else if (0 < action_val && action_val <= 1 && this.cash > 0) {
this.step_data["TRADE_ACTION"] = "buy";
let cash_diff = Math.abs(action_val) * this.cash;
let eth_diff = cash_diff / open_price;
this.cash -= cash_diff;
this.coin_held += eth_diff;
this.cash -= GAS_FEE * open_price + cash_diff * EX_RATE;
}
console.error(`[CRYPTOTRADE]: ***** executeTrade end *****`);
resolve(null);
});
}
parseAction(response) {
if (typeof response === "string") {
const regex = /-?(?:0(?:\.\d{1})|1\.0)/g;
const actions = response.match(regex);
if (!actions || actions.length === 0) {
console.error(`ERROR: Invalid llm response:
${response}.
Set to no action.`);
response = 0;
} else if (actions.length === 1) {
response = parseFloat(actions[0]);
} else {
response = parseFloat(actions[actions.length - 1]);
}
}
if (typeof response !== "number" || response < -1 || response > 1) {
response = -999;
}
return response;
}
getPromptOfOnChainData(chain = "BTC", date = "2024-09-26", windowSize = 5) {
let idx_price = this.price_data.findIndex((item) => item.key === date);
let price_s = "You are an " + chain + "cryptocurrency trading analyst. The recent price and auxiliary information is given in chronological order below:" + delim;
if (!this.useTransactionData) {
if (-1 != idx_price) {
let idx_price_start = idx_price - windowSize < 0 ? 0 : idx_price - windowSize;
for (; idx_price_start <= idx_price; idx_price_start++) {
let data_str = "Open price: " + this.price_data[idx_price_start].value["open"];
let macd = this.price_data[idx_price_start].value["macd"];
let macd_signal_line = this.price_data[idx_price_start].value["signalLine"];
let macd_signal = "hold";
if (macd < macd_signal_line) {
macd_signal = "buy";
} else if (macd > macd_signal_line) {
macd_signal = "sell";
}
data_str += `, MACD Signal: ${macd_signal}`;
data_str += ";\n";
price_s += data_str;
}
price_s += delim + "Write one concise paragraph to analyze the recent information and estimate the market trend accordingly.";
return price_s;
}
return null;
}
let idx_transaction = this.transaction_data.findIndex((item) => item.key === date);
if (-1 != idx_price && -1 != idx_transaction) {
let idx_price_start = idx_price - windowSize < 0 ? 0 : idx_price - windowSize;
let idx_transaction_start = idx_transaction - windowSize < 0 ? 0 : idx_transaction - windowSize;
for (; idx_price_start <= idx_price && idx_transaction_start <= idx_transaction; idx_price_start++, idx_transaction_start++) {
let data_str = "Open price: " + this.price_data[idx_price_start].value["open"];
let transaction_value_set = this.transaction_data[idx_transaction_start].value;
let transaction_labels = Object.keys(transaction_value_set);
for (const label of transaction_labels) {
data_str += `, ${label}: ${transaction_value_set[label]}`;
}
let macd = this.price_data[idx_price_start].value["macd"];
let macd_signal_line = this.price_data[idx_price_start].value["signalLine"];
let macd_signal = "hold";
if (macd < macd_signal_line) {
macd_signal = "buy";
} else if (macd > macd_signal_line) {
macd_signal = "sell";
}
data_str += `, MACD signal: ${macd_signal}`;
data_str += ";\n";
price_s += data_str;
}
price_s += delim + "Write one concise paragraph to analyze the recent information and estimate the market trend accordingly.";
return price_s;
} else {
return null;
}
}
getPromptOfProcessNewsData(chain = "btc", date = "2024-09-26", maxArticles = 3) {
let idx_news = this.news_data.findIndex((item) => item.date === date);
logger2.error("API SERVICE getPromptOfNewsData: [" + idx_news + "]\n");
if (-1 != idx_news && this.news_data[idx_news].data.length > 0) {
let news_s = "";
if (this.enableNewsSimplification) {
if (!(this.news_data.length > 0)) {
throw new Error(`Error: The SIMPLIFIED_NEWS set on ${this.step_data["DATE"]} is empty.`);
}
news_s = `You are an ${chain.toUpperCase()} cryptocurrency trading analyst. There are some articles about cryptocurrency today, and an analyst has completed the summary. You are required to analyze the following summary of these articles:` + delim;
for (let i = 0; i < this.news_data[idx_news].data.length; i++) {
news_s += `{title:${this.news_data[idx_news].data[i].title}, time:${this.news_data[idx_news].data[i].time}, content:${this.news_data[idx_news].data[i].content_simplified}}
`;
}
news_s += delim + `Write one concise paragraph to analyze the summary and estimate the market trend accordingly.`;
} else {
news_s = `You are an ${chain.toUpperCase()} cryptocurrency trading analyst. You are required to analyze the following news articles:` + delim;
if (this.news_data[idx_news].data.length > maxArticles) {
for (let i = 0; i < maxArticles; i++) {
news_s += JSON.stringify(this.news_data[idx_news].data[i]);
}
} else {
news_s += JSON.stringify(this.news_data[idx_news].data);
}
news_s += delim + `Write one concise paragraph to analyze the news and estimate the market trend accordingly.`;
}
return news_s;
} else {
throw new Error("FAILED TO FETCH NEWS DATA");
}
}
async simplifyNewsData(chain = "btc", date = this.step_data["DATE"]) {
let idx_news_set = this.news_data.findIndex((item) => item.date === date);
logger2.error("API SERVICE getPromptOfSimplifyNewsData: [" + idx_news_set + "]\n");
if (-1 != idx_news_set && this.news_data[idx_news_set].data.length > 0) {
const simp_tmp = `
You are a parsing agent for cryptocurrency news. Your goal is to extract and structure key factual information from each article so that it can be used as input by downstream generation agents.
Input includes:
- Title
- Time
- Full article text
Instructions:
- Do not write in full prose or natural language.
- Extract core information points in a structured, atomic way.
- Avoid summarizing style or commentary; focus only on facts.
- Output should be short, less than 500 words.
- Organize output in a consistent and machine-readable format (e.g., JSON or labeled bullet points).
- Include fields such as: main events, impact, involved entities (optional: sentiment, domain).
Output Format Example:
{
"short_summary": "...",
"impact": "...",
"domain": "...",
"sentiment": "neutral",
"key_points": ["...", "..."],
}
Input article data:
` + delim;
for (let i = 0; i < this.news_data[idx_news_set].data.length; i++) {
let simp_s = simp_tmp + JSON.stringify({
title: this.news_data[idx_news_set].data[i].title,
time: this.news_data[idx_news_set].data[i].time,
content: this.news_data[idx_news_set].data[i].content
});
simp_s += delim;
const resp = await this.tryToCallLLMsWithoutFormat(
simp_s,
false,
false,
/*maxTokens:*/
200
);
this.news_data[idx_news_set].data[i].content_simplified = resp;
}
return "simplifyNewsData done, data record to this.news_data[idx_news_set]";
} else {
return "FAILED TO FETCH NEWS DATA";
}
}
getPromptOfReflectHistory(chain = "btc", windowSize = 5) {
const record_len = this.record.length;
let reflect_s = `You are an ${chain.toUpperCase()} cryptocurrency trading analyst. Your analysis and action history is given in chronological order:` + delim;
if (0 === record_len) {
reflect_s += "There is not any analysis and action history yet.";
} else {
let idx_start = record_len - windowSize < 0 ? 0 : record_len - windowSize;
for (; idx_start < record_len; idx_start++) {
const data_set = this.record[idx_start];
let reflect_data = [];
reflect_data.push({
Step: data_set["STEP"],
Date: data_set["DATE"],
Reasoning: data_set["TRADE_REASON"],
Action: data_set["TRADE_ACTION"],
ActionValue: data_set["TRADE_ACTION_VALUE"],
TotalReturn: data_set["TOTAL_ROI"],
DailyReturn: data_set["TODAY_ROI"]
});
reflect_s += JSON.stringify(reflect_data) + "\n";
}
}
reflect_s += delim + `Reflect on your recent performance and instruct your future trades from a high level, e.g., identify what information is currently more important, and what to be next, like aggresive or conversative. Write one concise paragraph to reflect on your recent trading performance with a focus on the effective strategies and information that led to the most successful outcomes, and the ineffective strategies and information that led to loss of profit. Identify key trends and indicators in the current cryptocurrency market that are likely to influence future trades. Also assess whether a more aggressive or conservative trading approach is warranted.`;
return reflect_s;
}
getPromptOfMakeTrade(chain = "btc") {
let trade_s = `You are an experienced ${chain.toUpperCase()} cryptocurrency trader and you are trying to maximize your overall profit by trading ${chain.toUpperCase()}. In each day, you will make an action to buy or sell ${chain.toUpperCase()}. You are assisted by a few analysts below and need to decide the final action.`;
trade_s += `
ON-CHAIN ANALYST REPORT:${delim}${this.step_data["ANALYSIS_REPORT_ON_CHAIN"]}${delim}
NEWS ANALYST REPORT:${delim}${this.step_data["ANALYSIS_REPORT_NEWS"]}${delim}
REFLECTION ANALYST REPORT:${delim}${this.step_data["ANALYSIS_REPORT_REFLECT"]}${delim}
`;
trade_s += "Now, start your response with your brief reasoning over the given reports. Then, based on the synthesized reports, conclude a clear market trend, emphasizing long-term strategies over short-term gains. Finally, indicate your trading action as a 1-decimal float in the range of [-1,1], reflecting your confidence in the market trend and your strategic decision to manage risk appropriately.";
return trade_s;
}
async tryToCallLLMsWithoutFormat(prompt, parseAction = false, debug = true, maxTokens = 1e3, temperature = 0.7, contextSize = 16384) {
return new Promise(async (resolve, reject) => {
let response = "LLM HAS NOT RESPONSE";
for (var i = 0; i < LLM_retry_times; i++) {
try {
if (debug) {
logger2.warn("[CryptoTrader] tryToCallLLMsWithoutFormat *** prompt content ***\n", prompt);
}
response = await this.runtime.useModel(ModelType.TEXT_LARGE, {
prompt,
temperature,
maxTokens,
contextSize
});
if (parseAction) {
this.step_data["TRADE_ACTION_VALUE"] = this.parseAction(response);
if (-999 === this.step_data["TRADE_ACTION_VALUE"]) {
continue;
}
}
if (response && response.length > 25 && response.length < maxTokens * 4) {
break;
}
} catch (error) {
response = null;
}
}
if (!response) {
this.abortAllTasks = true;
reject("LLM_ERROR");
} else {
if (debug) {
logger2.warn("[CryptoTrader] tryToCallLLMsWithoutFormat *** response ***\n", response);
}
resolve(response);
}
});
}
async tryToCallLLMs4Json(prompt, runtime) {
return new Promise(async (resolve, reject) => {
let parsedJson;
for (var i = 0; i < LLM_retry_times; i++) {
try {
logger2.warn("[CryptoTrader] tryToCallLLM *** prompt ***\n", prompt);
const response = await runtime.useModel(ModelType.TEXT_LARGE, {
prompt
});
logger2.warn("[CryptoTrader] tryToCallLLM *** response ***\n", response);
parsedJson = JSON.parse(response);
if (response && parsedJson) {
break;
}
} catch (error) {
parsedJson = null;
}
}
if (!parsedJson) {
reject("LLM_ERROR");
} else {
resolve(parsedJson);
}
});
}
};
async function fetchFileFromWeb() {
try {
const response = await fetch("https://domain.com/file.csv");
if (!response.ok) {
throw new Error("Network response was not ok");
}
const data = await response.text();
const json = await response.json();
console.log(data);
return json;
} catch (error) {
console.error("There has been a problem with your fetch operation:", error);
}
}
// src/actions/Common/ActionGetOffChainNewsData.ts
var getNewsData = {
name: "GET_NEWS",
similes: [
"CHECK_NEWS",
"FETCH_NEWS",
"GET_CRYPTO_NEWS",
"CRYPTO_NEWS",
"CHECK_CRYPTO_NEWS"
],
description: "Get news for a cryptocurrency",
validate: async (runtime, message, state) => {
return true;
},
handler: async (runtime, message, state, _options, callback, _responses) => {
try {
const service = runtime.getService(ApiService.serviceType);
if (service.is_action_executing["GET_NEWS"]) {
return false;
}
service.is_action_executing["GET_NEWS"] = true;
logger3.warn("***** GET NEWS DATA START ***** \n");
const resp = `service.loadNewsData: ` + await service.loadNewsData();
logger3.warn("***** GET NEWS DATA END ***** \n", resp);
if (callback && service.callbackInActions) {
if (!resp) {
callback({
text: `Error in fetch news DATA. `
});
return false;
}
callback({
thought: resp,
text: `News data loaded. `
});
}
service.step_state["GET_NEWS"] = "DONE";
var message;
message.content.text = "CryptoTrade_Action_GET_NEWS DONE";
message.id = asUUID(v4_default());
await runtime.emitEvent(EventType.MESSAGE_SENT, { runtime, message, source: "CryptoTrade_Action_GET_NEWS" });
logger3.warn("***** ACTION GET_NEWS DONE *****");
service.is_action_executing["GET_NEWS"] = false;
return true;
} catch (error) {
logger3.error("Error in news fetch:", error);
if (callback) {
callback({
text: `
Error in news fetch:
${error.message}
`
});
}
return false;
}
},
examples: []
};
// src/actions/Common/ActionGetOnChainData.ts
import {
EventType as EventType2,
logger as logger4,
asUUID as asUUID2
} from "@elizaos/core";
import { z } from "zod";
var Blockchains = /* @__PURE__ */ ((Blockchains2) => {
Blockchains2["BTC"] = "btc";
Blockchains2["SOL"] = "sol";
Blockchains2["ETH"] = "eth";
return Blockchains2;
})(Blockchains || {});
var getBlockchainPriceRequestSchema = z.object({
blockchain: z.nativeEnum(Blockchains).describe("The blockchain to get statistics for"),
date: z.string().optional().describe("The date to request (optional)")
// toTimestamp: z
// .number()
// .optional()
// .describe("End timestamp for the transfers (optional)"),
});
var getOnChainData = {
name: "GET_PRICE",
similes: [
"GET_PRICE",
"CHECK_PRICE",
"PRICE_CHECK",
"GET_CRYPTO_PRICE",
"CRYPTO_PRICE",
"CHECK_CRYPTO_PRICE",
"PRICE_LOOKUP"
],
description: "Get current price information for a cryptocurrency pair",
validate: async (runtime, message, state) => {
return true;
},
handler: async (runtime, message, state, _options, callback, _responses) => {
try {
let service = runtime.getService(ApiService.serviceType);
if (service.is_action_executing["GET_PRICE"]) {
return false;
}
service.is_action_executing["GET_PRICE"] = true;
await service.loadPriceData();
if (service.useTransactionData) {
await service.loadTransactionData(true);
}
logger4.warn(`today_idx: ${service.today_idx}
end_day_idx: ${service.end_day_idx}`);
if (!service.today_idx || !service.end_day_idx) {
if (service.CRYPT_STAGE) {
switch (service.CRYPT_STAGE) {
case "bull":
service.start_day = bull_starting_date;
service.end_day = bull_ending_date;
break;
case "bear":
service.start_day = bear_starting_date;
service.end_day = bear_ending_date;
break;
case "sideways":
service.start_day = sideways_starting_date;
service.end_day = sideways_ending_date;
break;
}
} else {
service.start_day = starting_date;
service.end_day = ending_date;
}
service.today_idx = service.price_data.findIndex((d) => d.key === service.start_day);
service.end_day_idx = service.price_data.findIndex((d) => d.key === service.end_day);
}
if (!service.project_initialized) {
service.initProject();
}
service.step_data["DATE"] = service.price_data[service.today_idx].key;
logger4.warn(`today_idx: ${service.today_idx}
end_day_idx: ${service.end_day_idx}`);
logger4.warn("***** GET_PRICE DATA END ***** \n");
const resp = `Price and transaction data loaded.
BTC open price on ${service.price_data[service.today_idx].value["timeOpen"]} is ${service.price_data[service.today_idx].value["open"]}`;
if (callback && service.callbackInActions) {
callback({
thought: ``,
text: `Here is the on-chain price data: ${resp} `
});
}
var message;
message.content.text = "CryptoTrade_Action_GET_PRICE DONE";
message.id = asUUID2(v4_default());
await runtime.emitEvent(EventType2.MESSAGE_SENT, { runtime, message, source: "CryptoTrade_Action_GET_PRICE" });
logger4.warn("***** ACTION GET_PRICE DONE *****");
service.step_state["GET_PRICE"] = "DONE";
service.step_state["Executing"] = true;
service.is_action_executing["GET_PRICE"] = false;
return true;
} catch (error) {
logger4.error("Error in price check:", error);
if (callback) {
callback({
text: `Error in price check: ${error.message} `
});
return false;
}
return false;
}
},
examples: [
[
{
name: "{{user1}}",
content: {
text: "What's the market price of Bitcoin yesterday?"
}
},
{
name: "{{agent}}",
content: {
text: "I'll check the Bitcoin market price for you right away.",
action: "GET_PRICE"
}
},
{
name: "{{agent}}",
content: {
text: "The current BTC market price is {date}, open: {open price} USDT, close: {close price}} USDT"
}
}
],
[
{
user: "{{user1}}",
content: {
text: "Can you check ETH price on 2025/04/03?"
}
},
{
user: "{{agent}}",
content: {
text: "I'll fetch the Ethereum price on 2025/04/03 for you.",
action: "GET_PRICE"
}
},
{
user: "{{agent}}",
content: {
text: "The ETH price on 2025/04/03 is {date}, open: {open price} USDT, close: {close price}} USDT"
}
}
]
]
};
// src/actions/Common/ActionMakeTrade.ts
import {
EventType as EventType3,
logger as logger5,
asUUID as asUUID3,
composePromptFromState
} from "@elizaos/core";
var makeTrade = {
name: "MAKE_TRADE",
similes: [
"MAKE_DECISION"
],
description: "Make a cryptocurrency trade",
validate: async (_runtime, _message, _state) => {
return true;
},
handler: async (runtime, message, state, _options, callback, _responses) => {
try {
const service = runtime.getService(ApiService.serviceType);
let tmp = await service.getPromptOfMakeTrade("BTC");
const prompt = composePromptFromState({
state,
template: tmp
});
let resp = await service.tryToCallLLMsWithoutFormat(prompt, true, true);
service.step_data["TRADE_REASON"] = resp;
if (service.step_data["TRADE_ACTION_VALUE"] === -999) {
service.step_data["TRADE_ACTION_VALUE"] = 0;
}
await service.executeTrade();
await service.calculateROI();
if (callback) {
callback({
thought: `${resp}`,
text: `Here is the action of Trade Agent:
Action: ${service.step_data["TRADE_ACTION"]}
Value: ${service.step_data["TRADE_ACTION_VALUE"]}
Daily Return: ${service.step_data["TODAY_ROI"] * 100} %
Total Return: ${service.total_roi * 100} %`
});
}
var message;
message.content.text = "CryptoTrade_Action_MAKE_TRADE DONE";
message.id = asUUID3(v4_default());
runtime.emitEvent(EventType3.MESSAGE_SENT, { runtime, message, source: "CryptoTrade_Action_MAKE_TRADE" });
logger5.warn("***** ACTION MAKE_TRADE DONE *****");
service.step_state["MAKE_TRADE"] = "DONE";
service.step_state["Executing"] = false;
service.stepEnd();
return true;
} catch (error) {
logger5.error("Error in MAKE_TRADE:", error);
if (callback) {
callback({
text: `
Error in MAKE_TRADE:
${error.message}
`
});
return false;
}
return false;
}
},
examples: []
};
// src/actions/Common/ActionProcessOffChainNewsData.ts
import {
EventType as EventType4,
logger as logger6,
asUUID as asUUID4,
composePromptFromState as composePromptFromState2
} from "@elizaos/core";
var processNewsData = {
name: "PROCESS_NEWS",
similes: [
"ANALYZE_NEWS"
],
description: "Analyze news and make a cryptocurrency trade",
validate: async (runtime, message, state) => {
return true;
},
handler: async (runtime, message, state, _options, callback, _responses) => {
try {
let service = runtime.getService(ApiService.serviceType);
if (service.is_action_executing["PROCESS_NEWS"]) {
logger6.error("***** ACTION PROCESS_NEWS IS RUNNING, SKIP ACTION ***** \n");
return false;
}
service.is_action_executing["PROCESS_NEWS"] = true;
logger6.error(`[CRYPTOTRADE] PROCESS_NEWS START
`);
let tmp = await service.getPromptOfProcessNewsData("BTC", service.price_data[service.today_idx].key);
const prompt = composePromptFromState2({
state,
template: tmp
});
let resp = await service.tryToCallLLMsWithoutFormat(prompt);
if (callback && service.callbackInActions) {
callback({
thought: `Reading news on ${service.price_data[service.today_idx].key}...`,
text: `Here is the reponse of News Analysis Agent:
${resp}`
});
}
service.step_data["ANALYSIS_REPORT_NEWS"] = resp;
service.step_state["PROCESS_NEWS"] = "DONE";
var message;
message.content.text = "CryptoTrade_Action_PROCESS_NEWS DONE";
message.id = asUUID4(v4_default());
runtime.emitEvent(EventType4.MESSAGE_SENT, { runtime, message, source: "CryptoTrade_Action_PROCESS_NEWS" });
logger6.warn("***** ACTION PROCESS_NEWS DONE *****");
service.is_action_executing["PROCESS_NEWS"] = false;
return true;
} catch (error) {
logger6.error("Error in news analyse:", error);
if (callback) {
callback({
text: `
Error in news analyze:
${error.message}
`
});
return false;
}
return false;
}
},
examples: []
};
// src/actions/Common/ActionProcessOnChainData.ts
import {
EventType as EventType5,
logger as logger7,
asUUID as asUUID5,
composePromptFromState as composePromptFromState3
} from "@elizaos/core";
var processPriceData = {
name: "PROCESS_PRICE",
similes: [
"ANALYZE_PRICE"
],
description: "Analyze price and make a cryptocurrency trade",
validate: async (runtime, message, state) => {
return true;
},
handler: async (runtime, message, state, _options, callback, _responses) => {
try {
let service = runtime.getService(ApiService.serviceType);
if (service.is_action_executing["PROCESS_PRICE"]) {
logger7.error("***** ACTION PROCESS_PRICE IS RUNNING, SKIP ACTION ***** \n");
return false;
}
service.is_action_executing["PROCESS_PRICE"] = true;
logger7.error(`[CRYPTOTRADE] PROCESS_PRICE START
`);
let tmp = await service.getPromptOfOnChainData("BTC", service.price_data[service.today_idx].key);
const prompt = composePromptFromState3({
state,
template: tmp
});
let resp = await service.tryToCallLLMsWithoutFormat(prompt);
if (callback && service.callbackInActions) {
if (!resp || resp === "") {
callback({
text: `
LLM ERROR: NOT RESPOND
`
});
return;
}
callback({
thought: `Reading On-Chain data on ${service.price_data[service.today_idx].key}...`,
text: `Here is the reponse of On-Chain Data Analysis Agent:
${resp}`
});
}
service.step_data["ANALYSIS_REPORT_ON_CHAIN"] = resp;
service.step_state["PROCESS_PRICE"] = "DONE";
var message;
message.content.text = "CryptoTrade_Action_PROCESS_PRICE DONE";
message.id = asUUID5(v4_default());
await runtime.emitEvent(EventType5.MESSAGE_SENT, { runtime, message, source: "CryptoTrade_Action_PROCESS_PRICE" });
logger7.warn("***** ACTION PROCESS_PRICE DONE *****");
service.is_action_executing["PROCESS_PRICE"] = false;
return true;
} catch (error) {
logger7.error("Error in price analyse:", error);
if (callback) {
callback({
text: `
Error in price analyze:
${error.message}
`
});
return false;
}
return false;
}
},
examples: []
};
// src/actions/Common/ActionProcessReflect.ts
import {
EventType as EventType6,
logger as logger8,
asUUID as asUUID6,
composePromptFromState as composePromptFromState4
} from "@elizaos/core";
var processRelect = {
name: "PROCESS_REFLECT",
similes: [
"ANALYZE_REFLECT"
],
description: "Analyze records and reflect to make a cryptocurrency trade",
validate: async (runtime, message, state) => {
return true;
},
handler: async (runti