diff --git a/src/lib/db/schema.ts b/src/lib/db/schema.ts
index a7ec32a..aa285fa 100644
--- a/src/lib/db/schema.ts
+++ b/src/lib/db/schema.ts
@@ -12,3 +12,14 @@ export const bybit_logs = pgTable('bybit_logs', (t) => ({
response: t.json('response').notNull(),
createdAt: t.bigint('created_at', { mode: 'number' }).notNull(),
}))
+
+export const stacking_config = pgTable('stacking_config', (t) => ({
+ id: t.text('id').primaryKey(),
+ threshhold: t.bigint('threshold', { mode: 'number' }).notNull(),
+ stackRate: t.integer('stack_rate').notNull(),
+}))
+
+export const current_stack = pgTable('current_stack', (t) => ({
+ id: t.text('id').primaryKey(),
+ stack: t.integer('stack').notNull(),
+}))
diff --git a/src/lib/nanoid.ts b/src/lib/nanoid.ts
new file mode 100644
index 0000000..63c4615
--- /dev/null
+++ b/src/lib/nanoid.ts
@@ -0,0 +1,14 @@
+// nanoid code
+const urlAlphabet =
+ 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'
+export const nanoid = (size = 32) => {
+ let id = ''
+ let bytes = crypto.getRandomValues(new Uint8Array((size |= 0)))
+ while (size--) {
+ // Using the bitwise AND operator to "cap" the value of
+ // the random byte from 255 to 63, in that way we can make sure
+ // that the value will be a valid index for the "chars" string.
+ id += urlAlphabet[bytes[size] & 63]
+ }
+ return id
+}
diff --git a/src/routes/api/bybit/stacking/+server.ts b/src/routes/api/bybit/stacking/+server.ts
new file mode 100644
index 0000000..de41cd6
--- /dev/null
+++ b/src/routes/api/bybit/stacking/+server.ts
@@ -0,0 +1,40 @@
+import { db } from '$lib/db/index.js'
+import { stacking_config } from '$lib/db/schema.js'
+import { nanoid } from '$lib/nanoid.js'
+import { eq } from 'drizzle-orm'
+import { z } from 'zod'
+
+export const POST = async ({ request }) => {
+ const body = await request.json()
+
+ const data = z
+ .object({
+ threshhold: z.number().min(0),
+ stackRate: z.number().min(0),
+ })
+ .safeParse(body)
+
+ if (!data.success) {
+ return new Response()
+ }
+
+ const existing = await db.query.stacking_config.findFirst()
+
+ if (!existing) {
+ await db.insert(stacking_config).values({
+ id: nanoid(),
+ stackRate: data.data.stackRate,
+ threshhold: data.data.threshhold,
+ })
+ } else {
+ await db
+ .update(stacking_config)
+ .set({
+ stackRate: data.data.stackRate,
+ threshhold: data.data.threshhold,
+ })
+ .where(eq(stacking_config.id, existing.id))
+ }
+
+ return new Response()
+}
diff --git a/src/routes/dashboard/(components)/app-sidebar.svelte b/src/routes/dashboard/(components)/app-sidebar.svelte
index e5399de..d4bb572 100644
--- a/src/routes/dashboard/(components)/app-sidebar.svelte
+++ b/src/routes/dashboard/(components)/app-sidebar.svelte
@@ -24,6 +24,12 @@
icon: HomeIcon,
isActive: page.url.pathname === `/dashboard/crypto-data`,
},
+ {
+ title: 'Stack',
+ url: `/dashboard/stack`,
+ icon: CoinsIcon,
+ isActive: page.url.pathname === `/dashboard/stack`,
+ },
{
title: 'Download',
url: `/dashboard/download`,
diff --git a/src/routes/dashboard/stack/+page.server.ts b/src/routes/dashboard/stack/+page.server.ts
new file mode 100644
index 0000000..5b96d07
--- /dev/null
+++ b/src/routes/dashboard/stack/+page.server.ts
@@ -0,0 +1,8 @@
+import { db } from '$lib/db'
+
+export const load = async () => {
+ const config = await db.query.stacking_config.findFirst()
+ return {
+ config,
+ }
+}
diff --git a/src/routes/dashboard/stack/+page.svelte b/src/routes/dashboard/stack/+page.svelte
new file mode 100644
index 0000000..c5db5a5
--- /dev/null
+++ b/src/routes/dashboard/stack/+page.svelte
@@ -0,0 +1,38 @@
+
+
+
+
+
+ Threshold
+
+
+
+ Stack Rate (+%):
+
+
+
+
+
diff --git a/src/routes/webhook/tradingview/+server.ts b/src/routes/webhook/tradingview/+server.ts
index 4244745..7cb79c2 100644
--- a/src/routes/webhook/tradingview/+server.ts
+++ b/src/routes/webhook/tradingview/+server.ts
@@ -1,7 +1,13 @@
import { db } from '$lib/db/index.js'
-import { bybit_logs } from '$lib/db/schema.js'
+import {
+ bybit_logs,
+ current_stack,
+ stacking_config,
+} from '$lib/db/schema.js'
+import { nanoid } from '$lib/nanoid.js'
import { json } from '@sveltejs/kit'
import { RestClientV5 } from 'bybit-api'
+import { eq } from 'drizzle-orm'
import { z } from 'zod'
export const POST = async ({ locals, request }) => {
@@ -11,6 +17,8 @@ export const POST = async ({ locals, request }) => {
return new Response()
}
+ const requestID = nanoid(6)
+
const body = await request.json()
console.log('Body:', body)
@@ -27,6 +35,18 @@ export const POST = async ({ locals, request }) => {
stopLoss: z.string().optional(),
hedge: z.enum(['true', 'false']).optional(),
})
+ const compoundingSchema = z.object({
+ type: z.literal('Compound'),
+ key: z.string(),
+ secret: z.string(),
+ symbol: z.string(),
+ side: z.enum(['Buy', 'Sell']),
+ qty: z.string(),
+ leverage: z.string().optional(),
+ takeProfit: z.string().optional(),
+ stopLoss: z.string().optional(),
+ hedge: z.enum(['true', 'false']).optional(),
+ })
const percentSchema = z.object({
type: z.literal('Percent'),
key: z.string(),
@@ -51,7 +71,12 @@ export const POST = async ({ locals, request }) => {
side: z.enum(['Buy', 'Sell']).optional(),
})
const form = z
- .union([flatSchema, percentSchema, closePositionSchema])
+ .union([
+ flatSchema,
+ percentSchema,
+ closePositionSchema,
+ compoundingSchema,
+ ])
.safeParse(body)
if (!form.success) {
@@ -149,7 +174,7 @@ export const POST = async ({ locals, request }) => {
).toFixed(decimalLength)
}
positionIdx = form.data.side === 'Buy' ? 1 : 2
- } else {
+ } else if (form.data.type === 'Close Position') {
const position = await client.getPositionInfo({
category: 'linear',
symbol: symbol,
@@ -192,6 +217,21 @@ export const POST = async ({ locals, request }) => {
: ('Buy' as const)
qty = position.result.list[0].size
}
+ } else {
+ const stacking_config =
+ await db.query.stacking_config.findFirst()
+
+ if (!stacking_config) {
+ return new Response()
+ }
+
+ qty = (
+ Number(form.data.qty) * Number(form.data.leverage || '1')
+ ).toString()
+ takeProfit = form.data.takeProfit
+ stopLoss = form.data.stopLoss
+ side = form.data.side
+ positionIdx = form.data.side === 'Buy' ? 1 : 2
}
console.log({ qty, takeProfit, stopLoss })
@@ -209,6 +249,64 @@ export const POST = async ({ locals, request }) => {
console.log('Order:', order)
+ if (form.data.type === 'Close Position') {
+ console.log('Close Position, checking for compound...')
+
+ const wallet = await client.getTransferableAmount({
+ coinName: 'USDT',
+ })
+
+ const balance = wallet.result.availableWithdrawal
+
+ console.log(`[REQUEST_${requestID}][COMPOUND]`)
+ console.log(
+ `[REQUEST_${requestID}][COMPOUND][AVAILABLE_BALANCE]`,
+ balance,
+ )
+
+ const config = await db.query.stacking_config.findFirst()
+
+ if (config) {
+ console.log(
+ `[REQUEST_${requestID}][COMPOUND][THRESHOLD]`,
+ config.threshhold,
+ )
+
+ if (parseFloat(balance) >= config.threshhold * 2) {
+ const withdrawAmount =
+ parseFloat(balance) - config.threshhold
+
+ console.log(
+ `[REQUEST_${requestID}][COMPOUNT][WITHDRAW_AMOUNT]`,
+ withdrawAmount,
+ )
+
+ const response = await client.createInternalTransfer(
+ crypto.randomUUID(),
+ 'USDT',
+ withdrawAmount.toString(),
+ 'UNIFIED',
+ 'FUND',
+ )
+
+ console.log(
+ `[REQUEST_${requestID}][COMPOUNT][WITHDRAW_RESPONSE]`,
+ response,
+ )
+
+ const stack = await db.query.current_stack.findFirst()
+ if (stack) {
+ await db
+ .update(current_stack)
+ .set({
+ stack: stack.stack + config.stackRate,
+ })
+ .where(eq(current_stack.id, stack.id))
+ }
+ }
+ }
+ }
+
await db.insert(bybit_logs).values({
status: order.retCode === 0 ? 'success' : 'failed',
request: form.data,