update
This commit is contained in:
parent
9cde60bb9c
commit
c35d84cf07
11 changed files with 2109 additions and 68 deletions
|
|
@ -15,6 +15,8 @@ export interface Analysis {
|
|||
isSell: boolean;
|
||||
isTouch200: boolean;
|
||||
isReverse200: boolean;
|
||||
isOverBbUpper: boolean;
|
||||
isUnderBbLower: boolean;
|
||||
lowHight: number;
|
||||
numberTouch200: number;
|
||||
numberMacdCrossUp: number;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
export interface Order {
|
||||
symbol: string;
|
||||
interval: string;
|
||||
side: "buy" | "sell";
|
||||
entry: number;
|
||||
volume: number;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const bybitService = new BybitService(process.env.BYBIT_API_KEY!, process.env.BY
|
|||
|
||||
export const createOrder = async (req: Request, res: Response) => {
|
||||
// TODO: Lấy client từ credential
|
||||
const order = await bybitService.createOrder(req.body);
|
||||
const order = await bybitService.submitOrder(req.body);
|
||||
res.json(order);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,52 +5,23 @@ import { KlineIntervalV3 } from 'bybit-api';
|
|||
import { sendLarkMessage } from '../services/messageService';
|
||||
import { Candle } from '../dao/candles';
|
||||
import { Order } from '../dao/order';
|
||||
import { EventHandler } from '../services/indicatorService';
|
||||
|
||||
const indicatorService = new IndicatorService();
|
||||
const bybitService = new BybitService(process.env.BYBIT_API_KEY!, process.env.BYBIT_API_SECRET!, false);
|
||||
|
||||
function sendMessage(message: string) {
|
||||
sendLarkMessage('oc_f9b2e8f0309ecab0c94e3e134b0ddd29', message);
|
||||
}
|
||||
|
||||
// Hàm thực hiện phân tích nến
|
||||
export async function analyzeCandlesJob(symbol: string, interval: KlineIntervalV3, end?: number) {
|
||||
export async function analyzeCandlesJob(symbol: string, interval: KlineIntervalV3, eventHandler?: EventHandler, end?: number) {
|
||||
// TODO: Lấy client thật nếu cần
|
||||
const candles = await bybitService.getCandles({ symbol, interval, category: 'linear', limit: 500, end });
|
||||
const wave = await indicatorService.getWave(symbol, interval);
|
||||
console.log(wave);
|
||||
const analysis= indicatorService.analyze(candles, wave);
|
||||
indicatorService.handleEvent(analysis, candles, {
|
||||
onBuy: (order: Order, reason: string) => {
|
||||
sendMessage(`Buy ${symbol} ${interval}M ${reason}
|
||||
symbol: ${order.symbol}
|
||||
side: ${order.side}
|
||||
entry: ${order.entry}
|
||||
stopLoss: ${order.stopLoss}
|
||||
volume: ${order.volume}
|
||||
reason: ${reason}
|
||||
`);
|
||||
console.log(`Buy ${symbol} ${interval}M ${reason} ${candles[0].time}`);
|
||||
},
|
||||
onSell: (order: Order, reason: string) => {
|
||||
sendMessage(`Sell ${symbol} ${interval}M ${reason}
|
||||
symbol: ${order.symbol}
|
||||
side: ${order.side}
|
||||
entry: ${order.entry}
|
||||
stopLoss: ${order.stopLoss}
|
||||
volume: ${order.volume}
|
||||
reason: ${reason}
|
||||
`);
|
||||
console.log(`Sell ${symbol} ${interval}M ${reason} ${candles[0].time}`);
|
||||
},
|
||||
onEvent: (data: any, eventType: string) => {
|
||||
sendMessage(`${eventType} ${symbol} ${interval}M ${data.close}`)
|
||||
console.log(`${eventType} ${symbol} ${interval}M ${JSON.stringify(data)}`);
|
||||
},
|
||||
});
|
||||
if (eventHandler) {
|
||||
indicatorService.handleEvent(analysis, candles, eventHandler);
|
||||
}
|
||||
|
||||
analysis.symbol = symbol;
|
||||
analysis.interval = interval;
|
||||
console.log(analysis);
|
||||
await indicatorService.upsertWave({
|
||||
symbol,
|
||||
interval,
|
||||
|
|
@ -62,7 +33,7 @@ export async function analyzeCandlesJob(symbol: string, interval: KlineIntervalV
|
|||
});
|
||||
}
|
||||
|
||||
export function createCandleAnalysisSchedule(symbol: string, interval: KlineIntervalV3) {
|
||||
export function createCandleAnalysisSchedule(symbol: string, interval: KlineIntervalV3, eventHandler?: EventHandler) {
|
||||
const rule = new schedule.RecurrenceRule();
|
||||
rule.tz = 'Asia/Ho_Chi_Minh';
|
||||
switch (interval) {
|
||||
|
|
@ -89,6 +60,6 @@ export function createCandleAnalysisSchedule(symbol: string, interval: KlineInte
|
|||
}
|
||||
rule.second = 59;
|
||||
schedule.scheduleJob(rule, () => {
|
||||
analyzeCandlesJob(symbol, interval);
|
||||
analyzeCandlesJob(symbol, interval, eventHandler);
|
||||
});
|
||||
}
|
||||
|
|
@ -1,8 +1,186 @@
|
|||
import { createCandleAnalysisSchedule } from "./candleAnalysisSchedule";
|
||||
import { EventHandler, EventType } from "../services/indicatorService";
|
||||
import { Order } from "../dao/order";
|
||||
import { sendLarkMessage } from "../services/messageService";
|
||||
import { BybitService } from "src/services/bybitService";
|
||||
import { Analysis } from "src/dao/analysis";
|
||||
|
||||
createCandleAnalysisSchedule("ETHUSDT", "15");
|
||||
createCandleAnalysisSchedule("BTCUSDT", "15");
|
||||
createCandleAnalysisSchedule("ETHUSDT", "60");
|
||||
createCandleAnalysisSchedule("BTCUSDT", "60");
|
||||
createCandleAnalysisSchedule("ETHUSDT", "240");
|
||||
createCandleAnalysisSchedule("BTCUSDT", "240");
|
||||
function sendMessage(message: string) {
|
||||
sendLarkMessage("oc_f9b2e8f0309ecab0c94e3e134b0ddd29", message);
|
||||
}
|
||||
|
||||
const bybitService = new BybitService(
|
||||
process.env.BYBIT_API_KEY!,
|
||||
process.env.BYBIT_API_SECRET!,
|
||||
false
|
||||
);
|
||||
|
||||
|
||||
|
||||
const eventHandlerFuture: EventHandler = {
|
||||
onBuy: (order: Order, reason: string) => {
|
||||
sendMessage(`Future Buy ${order.symbol} ${order.interval}M
|
||||
symbol: ${order.symbol}
|
||||
side: ${order.side}
|
||||
entry: ${order.entry}
|
||||
stopLoss: ${order.stopLoss}
|
||||
volume: ${order.volume}
|
||||
reason: ${reason}
|
||||
`);
|
||||
|
||||
bybitService.createOrderFuture(0.1, 25, {
|
||||
category: "linear",
|
||||
symbol: order.symbol,
|
||||
side: "Buy",
|
||||
orderType: "Limit",
|
||||
price: Number(order.entry).toFixed(2),
|
||||
qty: '1',
|
||||
});
|
||||
|
||||
console.log(
|
||||
`Buy ${order.symbol} ${order.interval}M ${reason} ${order.entry}`
|
||||
);
|
||||
},
|
||||
onSell: (order: Order, reason: string) => {
|
||||
sendMessage(`Future Sell ${order.symbol} ${order.interval}M
|
||||
symbol: ${order.symbol}
|
||||
side: ${order.side}
|
||||
entry: ${order.entry}
|
||||
stopLoss: ${order.stopLoss}
|
||||
volume: ${order.volume}
|
||||
reason: ${reason}
|
||||
`);
|
||||
bybitService.createOrderFuture(0.1, 25, {
|
||||
category: "linear",
|
||||
symbol: order.symbol,
|
||||
side: "Sell",
|
||||
orderType: "Limit",
|
||||
price: Number(order.entry).toFixed(2),
|
||||
qty: '1',
|
||||
});
|
||||
console.log(
|
||||
`Sell ${order.symbol} ${order.interval}M ${reason} ${order.entry}`
|
||||
);
|
||||
},
|
||||
onEvent: async (eventType: EventType, analysis: Analysis) => {
|
||||
if (eventType === "HighVolatility") {
|
||||
const positions = await bybitService.listPositions({
|
||||
symbol: analysis.symbol,
|
||||
});
|
||||
if (positions.length > 0) {
|
||||
const position = positions[0];
|
||||
if (position.side === "Buy" && analysis.isOverBbUpper) {
|
||||
const halfSize = (Number(position.size) / 2).toFixed(2);
|
||||
await bybitService.submitOrder({
|
||||
category: "linear",
|
||||
symbol: analysis.symbol,
|
||||
side: "Sell",
|
||||
orderType: "Limit",
|
||||
price: Number(analysis.currentBB.upper).toFixed(2),
|
||||
qty: halfSize,
|
||||
});
|
||||
sendMessage(`Future Căt nửa ${analysis.symbol} ${analysis.interval}M ${analysis.currentBB.upper} ${halfSize}`);
|
||||
}
|
||||
if (position.side === "Sell" && analysis.isUnderBbLower) {
|
||||
const halfSize = (Number(position.size) / 2).toFixed(2);
|
||||
await bybitService.submitOrder({
|
||||
category: "linear",
|
||||
symbol: analysis.symbol,
|
||||
side: "Buy",
|
||||
orderType: "Limit",
|
||||
price: Number(analysis.currentBB.lower).toFixed(2),
|
||||
qty: halfSize,
|
||||
});
|
||||
sendMessage(`Future Căt nửa ${analysis.symbol} ${analysis.interval}M ${analysis.currentBB.lower} ${halfSize}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const eventHandlerSpot: EventHandler = {
|
||||
onBuy: (order: Order, reason: string) => {
|
||||
sendMessage(`Spot Buy ${order.symbol} ${order.interval}M
|
||||
symbol: ${order.symbol}
|
||||
side: ${order.side}
|
||||
entry: ${order.entry}
|
||||
stopLoss: ${order.stopLoss}
|
||||
volume: ${order.volume}
|
||||
reason: ${reason}
|
||||
`);
|
||||
|
||||
bybitService.createOrder(0.1, {
|
||||
category: "spot",
|
||||
symbol: order.symbol,
|
||||
side: "Buy",
|
||||
orderType: "Limit",
|
||||
price: Number(order.entry).toFixed(2),
|
||||
qty: '1',
|
||||
});
|
||||
|
||||
console.log(
|
||||
`Buy ${order.symbol} ${order.interval}M ${reason} ${order.entry}`
|
||||
);
|
||||
},
|
||||
onSell: (order: Order, reason: string) => {
|
||||
sendMessage(`Spot Sell ${order.symbol} ${order.interval}M
|
||||
symbol: ${order.symbol}
|
||||
side: ${order.side}
|
||||
entry: ${order.entry}
|
||||
stopLoss: ${order.stopLoss}
|
||||
volume: ${order.volume}
|
||||
reason: ${reason}
|
||||
`);
|
||||
},
|
||||
onEvent: async (eventType: EventType, analysis: Analysis) => {
|
||||
},
|
||||
};
|
||||
|
||||
const eventHandlerNotification: EventHandler = {
|
||||
onBuy: (order: Order, reason: string) => {
|
||||
sendMessage(`Warning Buy ${order.symbol} ${order.interval}M
|
||||
symbol: ${order.symbol}
|
||||
side: ${order.side}
|
||||
entry: ${order.entry}
|
||||
stopLoss: ${order.stopLoss}
|
||||
volume: ${order.volume}
|
||||
reason: ${reason}
|
||||
`);
|
||||
|
||||
console.log(
|
||||
`Buy ${order.symbol} ${order.interval}M ${reason} ${order.entry}`
|
||||
);
|
||||
},
|
||||
onSell: (order: Order, reason: string) => {
|
||||
sendMessage(`Warning Sell ${order.symbol} ${order.interval}M
|
||||
symbol: ${order.symbol}
|
||||
side: ${order.side}
|
||||
entry: ${order.entry}
|
||||
stopLoss: ${order.stopLoss}
|
||||
volume: ${order.volume}
|
||||
reason: ${reason}
|
||||
`);
|
||||
},
|
||||
onEvent: async (eventType: EventType, analysis: Analysis) => {
|
||||
},
|
||||
};
|
||||
|
||||
createCandleAnalysisSchedule("ETHUSDT", "15", eventHandlerFuture);
|
||||
createCandleAnalysisSchedule("BTCUSDT", "15", eventHandlerFuture);
|
||||
createCandleAnalysisSchedule("ETHUSDT", "60", eventHandlerNotification);
|
||||
createCandleAnalysisSchedule("BTCUSDT", "60", eventHandlerNotification);
|
||||
|
||||
// Spot
|
||||
createCandleAnalysisSchedule("ETHUSDT", "240", eventHandlerNotification);
|
||||
createCandleAnalysisSchedule("BTCUSDT", "240", eventHandlerNotification);
|
||||
createCandleAnalysisSchedule("SOLUSDT", "240", eventHandlerNotification);
|
||||
createCandleAnalysisSchedule("ARBUSDT", "240", eventHandlerNotification);
|
||||
createCandleAnalysisSchedule("SUIUSDT", "240", eventHandlerNotification);
|
||||
createCandleAnalysisSchedule("APTUSDT", "240", eventHandlerNotification);
|
||||
|
||||
createCandleAnalysisSchedule("ETHUSDT", "D", eventHandlerSpot);
|
||||
createCandleAnalysisSchedule("BTCUSDT", "D", eventHandlerSpot);
|
||||
createCandleAnalysisSchedule("SOLUSDT", "D", eventHandlerSpot);
|
||||
createCandleAnalysisSchedule("ARBUSDT", "D", eventHandlerSpot);
|
||||
createCandleAnalysisSchedule("SUIUSDT", "D", eventHandlerSpot);
|
||||
createCandleAnalysisSchedule("APTUSDT", "D", eventHandlerSpot);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { CancelOrderParamsV5, GetAccountOrdersParamsV5, GetKlineParamsV5, OrderParamsV5, RestClientV5 } from 'bybit-api';
|
||||
import { CancelOrderParamsV5, GetAccountOrdersParamsV5, GetKlineParamsV5, OrderParamsV5, PositionV5, RestClientV5 } from 'bybit-api';
|
||||
import { Candle } from '../dao/candles';
|
||||
export class BybitService {
|
||||
private client: RestClientV5;
|
||||
|
|
@ -11,7 +11,7 @@ export class BybitService {
|
|||
});
|
||||
}
|
||||
|
||||
async createOrder(orderData: OrderParamsV5): Promise<any> {
|
||||
async submitOrder(orderData: OrderParamsV5): Promise<any> {
|
||||
try {
|
||||
const res = await this.client.submitOrder(orderData);
|
||||
return { success: true, data: res };
|
||||
|
|
@ -20,6 +20,56 @@ export class BybitService {
|
|||
}
|
||||
}
|
||||
|
||||
async createOrder(vl: number, orderData: OrderParamsV5): Promise<any> {
|
||||
try {
|
||||
const balance = await this.client.getWalletBalance({
|
||||
accountType: "UNIFIED",
|
||||
coin: "USDT"
|
||||
});
|
||||
console.log(balance);
|
||||
const total = balance.result.list[0].totalEquity
|
||||
const volume = Number(total) * vl
|
||||
const qty = volume / Number(orderData.price)
|
||||
orderData.qty = qty.toFixed(2);
|
||||
const res = await this.client.submitOrder(orderData);
|
||||
return { success: true, data: res };
|
||||
} catch (error: any) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
async createOrderFuture(vl: number, leverage: number, orderData: OrderParamsV5): Promise<any> {
|
||||
try {
|
||||
const balance = await this.client.getWalletBalance({
|
||||
accountType: "UNIFIED",
|
||||
coin: "USDT"
|
||||
});
|
||||
await this.client.setLeverage({
|
||||
category: "linear",
|
||||
symbol: orderData.symbol,
|
||||
buyLeverage: leverage.toString(),
|
||||
sellLeverage: leverage.toString(),
|
||||
});
|
||||
|
||||
const total = balance.result.list[0].totalEquity
|
||||
const volume = Number(total) * vl * leverage
|
||||
const qty = volume / Number(orderData.price)
|
||||
orderData.qty = qty.toFixed(2);
|
||||
const res = await this.client.submitOrder(orderData);
|
||||
return { success: true, data: res };
|
||||
} catch (error: any) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
async getBalance(): Promise<any> {
|
||||
const balance = await this.client.getWalletBalance({
|
||||
accountType: "UNIFIED",
|
||||
coin: "USDT"
|
||||
});
|
||||
return balance;
|
||||
}
|
||||
|
||||
async listOrders(params: GetAccountOrdersParamsV5): Promise<any[]> {
|
||||
try {
|
||||
const res = await this.client.getActiveOrders(params);
|
||||
|
|
@ -38,7 +88,7 @@ export class BybitService {
|
|||
}
|
||||
}
|
||||
|
||||
async listPositions(params: any): Promise<any[]> {
|
||||
async listPositions(params: any): Promise<PositionV5[]> {
|
||||
try {
|
||||
const res = await this.client.getPositionInfo(params);
|
||||
return res.result?.list || [];
|
||||
|
|
|
|||
|
|
@ -11,10 +11,12 @@ import { Order } from "../dao/order";
|
|||
import { supabase } from "./supabaseService";
|
||||
import { Wave } from "../dao/wave";
|
||||
|
||||
export type EventType = "HighVolatility" | "PinBar" | "EmaCross" | "MacdCross" | "MacdCrossUp" | "MacdCrossDown" | "Touch200" | "Reverse200";
|
||||
|
||||
export interface EventHandler {
|
||||
onBuy: (candle: Order, reason: string) => void;
|
||||
onSell: (candle: Order, reason: string) => void;
|
||||
onEvent: (data: any, eventType: string) => void;
|
||||
onEvent: (eventType: EventType, analysis: Analysis) => void;
|
||||
}
|
||||
|
||||
export class IndicatorService {
|
||||
|
|
@ -77,6 +79,8 @@ export class IndicatorService {
|
|||
isSell: false,
|
||||
isTouch200: false,
|
||||
isReverse200: false,
|
||||
isOverBbUpper: false,
|
||||
isUnderBbLower: false,
|
||||
lowHight: wave.lowOrHighPrice,
|
||||
numberTouch200: wave.numberTouchEma,
|
||||
numberMacdCrossUp: wave.numberMacdCrossUp,
|
||||
|
|
@ -94,6 +98,14 @@ export class IndicatorService {
|
|||
analysis.currentBB.middle = bb[0].middle;
|
||||
analysis.currentBB.lower = bb[0].lower;
|
||||
|
||||
if (candles[0].high > analysis.currentBB.upper) {
|
||||
analysis.isOverBbUpper = true;
|
||||
}
|
||||
|
||||
if (candles[0].low < analysis.currentBB.lower) {
|
||||
analysis.isUnderBbLower = true;
|
||||
}
|
||||
|
||||
if (ema34[0] > ema200[0]) {
|
||||
if (wave.trend === "Bearish") {
|
||||
analysis.numberTouch200 = 0;
|
||||
|
|
@ -241,6 +253,7 @@ export class IndicatorService {
|
|||
|
||||
const order: Order = {
|
||||
symbol: analysis.symbol,
|
||||
interval: analysis.interval,
|
||||
side,
|
||||
entry,
|
||||
stopLoss: side === "buy" ? lowestPrice : highestPrice,
|
||||
|
|
@ -287,6 +300,10 @@ export class IndicatorService {
|
|||
const order = this.makeOrder(analysis, candles, "sell");
|
||||
eventHandler.onSell(order, "Counter trend rủi ro cao MACD Cross Down");
|
||||
}
|
||||
|
||||
if (analysis.isHighVolatility) {
|
||||
eventHandler.onEvent("HighVolatility", analysis);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Client } from '@larksuiteoapi/node-sdk';
|
||||
import TelegramBot from 'node-telegram-bot-api';
|
||||
|
||||
export async function sendLarkMessage(receiveId: string, message: string): Promise<any> {
|
||||
console.log(process.env.LARK_APP_ID!, process.env.LARK_APP_SECRET!);
|
||||
|
|
@ -19,4 +20,9 @@ export async function sendLarkMessage(receiveId: string, message: string): Promi
|
|||
});
|
||||
|
||||
return { success: true, data: res.data };
|
||||
}
|
||||
}
|
||||
|
||||
export function sendTelegramMessage(chatId: string, message: string) {
|
||||
const bot = new TelegramBot(process.env.TELEGRAM_BOT_TOKEN!);
|
||||
bot.sendMessage(chatId, message);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue