add bingx api
parent
a4d3c33512
commit
0f4630a534
@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div bind:this={ref} class={cn("p-6", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
|
||||
</script>
|
||||
|
||||
<p bind:this={ref} class={cn("text-muted-foreground text-sm", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</p>
|
||||
@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div bind:this={ref} class={cn("flex items-center p-6 pt-0", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div bind:this={ref} class={cn("flex flex-col space-y-1.5 p-6 pb-0", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
level = 3,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
||||
level?: 1 | 2 | 3 | 4 | 5 | 6;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
role="heading"
|
||||
aria-level={level}
|
||||
bind:this={ref}
|
||||
class={cn("text-2xl font-semibold leading-none tracking-tight", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { WithElementRef } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
class={cn("bg-card text-card-foreground rounded-lg border shadow-sm", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@ -0,0 +1,22 @@
|
||||
import Root from "./card.svelte";
|
||||
import Content from "./card-content.svelte";
|
||||
import Description from "./card-description.svelte";
|
||||
import Footer from "./card-footer.svelte";
|
||||
import Header from "./card-header.svelte";
|
||||
import Title from "./card-title.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Description,
|
||||
Footer,
|
||||
Header,
|
||||
Title,
|
||||
//
|
||||
Root as Card,
|
||||
Content as CardContent,
|
||||
Description as CardDescription,
|
||||
Footer as CardFooter,
|
||||
Header as CardHeader,
|
||||
Title as CardTitle,
|
||||
};
|
||||
@ -0,0 +1,10 @@
|
||||
import Scrollbar from "./scroll-area-scrollbar.svelte";
|
||||
import Root from "./scroll-area.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Scrollbar,
|
||||
//,
|
||||
Root as ScrollArea,
|
||||
Scrollbar as ScrollAreaScrollbar,
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { ScrollArea as ScrollAreaPrimitive, type WithoutChild } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
orientation = "vertical",
|
||||
children,
|
||||
...restProps
|
||||
}: WithoutChild<ScrollAreaPrimitive.ScrollbarProps> = $props();
|
||||
</script>
|
||||
|
||||
<ScrollAreaPrimitive.Scrollbar
|
||||
bind:ref
|
||||
{orientation}
|
||||
class={cn(
|
||||
"flex touch-none select-none transition-colors",
|
||||
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-px",
|
||||
orientation === "horizontal" && "h-2.5 w-full border-t border-t-transparent p-px",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
<ScrollAreaPrimitive.Thumb
|
||||
class={cn("bg-border relative rounded-full", orientation === "vertical" && "flex-1")}
|
||||
/>
|
||||
</ScrollAreaPrimitive.Scrollbar>
|
||||
@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { ScrollArea as ScrollAreaPrimitive, type WithoutChild } from "bits-ui";
|
||||
import { Scrollbar } from "./index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
orientation = "vertical",
|
||||
scrollbarXClasses = "",
|
||||
scrollbarYClasses = "",
|
||||
children,
|
||||
...restProps
|
||||
}: WithoutChild<ScrollAreaPrimitive.RootProps> & {
|
||||
orientation?: "vertical" | "horizontal" | "both" | undefined;
|
||||
scrollbarXClasses?: string | undefined;
|
||||
scrollbarYClasses?: string | undefined;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<ScrollAreaPrimitive.Root bind:ref {...restProps} class={cn("relative overflow-hidden", className)}>
|
||||
<ScrollAreaPrimitive.Viewport class="h-full w-full rounded-[inherit]">
|
||||
{@render children?.()}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
{#if orientation === "vertical" || orientation === "both"}
|
||||
<Scrollbar orientation="vertical" class={scrollbarYClasses} />
|
||||
{/if}
|
||||
{#if orientation === "horizontal" || orientation === "both"}
|
||||
<Scrollbar orientation="horizontal" class={scrollbarXClasses} />
|
||||
{/if}
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
@ -0,0 +1,18 @@
|
||||
import { Tabs as TabsPrimitive } from "bits-ui";
|
||||
import Content from "./tabs-content.svelte";
|
||||
import List from "./tabs-list.svelte";
|
||||
import Trigger from "./tabs-trigger.svelte";
|
||||
|
||||
const Root = TabsPrimitive.Root;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
List,
|
||||
Trigger,
|
||||
//
|
||||
Root as Tabs,
|
||||
Content as TabsContent,
|
||||
List as TabsList,
|
||||
Trigger as TabsTrigger,
|
||||
};
|
||||
@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { Tabs as TabsPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: TabsPrimitive.ContentProps = $props();
|
||||
</script>
|
||||
|
||||
<TabsPrimitive.Content
|
||||
bind:ref
|
||||
class={cn(
|
||||
"ring-offset-background focus-visible:ring-ring mt-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { Tabs as TabsPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: TabsPrimitive.ListProps = $props();
|
||||
</script>
|
||||
|
||||
<TabsPrimitive.List
|
||||
bind:ref
|
||||
class={cn(
|
||||
"bg-muted text-muted-foreground inline-flex h-10 items-center justify-center rounded-md p-1",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { Tabs as TabsPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: TabsPrimitive.TriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<TabsPrimitive.Trigger
|
||||
bind:ref
|
||||
class={cn(
|
||||
"ring-offset-background focus-visible:ring-ring data-[state=active]:bg-background data-[state=active]:text-foreground inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
@ -0,0 +1,289 @@
|
||||
<script lang="ts">
|
||||
import * as Form from '$lib/components/ui/form'
|
||||
import * as Select from '$lib/components/ui/select'
|
||||
import { Input } from '$lib/components/ui/input'
|
||||
import { defaults, superForm } from 'sveltekit-superforms'
|
||||
import { zod } from 'sveltekit-superforms/adapters'
|
||||
import { bingxAPISchema, intervals } from './schema'
|
||||
import { CalendarIcon } from 'lucide-svelte'
|
||||
import { Calendar } from '$lib/components/ui/calendar/index.js'
|
||||
import * as Popover from '$lib/components/ui/popover/index.js'
|
||||
import {
|
||||
CalendarDate,
|
||||
DateFormatter,
|
||||
type DateValue,
|
||||
getLocalTimeZone,
|
||||
parseDate,
|
||||
today,
|
||||
} from '@internationalized/date'
|
||||
import { cn } from '$lib/utils.js'
|
||||
|
||||
import { buttonVariants } from '$lib/components/ui/button'
|
||||
import axios from 'axios'
|
||||
|
||||
const apiEndpoint =
|
||||
'https://open-api.bingx.io/openApi/swap/v3/quote/klines'
|
||||
|
||||
const form = superForm(
|
||||
defaults(zod(bingxAPISchema), {
|
||||
defaults: {
|
||||
limit: 500,
|
||||
symbol: 'BTC-USDT',
|
||||
interval: '1h',
|
||||
start: '2024-11-15',
|
||||
startTime: '00:00',
|
||||
end: '2024-11-15',
|
||||
endTime: '00:00',
|
||||
},
|
||||
}),
|
||||
{
|
||||
resetForm: false,
|
||||
SPA: true,
|
||||
validators: zod(bingxAPISchema),
|
||||
onUpdated: async ({ form }) => {
|
||||
if (!form.valid) return
|
||||
|
||||
console.log(form.data)
|
||||
// Form validation
|
||||
const url = new URL(apiEndpoint)
|
||||
url.searchParams.set('symbol', form.data.symbol)
|
||||
url.searchParams.set('interval', form.data.interval)
|
||||
|
||||
const start = new Date(
|
||||
form.data.start + ' ' + form.data.startTime + ':00',
|
||||
)
|
||||
url.searchParams.set('start', start.getTime().toString())
|
||||
|
||||
const end = new Date(
|
||||
form.data.end + ' ' + form.data.endTime + ':00',
|
||||
)
|
||||
|
||||
url.searchParams.set('end', end.getTime().toString())
|
||||
|
||||
if (form.data.limit)
|
||||
url.searchParams.set('limit', form.data.limit.toString())
|
||||
|
||||
url.searchParams.set(
|
||||
'timestamp',
|
||||
new Date().getTime().toString(),
|
||||
)
|
||||
|
||||
const response = await fetch('/api/bingx', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ url }),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
const list = data.data as {
|
||||
open: string
|
||||
close: string
|
||||
high: string
|
||||
low: string
|
||||
volume: string
|
||||
time: number
|
||||
}[]
|
||||
|
||||
let csvContent =
|
||||
'data:text/csv;charset=utf-8,' +
|
||||
'openPrice,closePrice,highPrice,lowPrice,volume,startTime\n' +
|
||||
list
|
||||
.map((data) => {
|
||||
let newData = []
|
||||
newData.push(data.open)
|
||||
newData.push(data.close)
|
||||
newData.push(data.high)
|
||||
newData.push(data.low)
|
||||
newData.push(data.volume)
|
||||
newData.push(
|
||||
new Date(data.time)
|
||||
.toLocaleString()
|
||||
.replace(',', ' |'),
|
||||
)
|
||||
return newData
|
||||
})
|
||||
.map((e: any) => e.join(','))
|
||||
.join('\n')
|
||||
|
||||
var encodedUri = encodeURI(csvContent)
|
||||
var link = document.createElement('a')
|
||||
link.setAttribute('href', encodedUri)
|
||||
link.setAttribute('download', 'data.csv')
|
||||
document.body.appendChild(link)
|
||||
|
||||
link.click()
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const { form: formData, enhance } = form
|
||||
|
||||
const df = new DateFormatter('en-US', {
|
||||
dateStyle: 'long',
|
||||
})
|
||||
|
||||
let start = $state<DateValue | undefined>()
|
||||
let end = $state<DateValue | undefined>()
|
||||
|
||||
$effect(() => {
|
||||
start = $formData.start ? parseDate($formData.start) : undefined
|
||||
end = $formData.end ? parseDate($formData.end) : undefined
|
||||
})
|
||||
|
||||
let placeholder = $state<DateValue>(today(getLocalTimeZone()))
|
||||
</script>
|
||||
|
||||
<form method="POST" use:enhance>
|
||||
<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="limit">
|
||||
<Form.Control>
|
||||
{#snippet children({ props })}
|
||||
<Form.Label>Limit</Form.Label>
|
||||
<Input
|
||||
{...props}
|
||||
bind:value={$formData.limit}
|
||||
type="number" />
|
||||
{/snippet}
|
||||
</Form.Control>
|
||||
<Form.Description>Limit</Form.Description>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
<Form.Field {form} name="interval">
|
||||
<Form.Control>
|
||||
{#snippet children({ props })}
|
||||
<Form.Label>Interval</Form.Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
bind:value={$formData.interval}
|
||||
name={props.name}>
|
||||
<Select.Trigger {...props}>
|
||||
{$formData.interval ?? 'Select a interval'}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each intervals as interval}
|
||||
<Select.Item value={interval} label={interval} />
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
{/snippet}
|
||||
</Form.Control>
|
||||
<Form.Description>Interval</Form.Description>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
<Form.Field {form} name="start" class="flex flex-col">
|
||||
<Form.Control>
|
||||
{#snippet children({ props })}
|
||||
<Form.Label>Start Date</Form.Label>
|
||||
<Popover.Root>
|
||||
<Popover.Trigger
|
||||
{...props}
|
||||
class={cn(
|
||||
buttonVariants({ variant: 'outline' }),
|
||||
'w-[280px] justify-start pl-4 text-left font-normal',
|
||||
!start && 'text-muted-foreground',
|
||||
)}>
|
||||
{start
|
||||
? df.format(start.toDate(getLocalTimeZone()))
|
||||
: 'Pick a date'}
|
||||
<CalendarIcon class="ml-auto size-4 opacity-50" />
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class="w-auto p-0" side="top">
|
||||
<Calendar
|
||||
type="single"
|
||||
value={start as DateValue}
|
||||
bind:placeholder
|
||||
minValue={new CalendarDate(1900, 1, 1)}
|
||||
maxValue={today(getLocalTimeZone())}
|
||||
calendarLabel="Date of birth"
|
||||
onValueChange={(v) => {
|
||||
if (v) {
|
||||
$formData.start = v.toString()
|
||||
} else {
|
||||
$formData.start = ''
|
||||
}
|
||||
}} />
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
<Form.Description>Start Date</Form.Description>
|
||||
<Form.FieldErrors />
|
||||
<input hidden value={$formData.start} name={props.name} />
|
||||
{/snippet}
|
||||
</Form.Control>
|
||||
</Form.Field>
|
||||
<Form.Field {form} name="startTime">
|
||||
<Form.Control>
|
||||
{#snippet children({ props })}
|
||||
<Form.Label>Start Time</Form.Label>
|
||||
<Input
|
||||
{...props}
|
||||
bind:value={$formData.startTime}
|
||||
type="time" />
|
||||
{/snippet}
|
||||
</Form.Control>
|
||||
<Form.Description>Start Time</Form.Description>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
<Form.Field {form} name="end" class="flex flex-col">
|
||||
<Form.Control>
|
||||
{#snippet children({ props })}
|
||||
<Form.Label>End Date</Form.Label>
|
||||
<Popover.Root>
|
||||
<Popover.Trigger
|
||||
{...props}
|
||||
class={cn(
|
||||
buttonVariants({ variant: 'outline' }),
|
||||
'w-[280px] justify-start pl-4 text-left font-normal',
|
||||
!end && 'text-muted-foreground',
|
||||
)}>
|
||||
{end
|
||||
? df.format(end.toDate(getLocalTimeZone()))
|
||||
: 'Pick a date'}
|
||||
<CalendarIcon class="ml-auto size-4 opacity-50" />
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class="w-auto p-0" side="top">
|
||||
<Calendar
|
||||
type="single"
|
||||
value={end as DateValue}
|
||||
bind:placeholder
|
||||
minValue={new CalendarDate(1900, 1, 1)}
|
||||
maxValue={today(getLocalTimeZone())}
|
||||
calendarLabel="Date of birth"
|
||||
onValueChange={(v) => {
|
||||
if (v) {
|
||||
$formData.end = v.toString()
|
||||
} else {
|
||||
$formData.end = ''
|
||||
}
|
||||
}} />
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
<Form.Description>End Date</Form.Description>
|
||||
<Form.FieldErrors />
|
||||
<input hidden value={$formData.end} name={props.name} />
|
||||
{/snippet}
|
||||
</Form.Control>
|
||||
</Form.Field>
|
||||
<Form.Field {form} name="endTime">
|
||||
<Form.Control>
|
||||
{#snippet children({ props })}
|
||||
<Form.Label>End Time</Form.Label>
|
||||
<Input
|
||||
{...props}
|
||||
bind:value={$formData.endTime}
|
||||
type="time" />
|
||||
{/snippet}
|
||||
</Form.Control>
|
||||
<Form.Description>End Time</Form.Description>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
<Form.Button>Submit</Form.Button>
|
||||
</form>
|
||||
@ -0,0 +1,46 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export type Interval =
|
||||
| '1m'
|
||||
| '3m'
|
||||
| '5m'
|
||||
| '15m'
|
||||
| '30m'
|
||||
| '1h'
|
||||
| '2h'
|
||||
| '4h'
|
||||
| '6h'
|
||||
| '8h'
|
||||
| '12h'
|
||||
| '1d'
|
||||
| '3d'
|
||||
| '1w'
|
||||
| '1M'
|
||||
|
||||
export const intervals: Interval[] = [
|
||||
'1m',
|
||||
'3m',
|
||||
'5m',
|
||||
'15m',
|
||||
'30m',
|
||||
'1h',
|
||||
'2h',
|
||||
'4h',
|
||||
'6h',
|
||||
'8h',
|
||||
'12h',
|
||||
'1d',
|
||||
'3d',
|
||||
'1w',
|
||||
'1M',
|
||||
]
|
||||
|
||||
export const bingxAPISchema = z.object({
|
||||
symbol: z.string(),
|
||||
interval: z.custom<Interval>(),
|
||||
start: z.string().min(1),
|
||||
startTime: z.string().min(1),
|
||||
end: z.string().min(1),
|
||||
endTime: z.string().min(1),
|
||||
limit: z.number().int().min(1).max(1000).optional(),
|
||||
})
|
||||
@ -0,0 +1,10 @@
|
||||
import { json } from '@sveltejs/kit'
|
||||
import type { RequestHandler } from '../$types'
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
const body = await request.json()
|
||||
|
||||
const response = await fetch(body.url)
|
||||
|
||||
return json(await response.json())
|
||||
}
|
||||
Loading…
Reference in New Issue