Add token expiry and Telegram notification features

- Introduced `STANDX_TOKEN_EXPIRY` configuration to manage token expiration, including handling logic for active, expired, and silent states.
- Implemented Telegram notifications for key events such as order filled, position opened/closed, stop loss triggered, and token expiration.
- Updated Maker Points engine to integrate token expiry checks and notification sending, enhancing user awareness of trading conditions.
- Enhanced documentation to include details on configuring token expiry and Telegram notifications for improved user guidance.
This commit is contained in:
discountry
2026-01-10 12:44:52 +08:00
parent cb1cef6f1b
commit 598f2a0eb6
8 changed files with 611 additions and 4 deletions
+113
View File
@@ -0,0 +1,113 @@
import type { NotificationSender, TradeNotification, NotificationConfig } from "./types";
export interface TelegramConfig extends NotificationConfig {
botToken: string;
chatId: string;
}
const LEVEL_EMOJI: Record<string, string> = {
info: "️",
warn: "⚠️",
error: "🚨",
success: "✅",
};
const TYPE_EMOJI: Record<string, string> = {
order_filled: "📝",
position_opened: "📈",
position_closed: "📉",
stop_loss: "🛑",
token_expired: "⏰",
custom: "📢",
};
function formatNotificationMessage(notification: TradeNotification, accountLabel?: string): string {
const levelEmoji = LEVEL_EMOJI[notification.level] ?? "";
const typeEmoji = TYPE_EMOJI[notification.type] ?? "";
const timestamp = notification.timestamp ?? Date.now();
const time = new Date(timestamp).toISOString().replace("T", " ").substring(0, 19);
const label = notification.accountLabel ?? accountLabel ?? notification.symbol;
const lines: string[] = [
`${typeEmoji}${levelEmoji} [${label}] ${notification.title}`,
``,
`${notification.message}`,
];
if (notification.details && Object.keys(notification.details).length > 0) {
lines.push(``);
for (const [key, value] of Object.entries(notification.details)) {
if (value != null) {
lines.push(`${key}: ${value}`);
}
}
}
lines.push(``);
lines.push(`🕐 ${time} UTC`);
lines.push(`📊 ${notification.symbol}`);
return lines.join("\n");
}
export class TelegramNotifier implements NotificationSender {
private readonly config: TelegramConfig;
private readonly baseUrl: string;
private sendQueue: Promise<void> = Promise.resolve();
constructor(config: Partial<TelegramConfig> = {}) {
this.config = {
enabled: Boolean(config.botToken && config.chatId),
botToken: config.botToken ?? "",
chatId: config.chatId ?? "",
accountLabel: config.accountLabel,
};
this.baseUrl = `https://api.telegram.org/bot${this.config.botToken}`;
}
isEnabled(): boolean {
return this.config.enabled;
}
async send(notification: TradeNotification): Promise<void> {
if (!this.isEnabled()) {
return;
}
this.sendQueue = this.sendQueue
.then(() => this.doSend(notification))
.catch(() => {});
}
private async doSend(notification: TradeNotification): Promise<void> {
const text = formatNotificationMessage(notification, this.config.accountLabel);
const url = `${this.baseUrl}/sendMessage`;
try {
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
chat_id: this.config.chatId,
text,
}),
});
if (!response.ok) {
const errorText = await response.text().catch(() => "unknown error");
console.error(`[Telegram] Failed to send notification: ${response.status} ${errorText}`);
}
} catch (error) {
console.error(`[Telegram] Failed to send notification: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
export function createTelegramNotifier(): TelegramNotifier {
return new TelegramNotifier({
botToken: process.env.TELEGRAM_BOT_TOKEN,
chatId: process.env.TELEGRAM_CHAT_ID,
accountLabel: process.env.TELEGRAM_ACCOUNT_LABEL,
});
}