@icecoins/plugin-cryptotrade
Version:
Plugin-cryptotrade plugin for ElizaOS
1,454 lines (1,410 loc) • 55 kB
JavaScript
// src/index.ts
import {
Service as Service3,
logger as logger10,
createUniqueUuid,
asUUID as asUUID7
} from "@elizaos/core";
// src/actions/ActionGetOffChainNewsData.ts
import {
elizaLogger,
EventType,
logger as logger2,
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)();
rnds[6] = rnds[6] & 15 | 64;
rnds[8] = rnds[8] & 63 | 128;
if (buf) {
offset = offset || 0;
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,
ModelType,
Service
} 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 Service {
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) {
logger.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) {
logger.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() {
logger.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 };
configs = {};
is_action_executing = {};
price_data = [];
transaction_data = [];
news_data = [];
record = [];
onChainDataLoaded = false;
offChainNewsLoaded = false;
abortAllTasks = false;
cash;
coin_held;
starting_net_worth;
net_worth;
total_roi;
last_net_worth;
starting_price;
today_idx;
end_day_idx;
project_initialized = false;
initConfigs() {
if (process.env.CRYPT_STARTING_DAY) {
this.configs["CRYPT_STARTING_DAY"] = process.env.CRYPT_STARTING_DAY;
}
if (process.env.CRYPT_ENDING_DAY) {
this.configs["CRYPT_ENDING_DAY"] = process.env.CRYPT_ENDING_DAY;
}
if (process.env.CRYPT_STAGE) {
this.configs["CRYPT_STAGE"] = process.env.CRYPT_STAGE;
}
}
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;
}
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["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["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;
}
stepEnd() {
this.record.push(structuredClone(this.step_data));
this.initData();
this.initState();
}
updateState(Executing, GET_PRICE, GET_NEWS, PROCESS_PRICE, PROCESS_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["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"],
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 {
logger.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;
}
}
logger.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 {
logger.error("DATA: loadTransactionData start");
}
let res = "error";
try {
let values = {};
if (local) {
res = await this.readLocalCsvFile("./data/local/bitcoin_transaction_statistics.csv", "transaction", false);
logger.error("loadTransactionData END");
} else {
values = await fetchFileFromWeb();
}
} catch (error) {
logger.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_data = await this.readFileByAbsPath(path.join(dir, name));
this.news_data.push({ key: name.split(".")[0], value: news_data });
}
this.offChainNewsLoaded = true;
resolve("News data has been successfully loaded.");
} catch (error) {
reject(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 {
logger.error("DATA: loadPriceData start");
}
let res = "error";
try {
let values;
if (local) {
res = await this.readLocalCsvFile("./data/local/bitcoin_daily_price.csv", "price", true);
logger.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];
}
logger.error("MACD DATA CALC END");
} else {
values = await fetchFileFromWeb();
}
} catch (error) {
logger.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;
}
async getPromptOfOnChainData(chain = "BTC", date = "2024-09-26", windowSize = 5) {
let idx_price = this.price_data.findIndex((item) => item.key === date);
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;
let price_s = "You are an " + chain + "cryptocurrency trading analyst. The recent price and auxiliary information is given in chronological order below:" + delim;
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;
}
}
async getPromptOfNewsData(chain = "btc", date = "2024-09-26") {
let idx_news = this.news_data.findIndex((item) => item.key === date);
logger.error("API SERVICE getPromptOfNewsData: [" + idx_news + "]\n");
if (-1 != idx_news) {
let news_s = `You are an ${chain.toUpperCase()} cryptocurrency trading analyst. You are required to analyze the following news articles:` + delim;
news_s += this.news_data[idx_news].value;
news_s += delim + `Write one concise paragraph to analyze the news and estimate the market trend accordingly.`;
return news_s;
} else {
return "FAILED TO FETCH NEWS DATA";
}
}
async 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;
}
async 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) {
return new Promise(async (resolve, reject) => {
let response = "LLM HAS NOT RESPONSE";
for (var i = 0; i < LLM_retry_times; i++) {
try {
logger.warn("[CryptoTrader] tryToCallLLMsWithoutFormat *** prompt content ***\n", prompt);
response = await this.runtime.useModel(ModelType.TEXT_LARGE, {
prompt
});
logger.warn("[CryptoTrader] tryToCallLLMsWithoutFormat *** response ***\n", response);
if (parseAction) {
this.step_data["TRADE_ACTION_VALUE"] = this.parseAction(response);
if (-999 === this.step_data["TRADE_ACTION_VALUE"]) {
continue;
}
}
if (response && response.length > 10) {
break;
}
} catch (error) {
response = null;
}
}
if (!response) {
this.abortAllTasks = true;
reject("LLM_ERROR");
} else {
resolve(response);
}
});
}
async tryToCallLLMs4Json(prompt, runtime) {
return new Promise(async (resolve, reject) => {
let parsedJson;
for (var i = 0; i < LLM_retry_times; i++) {
try {
logger.warn("[CryptoTrader] tryToCallLLM *** prompt ***\n", prompt);
const response = await runtime.useModel(ModelType.TEXT_LARGE, {
prompt
});
logger.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/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;
logger2.warn("***** GET NEWS DATA START ***** \n");
const resp = `service.loadNewsData: ` + await service.loadNewsData();
logger2.warn("***** GET NEWS DATA END ***** \n", resp);
if (callback) {
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" });
logger2.warn("***** ACTION GET_NEWS DONE *****");
service.is_action_executing["GET_NEWS"] = false;
return true;
} catch (error) {
elizaLogger.error("Error in news fetch:", error);
if (callback) {
callback({
text: `
Error in news fetch:
${error.message}
`
});
return false;
}
return false;
}
},
examples: [
[
{
name: "{{user1}}",
content: {
text: "What's the news of Bitcoin yesterday?"
}
},
{
name: "{{agent}}",
content: {
text: "I'll check the Bitcoin news for you right away.",
action: "GET_NEWS"
}
},
{
name: "{{agent}}",
content: {
text: "The news of BTC market price are: [{date:{date}, title:{tiles1}, context:{context1}},.....]"
}
}
],
[
{
name: "{{user1}}",
content: {
text: "Can you check news of ETH on 2025/04/03?"
}
},
{
name: "{{agent}}",
content: {
text: "I'll fetch the news of Ethereum on 2025/04/03 for you.",
action: "GET_NEWS"
}
},
{
name: "{{agent}}",
content: {
text: "The news of ETH price on 2025/04/03 are: [{date:{date}, title:{tiles1}, context:{context1}},.....]"
}
}
]
]
};
// src/actions/ActionGetOnChainData.ts
import {
elizaLogger as elizaLogger2,
EventType as EventType2,
logger as logger3,
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;
const load_res1 = `service.loadPriceData: ` + await service.loadPriceData();
const load_res2 = `service.loadTransactionData: ` + await service.loadTransactionData(true);
logger3.warn(`today_idx: ${service.today_idx}
end_day_idx: ${service.end_day_idx}`);
if (!service.today_idx || !service.end_day_idx) {
let star_date, end_date;
if (service.configs["CRYPT_STAGE"]) {
switch (service.configs["CRYPT_STAGE"]) {
case "bull":
star_date = bull_starting_date;
end_date = bull_ending_date;
case "bear":
star_date = bear_starting_date;
end_date = bear_ending_date;
case "sideways":
star_date = sideways_starting_date;
end_date = sideways_ending_date;
}
} else {
star_date = starting_date;
end_date = ending_date;
}
service.today_idx = service.price_data.findIndex((d) => d.key === star_date);
service.end_day_idx = service.price_data.findIndex((d) => d.key === end_date);
}
if (!service.project_initialized) {
service.initProject();
}
service.step_data["DATE"] = service.price_data[service.today_idx].key;
logger3.warn(`today_idx: ${service.today_idx}
end_day_idx: ${service.end_day_idx}`);
logger3.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) {
callback({
thought: `${load_res1}
${load_res2}`,
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" });
logger3.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) {
elizaLogger2.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/ActionProcessOffChainNewsData.ts
import {
elizaLogger as elizaLogger3,
EventType as EventType3,
logger as logger4,
asUUID as asUUID3,
composePromptFromState
} 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"]) {
logger4.error("***** ACTION PROCESS_NEWS IS RUNNING, SKIP ACTION ***** \n");
return false;
}
service.is_action_executing["PROCESS_NEWS"] = true;
logger4.error(`[CRYPTOTRADE] PROCESS_NEWS START
`);
let tmp = await service.getPromptOfNewsData("BTC", service.price_data[service.today_idx].key);
const prompt = composePromptFromState({
state,
template: tmp
});
let resp = await service.tryToCallLLMsWithoutFormat(prompt);
if (callback) {
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 = asUUID3(v4_default());
runtime.emitEvent(EventType3.MESSAGE_SENT, { runtime, message, source: "CryptoTrade_Action_PROCESS_NEWS" });
logger4.warn("***** ACTION PROCESS_NEWS DONE *****");
service.is_action_executing["PROCESS_NEWS"] = false;
return true;
} catch (error) {
elizaLogger3.error("Error in news analyse:", error);
if (callback) {
callback({
text: `
Error in news analyze:
${error.message}
`
});
return false;
}
return false;
}
},
examples: []
};
// src/actions/ActionProcessOnChainData.ts
import {
elizaLogger as elizaLogger4,
EventType as EventType4,
logger as logger5,
asUUID as asUUID4,
composePromptFromState as composePromptFromState2
} 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"]) {
logger5.error("***** ACTION PROCESS_PRICE IS RUNNING, SKIP ACTION ***** \n");
return false;
}
service.is_action_executing["PROCESS_PRICE"] = true;
logger5.error(`[CRYPTOTRADE] PROCESS_PRICE START
`);
let tmp = await service.getPromptOfOnChainData("BTC", service.price_data[service.today_idx].key);
const prompt = composePromptFromState2({
state,
template: tmp
});
let resp = await service.tryToCallLLMsWithoutFormat(prompt);
if (callback) {
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 = asUUID4(v4_default());
await runtime.emitEvent(EventType4.MESSAGE_SENT, { runtime, message, source: "CryptoTrade_Action_PROCESS_PRICE" });
logger5.warn("***** ACTION PROCESS_PRICE DONE *****");
service.is_action_executing["PROCESS_PRICE"] = false;
return true;
} catch (error) {
elizaLogger4.error("Error in price analyse:", error);
if (callback) {
callback({
text: `
Error in price analyze:
${error.message}
`
});
return false;
}
return false;
}
},
examples: []
};
// src/actions/ActionProcessReflect.ts
import {
elizaLogger as elizaLogger5,
EventType as EventType5,
logger as logger6,
asUUID as asUUID5,
composePromptFromState as composePromptFromState3
} 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 (runtime, message, state, _options, callback, _responses) => {
try {
const service = runtime.getService(ApiService.serviceType);
if (service.is_action_executing["PROCESS_REFLECT"]) {
logger6.error("***** ACTION PROCESS_REFLECT IS RUNNING, SKIP ACTION ***** \n");
return false;
}
service.is_action_executing["PROCESS_REFLECT"] = true;
logger6.error(`[CRYPTOTRADE] PROCESS_REFLECT START
`);
let tmp = await service.getPromptOfReflectHistory("BTC");
const prompt = composePromptFromState3({
state,
template: tmp
});
let resp = await service.tryToCallLLMsWithoutFormat(prompt);
if (callback) {
callback({
thought: `Reading actions and results on ${service.price_data[service.today_idx].key}...`,
text: `Here is the reponse of Reflect Agent:
${resp}`
});
}
service.step_data["ANALYSIS_REPORT_REFLECT"] = resp;
service.step_state["PROCESS_REFLET"] = "DONE";
var message;
message.content.text = "CryptoTrade_Action_PROCESS_REFLET DONE";
message.id = asUUID5(v4_default());
runtime.emitEvent(EventType5.MESSAGE_SENT, { runtime, message, source: "CryptoTrade_Action_PROCESS_REFLET" });
logger6.warn("***** ACTION PROCESS_REFLET DONE *****");
service.is_action_executing["PROCESS_REFLECT"] = false;
return true;
} catch (error) {
elizaLogger5.error("Error in reflect action:", error);
if (callback) {
callback({
text: `
Error in reflect:
${error.message}
`
});
return false;
}
return false;
}
},
examples: []
};
// src/actions/ActionReply.ts
import {
logger as logger7
} from "@elizaos/core";
import { z as z2 } from "zod";
var getBlockchainPriceRequestSchema2 = z2.object({
blockchain: z2.nativeEnum({ BTC: "btc", SOL: "sol", ETH: "eth" }).describe("The blockchain to get statistics for"),
date: z2.string().optional().describe("The date to request (optional)")
// toTimestamp: z
// .number()
// .optional()
// .describe("End timestamp for the transfers (optional)"),
});
var reply = {
name: "REPLY",
similes: [
"REPLY_TO_MESSAGE",
"RESPNES",
"RESPOND"
],
description: "Generate first response to user.",
validate: async (_runtime, _message, _state) => {
return true;
},
handler: async (_runtime, message, state, _options, callback, _responses) => {
try {
logger7.info("Handling reply action");
const service = _runtime.getService(ApiService.serviceType);
const responseContent = {
thought: "",
text: "The final decision of trade is ...\n",
actions: ["REPLY"]
};
if (callback) {
await callback(responseContent);
}
} catch (error) {
logger7.error("Error in REPLY action:", error);
throw error;
}
},
examples: []
};
// src/index.ts
import {
composePromptFromState as composePromptFromState5,
EventType as EventType7,
messageHandlerTemplate
} from "@elizaos/core";
// src/actions/ActionMakeTrade.ts
import {
elizaLogger as elizaLogger6,
EventType as EventType6,
logger as logger8,
asUUID as asUUID6,
composePromptFromState as composePromptFromState4
} 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 = composePromptFromState4({
state,
template: tmp
});
let resp = await service.tryToCallLLMsWithoutFormat(prompt, 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 = asUUID6(v4_default());
runtime.emitEvent(EventType6.MESSAGE_SENT, { runtime, message, source: "CryptoTrade_Action_MAKE_TRADE" });
logger8.warn("***** ACTION MAKE_TRADE DONE *****");
service.step_state["MAKE_TRADE"] = "DONE";
service.step_state["Executing"] = false;
service.stepEnd();
return true;
} catch (error) {
elizaLogger6.error("Error in MAKE_TRADE:", error);
if (callback) {
callback({
text: `
Error in news analyze:
${error.message}
`
});
return false;
}
return false;
}
},
examples: []
};
// src/services/BinanceService.ts
import { Service as Service2, logger as logger9 } from "@elizaos/core";
import { MainClient } from "binance";
var BinanceService = class _BinanceService extends Service2 {
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) {
logger9.info(`*** Starting binance service - MODIFIED: ${(/* @__PURE__ */ new Date()).toISOString()} ***`);
const service = new _BinanceService(runtime);
service.initConfigs();
service.initEnv();
return service;
}
static async stop(runtime) {
logger9.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() {
logger9.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
});
this.client.getAccountTradeList({ symbol: "BTCUSDT" }).then((result) => {
console.log("getAccountTradeList result: ", result);
}).catch((err) => {
console.error("getAccountTradeList error: ", err);
});
this.client.getExchangeInfo().then((result) => {
console.log("getExchangeInfo inverse result: ", result);
}).catch((err) => {
console.error("getExchangeInfo inverse error: ", err);
});
}
};
// src/index.ts
var helloWorldAction = {
name: "HELLO_WORLD",
similes: ["GREET", "SAY_HELLO"],
description: "Responds with a simple hello world message",
validate: async (_runtime, _message, _state) => {
return true;
},
handler: async (_runtime, message, _state, _options, callback, _responses) => {
try {
logger10.info("Handling HELLO_WORLD action");
const responseContent = {
text: "hello world!",
actions: ["HELLO_WORLD"],
source: message.content.source
};
await callback(responseContent);
return responseContent;
} catch (error) {
logger10.error("Error in HELLO_WORLD action:", error);
throw error;
}
},
examples: [
[
{
name: "{{name1}}",
content: {
text: "Can you say hello?"
}
},
{
name: "{{name2}}",
content: {
text: "hello world!",
actions: ["HELLO_WORLD"]
}
}
]
]
};
var helloWorldProvider = {
name: "HELLO_WORLD_PROVIDER",
description: "A simple example provider",
get: async (_runtime, _message, _state) => {
return {
text: "I am a provider",
values: {},
data: {}
};
}
};
var StarterService = class _StarterService extends Service3 {
static serviceType = "starter";
capabilityDescription = "This is a starter service which is attached to the agent through the starter plugin.";
constructor(runtime) {
super(runtime);
}
static async start(runtime) {
logger10.info(`*** Starting starter service - MODIFIED: ${(/* @__PURE__ */ new Date()).toISOString()} ***`);
const service = new _StarterService(runtime);
return service;
}
static async stop(runtime) {
logger10.info("*** TESTING DEV MODE - STOP MESSAGE CHANGED! ***");
const service = runtime.getService(_StarterService.serviceType);
if (!service) {
throw new Error("Starter service not found");
}
service.stop();
}
async stop() {
logger10.info("*** THIRD CHANGE - TESTING FILE WATCHING! ***");
}
};
var managerMsgHandler = async ({
runtime,
message,
callback,
onComplete
}) => {
let _state = await runtime.composeState(message);
let service = runtime.getService(ApiService.serviceType);
if (!LLM_produce_actions) {
do {
const _responseContent = {
thought: "",
actions: ["GET_PRICE", "GET_NEWS", "PROCESS_PRICE", "PROCESS_NEWS", "PROCESS_REFLECT", "MAKE_TRADE"],
text: ""
};
const _responseMessage = {
id: asUUID7(v4_default()),
entityId: runtime.agentId,
agentId: runtime.agentId,
content: _responseContent,
roomId: message.roomId,
createdAt: Date.now()
};
if (_responseContent && _responseContent.text && (_responseContent.actions?.length === 0 || _responseContent.actions?.length === 1 && _responseContent.actions[0].toUpperCase() === "REPLY")) {
logger10.warn("[Manager Handler] callback");
await callback(_responseContent);
} else {
logger10.warn("[Manager Handler] processActions");
await runtime.processActions