update
This commit is contained in:
parent
9cde60bb9c
commit
c35d84cf07
11 changed files with 2109 additions and 68 deletions
1831
package-lock.json
generated
1831
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -12,6 +12,7 @@
|
||||||
"dotenv": "^17.0.1",
|
"dotenv": "^17.0.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"node-schedule": "^2.1.1",
|
"node-schedule": "^2.1.1",
|
||||||
|
"node-telegram-bot-api": "^0.66.0",
|
||||||
"swagger-ui-express": "^5.0.1",
|
"swagger-ui-express": "^5.0.1",
|
||||||
"technicalindicators": "^3.0.0",
|
"technicalindicators": "^3.0.0",
|
||||||
"yamljs": "^0.3.0"
|
"yamljs": "^0.3.0"
|
||||||
|
|
@ -30,6 +31,7 @@
|
||||||
"@types/express": "^5.0.3",
|
"@types/express": "^5.0.3",
|
||||||
"@types/node": "^24.0.10",
|
"@types/node": "^24.0.10",
|
||||||
"@types/node-schedule": "^2.1.7",
|
"@types/node-schedule": "^2.1.7",
|
||||||
|
"@types/node-telegram-bot-api": "^0.64.9",
|
||||||
"@types/swagger-ui-express": "^4.1.8",
|
"@types/swagger-ui-express": "^4.1.8",
|
||||||
"@types/yamljs": "^0.2.34",
|
"@types/yamljs": "^0.2.34",
|
||||||
"nodemon": "^3.1.10",
|
"nodemon": "^3.1.10",
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ export interface Analysis {
|
||||||
isSell: boolean;
|
isSell: boolean;
|
||||||
isTouch200: boolean;
|
isTouch200: boolean;
|
||||||
isReverse200: boolean;
|
isReverse200: boolean;
|
||||||
|
isOverBbUpper: boolean;
|
||||||
|
isUnderBbLower: boolean;
|
||||||
lowHight: number;
|
lowHight: number;
|
||||||
numberTouch200: number;
|
numberTouch200: number;
|
||||||
numberMacdCrossUp: number;
|
numberMacdCrossUp: number;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
export interface Order {
|
export interface Order {
|
||||||
symbol: string;
|
symbol: string;
|
||||||
|
interval: string;
|
||||||
side: "buy" | "sell";
|
side: "buy" | "sell";
|
||||||
entry: number;
|
entry: number;
|
||||||
volume: 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) => {
|
export const createOrder = async (req: Request, res: Response) => {
|
||||||
// TODO: Lấy client từ credential
|
// TODO: Lấy client từ credential
|
||||||
const order = await bybitService.createOrder(req.body);
|
const order = await bybitService.submitOrder(req.body);
|
||||||
res.json(order);
|
res.json(order);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,52 +5,23 @@ import { KlineIntervalV3 } from 'bybit-api';
|
||||||
import { sendLarkMessage } from '../services/messageService';
|
import { sendLarkMessage } from '../services/messageService';
|
||||||
import { Candle } from '../dao/candles';
|
import { Candle } from '../dao/candles';
|
||||||
import { Order } from '../dao/order';
|
import { Order } from '../dao/order';
|
||||||
|
import { EventHandler } from '../services/indicatorService';
|
||||||
|
|
||||||
const indicatorService = new IndicatorService();
|
const indicatorService = new IndicatorService();
|
||||||
const bybitService = new BybitService(process.env.BYBIT_API_KEY!, process.env.BYBIT_API_SECRET!, false);
|
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
|
// 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
|
// TODO: Lấy client thật nếu cần
|
||||||
const candles = await bybitService.getCandles({ symbol, interval, category: 'linear', limit: 500, end });
|
const candles = await bybitService.getCandles({ symbol, interval, category: 'linear', limit: 500, end });
|
||||||
const wave = await indicatorService.getWave(symbol, interval);
|
const wave = await indicatorService.getWave(symbol, interval);
|
||||||
console.log(wave);
|
|
||||||
const analysis= indicatorService.analyze(candles, wave);
|
const analysis= indicatorService.analyze(candles, wave);
|
||||||
indicatorService.handleEvent(analysis, candles, {
|
if (eventHandler) {
|
||||||
onBuy: (order: Order, reason: string) => {
|
indicatorService.handleEvent(analysis, candles, eventHandler);
|
||||||
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)}`);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
analysis.symbol = symbol;
|
analysis.symbol = symbol;
|
||||||
analysis.interval = interval;
|
analysis.interval = interval;
|
||||||
console.log(analysis);
|
|
||||||
await indicatorService.upsertWave({
|
await indicatorService.upsertWave({
|
||||||
symbol,
|
symbol,
|
||||||
interval,
|
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();
|
const rule = new schedule.RecurrenceRule();
|
||||||
rule.tz = 'Asia/Ho_Chi_Minh';
|
rule.tz = 'Asia/Ho_Chi_Minh';
|
||||||
switch (interval) {
|
switch (interval) {
|
||||||
|
|
@ -89,6 +60,6 @@ export function createCandleAnalysisSchedule(symbol: string, interval: KlineInte
|
||||||
}
|
}
|
||||||
rule.second = 59;
|
rule.second = 59;
|
||||||
schedule.scheduleJob(rule, () => {
|
schedule.scheduleJob(rule, () => {
|
||||||
analyzeCandlesJob(symbol, interval);
|
analyzeCandlesJob(symbol, interval, eventHandler);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,186 @@
|
||||||
import { createCandleAnalysisSchedule } from "./candleAnalysisSchedule";
|
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");
|
function sendMessage(message: string) {
|
||||||
createCandleAnalysisSchedule("BTCUSDT", "15");
|
sendLarkMessage("oc_f9b2e8f0309ecab0c94e3e134b0ddd29", message);
|
||||||
createCandleAnalysisSchedule("ETHUSDT", "60");
|
}
|
||||||
createCandleAnalysisSchedule("BTCUSDT", "60");
|
|
||||||
createCandleAnalysisSchedule("ETHUSDT", "240");
|
const bybitService = new BybitService(
|
||||||
createCandleAnalysisSchedule("BTCUSDT", "240");
|
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';
|
import { Candle } from '../dao/candles';
|
||||||
export class BybitService {
|
export class BybitService {
|
||||||
private client: RestClientV5;
|
private client: RestClientV5;
|
||||||
|
|
@ -11,7 +11,7 @@ export class BybitService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async createOrder(orderData: OrderParamsV5): Promise<any> {
|
async submitOrder(orderData: OrderParamsV5): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const res = await this.client.submitOrder(orderData);
|
const res = await this.client.submitOrder(orderData);
|
||||||
return { success: true, data: res };
|
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[]> {
|
async listOrders(params: GetAccountOrdersParamsV5): Promise<any[]> {
|
||||||
try {
|
try {
|
||||||
const res = await this.client.getActiveOrders(params);
|
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 {
|
try {
|
||||||
const res = await this.client.getPositionInfo(params);
|
const res = await this.client.getPositionInfo(params);
|
||||||
return res.result?.list || [];
|
return res.result?.list || [];
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,12 @@ import { Order } from "../dao/order";
|
||||||
import { supabase } from "./supabaseService";
|
import { supabase } from "./supabaseService";
|
||||||
import { Wave } from "../dao/wave";
|
import { Wave } from "../dao/wave";
|
||||||
|
|
||||||
|
export type EventType = "HighVolatility" | "PinBar" | "EmaCross" | "MacdCross" | "MacdCrossUp" | "MacdCrossDown" | "Touch200" | "Reverse200";
|
||||||
|
|
||||||
export interface EventHandler {
|
export interface EventHandler {
|
||||||
onBuy: (candle: Order, reason: string) => void;
|
onBuy: (candle: Order, reason: string) => void;
|
||||||
onSell: (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 {
|
export class IndicatorService {
|
||||||
|
|
@ -77,6 +79,8 @@ export class IndicatorService {
|
||||||
isSell: false,
|
isSell: false,
|
||||||
isTouch200: false,
|
isTouch200: false,
|
||||||
isReverse200: false,
|
isReverse200: false,
|
||||||
|
isOverBbUpper: false,
|
||||||
|
isUnderBbLower: false,
|
||||||
lowHight: wave.lowOrHighPrice,
|
lowHight: wave.lowOrHighPrice,
|
||||||
numberTouch200: wave.numberTouchEma,
|
numberTouch200: wave.numberTouchEma,
|
||||||
numberMacdCrossUp: wave.numberMacdCrossUp,
|
numberMacdCrossUp: wave.numberMacdCrossUp,
|
||||||
|
|
@ -94,6 +98,14 @@ export class IndicatorService {
|
||||||
analysis.currentBB.middle = bb[0].middle;
|
analysis.currentBB.middle = bb[0].middle;
|
||||||
analysis.currentBB.lower = bb[0].lower;
|
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 (ema34[0] > ema200[0]) {
|
||||||
if (wave.trend === "Bearish") {
|
if (wave.trend === "Bearish") {
|
||||||
analysis.numberTouch200 = 0;
|
analysis.numberTouch200 = 0;
|
||||||
|
|
@ -241,6 +253,7 @@ export class IndicatorService {
|
||||||
|
|
||||||
const order: Order = {
|
const order: Order = {
|
||||||
symbol: analysis.symbol,
|
symbol: analysis.symbol,
|
||||||
|
interval: analysis.interval,
|
||||||
side,
|
side,
|
||||||
entry,
|
entry,
|
||||||
stopLoss: side === "buy" ? lowestPrice : highestPrice,
|
stopLoss: side === "buy" ? lowestPrice : highestPrice,
|
||||||
|
|
@ -287,6 +300,10 @@ export class IndicatorService {
|
||||||
const order = this.makeOrder(analysis, candles, "sell");
|
const order = this.makeOrder(analysis, candles, "sell");
|
||||||
eventHandler.onSell(order, "Counter trend rủi ro cao MACD Cross Down");
|
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 { Client } from '@larksuiteoapi/node-sdk';
|
||||||
|
import TelegramBot from 'node-telegram-bot-api';
|
||||||
|
|
||||||
export async function sendLarkMessage(receiveId: string, message: string): Promise<any> {
|
export async function sendLarkMessage(receiveId: string, message: string): Promise<any> {
|
||||||
console.log(process.env.LARK_APP_ID!, process.env.LARK_APP_SECRET!);
|
console.log(process.env.LARK_APP_ID!, process.env.LARK_APP_SECRET!);
|
||||||
|
|
@ -20,3 +21,8 @@ export async function sendLarkMessage(receiveId: string, message: string): Promi
|
||||||
|
|
||||||
return { success: true, data: res.data };
|
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);
|
||||||
|
}
|
||||||
21
test.ts
21
test.ts
|
|
@ -7,10 +7,11 @@ import {
|
||||||
isPinBar,
|
isPinBar,
|
||||||
} from "./src/helpers/candles";
|
} from "./src/helpers/candles";
|
||||||
import { analyzeCandlesJob } from "./src/schedule/candleAnalysisSchedule";
|
import { analyzeCandlesJob } from "./src/schedule/candleAnalysisSchedule";
|
||||||
|
import { log } from "console";
|
||||||
|
|
||||||
const bybitService = new BybitService(
|
const bybitService = new BybitService(
|
||||||
process.env.BYBIT_API_KEY!,
|
'dqGCPAJzLKoTRfgCMq',
|
||||||
process.env.BYBIT_API_SECRET!,
|
'aDYziLWN2jvdWNuz4QhWrM1O65JZ5f1NtEUO',
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -20,5 +21,19 @@ function toTimestamp(strTime: string): number {
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
// await analyzeCandlesJob("ETHUSDT", "15", toTimestamp("2025-07-08 23:59:59"));
|
// await analyzeCandlesJob("ETHUSDT", "15", toTimestamp("2025-07-08 23:59:59"));
|
||||||
await analyzeCandlesJob("ETHUSDT", "15");
|
// await analyzeCandlesJob("ETHUSDT", "15");
|
||||||
|
// const res = await bybitService.createOrder({
|
||||||
|
// category: "linear",
|
||||||
|
// symbol: "ETHUSDT",
|
||||||
|
// side: "Buy",
|
||||||
|
// orderType: "Limit",
|
||||||
|
// price: "3146.81",
|
||||||
|
// qty: "1",
|
||||||
|
// takeProfit: "3200",
|
||||||
|
// stopLoss: "3000",
|
||||||
|
// });
|
||||||
|
// console.log(res);
|
||||||
|
|
||||||
|
const balance = await bybitService.getBalance();
|
||||||
|
console.log(balance.result.list[0].totalEquity);
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue