add bybit trading stop
parent
dc4cd0a5c8
commit
d3447351e4
@ -0,0 +1,189 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import * as Tabs from '$lib/components/ui/tabs/index.js'
|
||||||
|
import * as Form from '$lib/components/ui/form'
|
||||||
|
import * as Select from '$lib/components/ui/select'
|
||||||
|
import { Input } from '$lib/components/ui/input'
|
||||||
|
import { superForm } from 'sveltekit-superforms'
|
||||||
|
import { zod } from 'sveltekit-superforms/adapters'
|
||||||
|
import { formSchema } from './schema'
|
||||||
|
import { Slider } from '$lib/components/ui/slider/index.js'
|
||||||
|
import { JsonView } from '@zerodevx/svelte-json-view'
|
||||||
|
import copy from 'copy-to-clipboard'
|
||||||
|
import json5 from 'json5'
|
||||||
|
import { toast } from 'svelte-sonner'
|
||||||
|
import { Checkbox } from '$lib/components/ui/checkbox'
|
||||||
|
|
||||||
|
let { data } = $props()
|
||||||
|
|
||||||
|
const form = superForm(data.form, {
|
||||||
|
resetForm: false,
|
||||||
|
SPA: true,
|
||||||
|
validators: zod(formSchema),
|
||||||
|
onSubmit: ({}) => {
|
||||||
|
copy(
|
||||||
|
json5.stringify(
|
||||||
|
{
|
||||||
|
...$formData,
|
||||||
|
entryPrice: '{trigger_entry_value}',
|
||||||
|
qtyPercent: $formData.qtyPercent.toString(),
|
||||||
|
qtyDecimalPoint: $formData.qtyDecimalPoint.toString(),
|
||||||
|
leverage: $formData.leverage.toString(),
|
||||||
|
takeProfitPercent: $formData.takeProfitPercent.toString(),
|
||||||
|
stopLossPercent: $formData.stopLossPercent.toString(),
|
||||||
|
demo: $formData.demo.toString(),
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
toast.success('Copied to Clipboard')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { form: formData, enhance } = form
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-1 flex-col items-center justify-center">
|
||||||
|
<div>
|
||||||
|
<form method="POST" use:enhance class="grid grid-cols-2 gap-4">
|
||||||
|
<Form.Field {form} name="key">
|
||||||
|
<Form.Control>
|
||||||
|
{#snippet children({ props })}
|
||||||
|
<Form.Label>Key</Form.Label>
|
||||||
|
<Input {...props} bind:value={$formData.key} />
|
||||||
|
{/snippet}
|
||||||
|
</Form.Control>
|
||||||
|
<Form.Description>Key</Form.Description>
|
||||||
|
<Form.FieldErrors />
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field {form} name="secret">
|
||||||
|
<Form.Control>
|
||||||
|
{#snippet children({ props })}
|
||||||
|
<Form.Label>Secret</Form.Label>
|
||||||
|
<Input {...props} bind:value={$formData.secret} />
|
||||||
|
{/snippet}
|
||||||
|
</Form.Control>
|
||||||
|
<Form.Description>Secret</Form.Description>
|
||||||
|
<Form.FieldErrors />
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field {form} name="symbol">
|
||||||
|
<Form.Control>
|
||||||
|
{#snippet children({ props })}
|
||||||
|
<Form.Label>Symbol</Form.Label>
|
||||||
|
<Input {...props} bind:value={$formData.symbol} />
|
||||||
|
{/snippet}
|
||||||
|
</Form.Control>
|
||||||
|
<Form.Description>Symbol</Form.Description>
|
||||||
|
<Form.FieldErrors />
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field {form} name="qtyPercent">
|
||||||
|
<Form.Control>
|
||||||
|
{#snippet children({ props })}
|
||||||
|
<Form.Label>Quantity (%)</Form.Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
{...props}
|
||||||
|
step={0.1}
|
||||||
|
bind:value={$formData.qtyPercent} />
|
||||||
|
{/snippet}
|
||||||
|
</Form.Control>
|
||||||
|
<Form.Description>Quantity</Form.Description>
|
||||||
|
<Form.FieldErrors />
|
||||||
|
<Slider
|
||||||
|
type="single"
|
||||||
|
bind:value={$formData.qtyPercent}
|
||||||
|
max={100}
|
||||||
|
step={0.1} />
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field {form} name="qtyDecimalPoint">
|
||||||
|
<Form.Control>
|
||||||
|
{#snippet children({ props })}
|
||||||
|
<Form.Label>Quantity Decimal Point</Form.Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
{...props}
|
||||||
|
step={1}
|
||||||
|
bind:value={$formData.qtyDecimalPoint} />
|
||||||
|
{/snippet}
|
||||||
|
</Form.Control>
|
||||||
|
<Form.Description>Quantity</Form.Description>
|
||||||
|
<Form.FieldErrors />
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field {form} name="leverage">
|
||||||
|
<Form.Control>
|
||||||
|
{#snippet children({ props })}
|
||||||
|
<Form.Label>Leverage</Form.Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
{...props}
|
||||||
|
bind:value={$formData.leverage} />
|
||||||
|
{/snippet}
|
||||||
|
</Form.Control>
|
||||||
|
<Form.Description>Leverage</Form.Description>
|
||||||
|
<Form.FieldErrors />
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field {form} name="takeProfitPercent">
|
||||||
|
<Form.Control>
|
||||||
|
{#snippet children({ props })}
|
||||||
|
<Form.Label>Take Profit (%)</Form.Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
{...props}
|
||||||
|
step={0.1}
|
||||||
|
bind:value={$formData.takeProfitPercent} />
|
||||||
|
{/snippet}
|
||||||
|
</Form.Control>
|
||||||
|
<Form.Description>Take Profit</Form.Description>
|
||||||
|
<Form.FieldErrors />
|
||||||
|
<Slider
|
||||||
|
type="single"
|
||||||
|
bind:value={$formData.takeProfitPercent}
|
||||||
|
max={100}
|
||||||
|
step={0.1} />
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field {form} name="stopLossPercent">
|
||||||
|
<Form.Control>
|
||||||
|
{#snippet children({ props })}
|
||||||
|
<Form.Label>Stop Loss (%)</Form.Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
{...props}
|
||||||
|
step={0.1}
|
||||||
|
bind:value={$formData.stopLossPercent} />
|
||||||
|
{/snippet}
|
||||||
|
</Form.Control>
|
||||||
|
<Form.Description>Stop Loss</Form.Description>
|
||||||
|
<Form.FieldErrors />
|
||||||
|
<Slider
|
||||||
|
type="single"
|
||||||
|
bind:value={$formData.stopLossPercent}
|
||||||
|
max={100}
|
||||||
|
step={0.1} />
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field {form} name="demo">
|
||||||
|
<Form.Control>
|
||||||
|
{#snippet children({ props })}
|
||||||
|
<Form.Label>Demo</Form.Label>
|
||||||
|
<Checkbox {...props} bind:checked={$formData.demo} />
|
||||||
|
{/snippet}
|
||||||
|
</Form.Control>
|
||||||
|
<Form.Description>Demo</Form.Description>
|
||||||
|
<Form.FieldErrors />
|
||||||
|
</Form.Field>
|
||||||
|
|
||||||
|
<Form.Button class="col-span-2">Copy</Form.Button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<JsonView
|
||||||
|
json={{
|
||||||
|
...$formData,
|
||||||
|
entryPrice: '{trigger_entry_value}',
|
||||||
|
qtyPercent: $formData.qtyPercent.toString(),
|
||||||
|
qtyDecimalPoint: $formData.qtyDecimalPoint.toString(),
|
||||||
|
leverage: $formData.leverage.toString(),
|
||||||
|
takeProfitPercent: $formData.takeProfitPercent.toString(),
|
||||||
|
stopLossPercent: $formData.stopLossPercent.toString(),
|
||||||
|
demo: $formData.demo.toString(),
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -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 }
|
||||||
|
}
|
||||||
@ -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
|
||||||
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue