Skip to content

Commit bcb73c7

Browse files
committed
feat: generate symbols ordering
1 parent a531603 commit bcb73c7

7 files changed

Lines changed: 341 additions & 257 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"format": "biome format --write .",
1212
"generate-client-factory": "tsx --env-file .env scripts/generate-client-factory.ts",
1313
"generate-routes": "tsr generate",
14-
"generate-tradingview-symbols": "tsx --env-file .env scripts/generate-tradingview-symbols.ts"
14+
"generate-tradingview-symbols": "tsx --env-file .env scripts/generate-tradingview-symbols/index.ts"
1515
},
1616
"dependencies": {
1717
"@base-ui/react": "^1.1.0",

scripts/generate-tradingview-symbols.ts renamed to scripts/generate-tradingview-symbols/index.ts

Lines changed: 21 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -7,69 +7,19 @@ import {
77
HttpClientRequest,
88
HttpClientResponse,
99
} from "@effect/platform";
10+
import { Array as _Array, Config, Effect, Layer, Logger } from "effect";
1011
import {
11-
Array as _Array,
12-
Config,
13-
Data,
14-
Effect,
15-
Layer,
16-
Logger,
17-
Order,
18-
Schema,
19-
} from "effect";
20-
21-
const DEFAULT_LIMIT = 50;
22-
23-
const exchangePriorityRecord: Record<string, number> = {
24-
Coinbase: 0,
25-
Binance: 1,
26-
CRYPTOCAP: 2,
27-
"Crypto.com": 3,
28-
};
29-
30-
// -----------------------------------------------------------------------------
31-
// Schemas
32-
// -----------------------------------------------------------------------------
33-
const TokenDto = Schema.Struct({ symbol: Schema.String });
34-
35-
const ProviderDto = Schema.Struct({
36-
id: Schema.String,
37-
name: Schema.optionalWith(Schema.String, { nullable: true }),
38-
});
39-
40-
const MarketDto = Schema.Struct({
41-
id: Schema.String,
42-
providerId: Schema.String,
43-
baseAsset: TokenDto,
44-
quoteAsset: TokenDto,
45-
});
46-
47-
const MarketsResponse = Schema.Struct({
48-
total: Schema.Number,
49-
offset: Schema.Number,
50-
limit: Schema.Number,
51-
items: Schema.optionalWith(Schema.Array(MarketDto), { nullable: true }),
52-
});
53-
54-
const ProvidersResponse = Schema.Array(ProviderDto);
55-
56-
const TradingViewSearchResponse = Schema.Struct({
57-
symbols: Schema.Array(
58-
Schema.Struct({
59-
symbol: Schema.String,
60-
exchange: Schema.String,
61-
provider_id: Schema.String,
62-
}),
63-
),
64-
});
65-
66-
// -----------------------------------------------------------------------------
67-
// Error Types
68-
// -----------------------------------------------------------------------------
69-
class HttpError extends Data.TaggedError("HttpError")<{
70-
message: string;
71-
cause?: unknown;
72-
}> {}
12+
type BaseSymbolSchema,
13+
byProviderAndCurrency,
14+
compareSymbolsFromBaseSymbol,
15+
DEFAULT_LIMIT,
16+
HttpError,
17+
MarketsResponse,
18+
makeResult,
19+
normalizeSymbol,
20+
ProvidersResponse,
21+
TradingViewSearchResponse,
22+
} from "scripts/generate-tradingview-symbols/utils";
7323

7424
// -----------------------------------------------------------------------------
7525
// HttpClient Services
@@ -123,11 +73,6 @@ class TradingViewClient extends Effect.Service<TradingViewClient>()(
12373
// -----------------------------------------------------------------------------
12474
// API Functions
12575
// -----------------------------------------------------------------------------
126-
const normalizeSymbol = (symbol: string) =>
127-
symbol
128-
.replace(/<\/?em>/g, "")
129-
.trim()
130-
.toUpperCase();
13176

13277
const getProviders = Effect.gen(function* () {
13378
const perpsClient = yield* PerpsClient;
@@ -226,50 +171,23 @@ const searchTradingViewSymbol = (
226171
),
227172
);
228173

229-
const CheckTradingViewSymbolResult = Schema.Union(
230-
Schema.Struct({
231-
status: Schema.Literal("match"),
232-
perpsSymbol: Schema.String,
233-
tradingViewSymbol: Schema.String,
234-
providerId: Schema.String,
235-
}),
236-
Schema.Struct({
237-
status: Schema.Literal("noMatch"),
238-
perpsSymbol: Schema.String,
239-
}),
240-
Schema.Struct({
241-
status: Schema.Literal("error"),
242-
perpsSymbol: Schema.String,
243-
}),
244-
);
245-
246-
const makeResult = Schema.decodeSync(CheckTradingViewSymbolResult);
247-
248-
const checkTradingViewSymbol = Effect.fn(function* (baseSymbol: string) {
174+
const checkTradingViewSymbol = Effect.fn(function* (
175+
baseSymbol: typeof BaseSymbolSchema.Type,
176+
) {
249177
const tvClient = yield* TradingViewClient;
250178

251179
const normalizedBase = normalizeSymbol(baseSymbol);
252180

253181
return yield* searchTradingViewSymbol(tvClient, normalizedBase).pipe(
254182
Effect.map((response) => {
255-
const results = _Array.sort(
256-
response.symbols,
257-
Order.mapInput(
258-
Order.number,
259-
(v: (typeof response.symbols)[number]) =>
260-
exchangePriorityRecord[v.exchange] ?? 999,
261-
),
262-
);
183+
const results = _Array.sort(response.symbols, byProviderAndCurrency);
184+
185+
const compareSymbols = compareSymbolsFromBaseSymbol(normalizedBase);
263186

264187
const matchedResult = _Array.findFirst(results, (result) => {
265188
const resultSymbol = normalizeSymbol(result.symbol);
266189

267-
return [
268-
normalizedBase,
269-
`${normalizedBase}USD`,
270-
`${normalizedBase}USDC`,
271-
`${normalizedBase}USDT`,
272-
].some((symbol) => resultSymbol === symbol);
190+
return _Array.some(compareSymbols, (symbol) => resultSymbol === symbol);
273191
});
274192

275193
if (matchedResult._tag === "Some") {
@@ -324,11 +242,12 @@ const program = Effect.gen(function* () {
324242

325243
const nonMatched = results.filter((result) => result.status !== "match");
326244

327-
yield* Effect.log("Non matched: ", JSON.stringify(nonMatched, null, 2));
245+
yield* Effect.log("Not matched: ", JSON.stringify(nonMatched, null, 2));
328246

329247
const path = join(
330248
dirname(fileURLToPath(import.meta.url)),
331249
"..",
250+
"..",
332251
"src",
333252
"assets",
334253
"tradingview-symbols.json",
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { Data, Option, Order, Record, Schema } from "effect";
2+
3+
export const DEFAULT_LIMIT = 50;
4+
5+
// -----------------------------------------------------------------------------
6+
// Schemas
7+
// -----------------------------------------------------------------------------
8+
export const ProviderId = Schema.String.pipe(Schema.brand("ProviderId"));
9+
10+
export const CompareCurrencySchema = Schema.Literal("USD", "USDC", "USDT");
11+
12+
export const currencyPriorityRecord: Record<
13+
typeof CompareCurrencySchema.Type,
14+
number
15+
> = {
16+
USD: 0,
17+
USDC: 1,
18+
USDT: 2,
19+
};
20+
21+
export const BaseSymbolSchema = Schema.String.pipe(Schema.brand("BaseSymbol"));
22+
23+
export const TokenDto = Schema.Struct({ symbol: BaseSymbolSchema });
24+
25+
export const ProviderDto = Schema.Struct({
26+
id: Schema.String,
27+
name: Schema.optionalWith(Schema.String, { nullable: true }),
28+
});
29+
30+
export const MarketDto = Schema.Struct({
31+
id: Schema.String,
32+
providerId: Schema.String,
33+
baseAsset: TokenDto,
34+
quoteAsset: TokenDto,
35+
});
36+
37+
export const MarketsResponse = Schema.Struct({
38+
total: Schema.Number,
39+
offset: Schema.Number,
40+
limit: Schema.Number,
41+
items: Schema.optionalWith(Schema.Array(MarketDto), { nullable: true }),
42+
});
43+
44+
export const ProvidersResponse = Schema.Array(ProviderDto);
45+
46+
export const TradingViewSearchResponse = Schema.Struct({
47+
symbols: Schema.Array(
48+
Schema.Struct({
49+
symbol: Schema.String, // e.g. ETHUSD
50+
exchange: Schema.String,
51+
provider_id: ProviderId,
52+
}),
53+
),
54+
});
55+
56+
const CheckTradingViewSymbolResult = Schema.Union(
57+
Schema.Struct({
58+
status: Schema.Literal("match"),
59+
perpsSymbol: Schema.String,
60+
tradingViewSymbol: Schema.String,
61+
providerId: Schema.String,
62+
}),
63+
Schema.Struct({
64+
status: Schema.Literal("noMatch"),
65+
perpsSymbol: Schema.String,
66+
}),
67+
Schema.Struct({
68+
status: Schema.Literal("error"),
69+
perpsSymbol: Schema.String,
70+
}),
71+
);
72+
73+
// -----------------------------------------------------------------------------
74+
// Error Types
75+
// -----------------------------------------------------------------------------
76+
export class HttpError extends Data.TaggedError("HttpError")<{
77+
message: string;
78+
cause?: unknown;
79+
}> {}
80+
81+
// -----------------------------------------------------------------------------
82+
// Utilities
83+
// -----------------------------------------------------------------------------
84+
85+
export const exchangePriorityRecord: Record<typeof ProviderId.Type, number> = {
86+
[ProviderId.make("coinbase")]: 0,
87+
[ProviderId.make("binance")]: 1,
88+
[ProviderId.make("cryptocom")]: 2,
89+
[ProviderId.make("kraken")]: 3,
90+
[ProviderId.make("okx")]: 4,
91+
[ProviderId.make("cryptocap")]: 5,
92+
};
93+
94+
export const normalizeSymbol = <T extends string>(symbol: T): T =>
95+
symbol
96+
.replace(/<\/?em>/g, "")
97+
.trim()
98+
.toUpperCase() as T;
99+
100+
export const makeResult = Schema.decodeSync(CheckTradingViewSymbolResult);
101+
102+
const byCurrency = Order.mapInput(
103+
Order.number,
104+
(val: (typeof TradingViewSearchResponse.Type)["symbols"][number]) => {
105+
const compareSymbol = Schema.decodeUnknownOption(
106+
Schema.TemplateLiteralParser(
107+
Schema.String,
108+
Schema.Literal("USD", "USDC", "USDT"),
109+
),
110+
)(val.symbol).pipe(
111+
Option.map((v) => v[1]),
112+
Option.getOrElse(() => null),
113+
);
114+
115+
if (!compareSymbol) return 999;
116+
117+
return Record.get(currencyPriorityRecord, compareSymbol).pipe(
118+
Option.getOrElse(() => 999),
119+
);
120+
},
121+
);
122+
123+
const byProvider = Order.mapInput(
124+
Order.number,
125+
(val: (typeof TradingViewSearchResponse.Type)["symbols"][number]) =>
126+
Record.get(exchangePriorityRecord, val.provider_id).pipe(
127+
Option.getOrElse(() => 999),
128+
),
129+
);
130+
131+
export const byProviderAndCurrency = Order.combine(byCurrency, byProvider);
132+
133+
export const compareSymbolsFromBaseSymbol = (
134+
baseSymbol: typeof BaseSymbolSchema.Type,
135+
) => {
136+
const compareSymbols = Schema.decodeSync(
137+
Schema.Array(
138+
Schema.TemplateLiteral(BaseSymbolSchema, CompareCurrencySchema),
139+
),
140+
)(
141+
CompareCurrencySchema.literals.map(
142+
(currency) => `${baseSymbol}${currency}` as const,
143+
),
144+
);
145+
146+
return [...compareSymbols, baseSymbol];
147+
};

0 commit comments

Comments
 (0)