diff --git a/src/routes/dashboard/(components)/app-sidebar.svelte b/src/routes/dashboard/(components)/app-sidebar.svelte index f11f99b..e5399de 100644 --- a/src/routes/dashboard/(components)/app-sidebar.svelte +++ b/src/routes/dashboard/(components)/app-sidebar.svelte @@ -42,6 +42,12 @@ icon: CoinsIcon, isActive: page.url.pathname === `/dashboard/bybit`, }, + { + title: 'ByBit Trading Stop', + url: `/dashboard/trading-stop`, + icon: CoinsIcon, + isActive: page.url.pathname === `/dashboard/trading-stop`, + }, { title: 'Logs', url: `/dashboard/logs`, diff --git a/src/routes/dashboard/trading-stop/+page.svelte b/src/routes/dashboard/trading-stop/+page.svelte new file mode 100644 index 0000000..a080e73 --- /dev/null +++ b/src/routes/dashboard/trading-stop/+page.svelte @@ -0,0 +1,189 @@ + + +
+
+
+ + + {#snippet children({ props })} + Key + + {/snippet} + + Key + + + + + {#snippet children({ props })} + Secret + + {/snippet} + + Secret + + + + + {#snippet children({ props })} + Symbol + + {/snippet} + + Symbol + + + + + {#snippet children({ props })} + Quantity (%) + + {/snippet} + + Quantity + + + + + + {#snippet children({ props })} + Quantity Decimal Point + + {/snippet} + + Quantity + + + + + {#snippet children({ props })} + Leverage + + {/snippet} + + Leverage + + + + + {#snippet children({ props })} + Take Profit (%) + + {/snippet} + + Take Profit + + + + + + {#snippet children({ props })} + Stop Loss (%) + + {/snippet} + + Stop Loss + + + + + + {#snippet children({ props })} + Demo + + {/snippet} + + Demo + + + + Copy +
+ + +
+
diff --git a/src/routes/dashboard/trading-stop/+page.ts b/src/routes/dashboard/trading-stop/+page.ts new file mode 100644 index 0000000..0dfc171 --- /dev/null +++ b/src/routes/dashboard/trading-stop/+page.ts @@ -0,0 +1,17 @@ +import { superValidate } from 'sveltekit-superforms' +import { zod } from 'sveltekit-superforms/adapters' +import { formSchema } from './schema' + +export const load = async () => { + const form = await superValidate( + { + key: '', + secret: '', + symbol: 'BTCUSDT', + }, + zod(formSchema), + { errors: false }, + ) + + return { form } +} diff --git a/src/routes/dashboard/trading-stop/schema.ts b/src/routes/dashboard/trading-stop/schema.ts new file mode 100644 index 0000000..f2d5c79 --- /dev/null +++ b/src/routes/dashboard/trading-stop/schema.ts @@ -0,0 +1,15 @@ +import { z } from 'zod' + +export const formSchema = z.object({ + key: z.string(), + secret: z.string(), + symbol: z.string(), + qtyPercent: z.number(), + qtyDecimalPoint: z.number(), + leverage: z.number().default(1), + takeProfitPercent: z.number(), + stopLossPercent: z.number(), + demo: z.boolean().default(true), +}) + +export type FormSchema = typeof formSchema diff --git a/src/routes/webhook/tradingview/tradingstop/+server.ts b/src/routes/webhook/tradingview/tradingstop/+server.ts new file mode 100644 index 0000000..1acc880 --- /dev/null +++ b/src/routes/webhook/tradingview/tradingstop/+server.ts @@ -0,0 +1,178 @@ +import { db } from '$lib/db/index.js' +import { bybit_logs } from '$lib/db/schema.js' +import { json } from '@sveltejs/kit' +import { RestClientV5 } from 'bybit-api' +import { z } from 'zod' + +export const POST = async ({ locals, request }) => { + const type = request.headers.get('Content-Type') + console.log(type) + if (!type || !type.startsWith('application/json')) { + return new Response() + } + + const body = await request.json() + + console.log('Body:', body) + + const schema = z.object({ + key: z.string(), + secret: z.string(), + symbol: z.string(), + entryPrice: z.string(), + qtyPercent: z.string(), + qtyDecimalPoint: z.string(), + leverage: z.string().default('1'), + takeProfitPercent: z.string(), + stopLossPercent: z.string(), + demo: z.enum(['true', 'false']), + }) + + const form = schema.safeParse(body) + + if (!form.success) { + console.log(form.error) + return json({}, { status: 400 }) + } + + const { key, secret, symbol, demo } = form.data + + try { + const client = new RestClientV5({ + key: key, + secret: secret, + demoTrading: demo === 'true', + }) + + client.switchPositionMode({ + category: 'linear', + mode: 3, + }) + + const wallet = await client.getWalletBalance({ + accountType: 'UNIFIED', + coin: 'USDT', + }) + + console.log( + 'Available Balance:', + wallet.result.list[0].totalAvailableBalance, + ) + + console.log('calculate:') + console.log('Entry Price:', form.data.entryPrice) + console.log('Qty Percent:', form.data.qtyPercent) + console.log( + 'Wallet Balance:', + wallet.result.list[0].totalAvailableBalance, + ) + + const [price, decimals] = form.data.entryPrice.split('.') + console.log('Decimals', decimals) + console.log('Decimals Length', decimals?.length || 0) + const decimalLength = decimals?.length || 0 + + const qty = ( + (Number(wallet.result.list[0].totalAvailableBalance) * + ((Number(form.data.qtyPercent) / 100) * + Number(form.data.leverage || '1'))) / + Number(form.data.entryPrice) + ).toFixed(Number(form.data.qtyDecimalPoint)) + + const takeProfitBuy = ( + Number(form.data.entryPrice) * + (1 + Number(form.data.takeProfitPercent) / 100) + ).toFixed(decimalLength) + const stopLossBuy = ( + Number(form.data.entryPrice) * + (1 - Number(form.data.stopLossPercent) / 100) + ).toFixed(decimalLength) + + const takeProfitSell = ( + Number(form.data.entryPrice) * + (1 - Number(form.data.takeProfitPercent) / 100) + ).toFixed(decimalLength) + const stopLossSell = ( + Number(form.data.entryPrice) * + (1 + Number(form.data.stopLossPercent) / 100) + ).toFixed(decimalLength) + + console.log({ + qty, + takeProfitBuy, + stopLossBuy, + takeProfitSell, + stopLossSell, + }) + const orders = await Promise.all([ + await client.submitOrder({ + category: 'linear', + symbol, + side: 'Buy', + orderType: 'Market', + qty, + takeProfit: takeProfitBuy, + stopLoss: stopLossBuy, + isLeverage: form.data.leverage ? 1 : 0, + positionIdx: 1, + }), + await client.submitOrder({ + category: 'linear', + symbol, + side: 'Sell', + orderType: 'Market', + qty, + takeProfit: takeProfitSell, + stopLoss: stopLossSell, + isLeverage: form.data.leverage ? 1 : 0, + positionIdx: 2, + }), + ]) + + console.log('Orders:', orders) + + const buyOrder = orders[0] + await db.insert(bybit_logs).values({ + status: buyOrder.retCode === 0 ? 'success' : 'failed', + request: form.data, + payload: { + category: 'linear', + symbol, + side: 'Buy', + orderType: 'Market', + qty, + takeProfit: takeProfitBuy, + stopLoss: stopLossBuy, + isLeverage: form.data.leverage ? 1 : 0, + positionIdx: 1, + }, + response: buyOrder, + createdAt: Date.now(), + }) + + const sellOrder = orders[1] + await db.insert(bybit_logs).values({ + status: sellOrder.retCode === 0 ? 'success' : 'failed', + request: form.data, + payload: { + category: 'linear', + symbol, + side: 'Sell', + orderType: 'Market', + qty, + takeProfit: takeProfitSell, + stopLoss: stopLossSell, + isLeverage: form.data.leverage ? 1 : 0, + positionIdx: 1, + }, + response: sellOrder, + createdAt: Date.now(), + }) + + return new Response() + } catch (error) { + console.log('Error', error) + } finally { + return new Response() + } +}