@quantform/create 0.7.26 → 0.7.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -37,7 +37,7 @@ async function addPackageJson() {
37
37
  config.set('scripts', {
38
38
  live: 'qf live app',
39
39
  start: 'qf paper app',
40
- replay: 'qf replay app',
40
+ replay: 'qf replay app -f 2026-04-02 -t 2026-04-03',
41
41
  pull: 'qf pull app'
42
42
  });
43
43
  config.save();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantform/create",
3
- "version": "0.7.26",
3
+ "version": "0.7.28",
4
4
  "license": "MIT",
5
5
  "author": "Mateusz Majchrzak",
6
6
  "description": "Node.js library for building systematic trading strategies in reactive way.",
package/src/index.ts CHANGED
@@ -39,7 +39,7 @@ async function addPackageJson() {
39
39
  config.set('scripts', {
40
40
  live: 'qf live app',
41
41
  start: 'qf paper app',
42
- replay: 'qf replay app',
42
+ replay: 'qf replay app -f 2026-04-02 -t 2026-04-03',
43
43
  pull: 'qf pull app'
44
44
  });
45
45
 
package/template/app.ts CHANGED
@@ -5,15 +5,21 @@ import { sqlite } from '@quantform/sqlite';
5
5
 
6
6
  import { useBinance } from './binance/use-binance';
7
7
 
8
- export function strategy() {
9
- const { watchAggTrade } = useBinance();
10
- const { info } = useLogger('usdc');
8
+ export function triangularArbitrageInefficiency() {
9
+ const { watchTrade } = useBinance();
10
+ const { info } = useLogger('arbitrage');
11
11
 
12
- return combineLatest([watchAggTrade('dogeeur'), watchAggTrade('dogeusdt')]).pipe(
13
- tap(([dogeeur, dogeusdt]) =>
14
- info(`eur/usdt: ${dogeusdt.payload.price.div(dogeeur.payload.price).toFixed(4)}`)
15
- )
12
+ return combineLatest([
13
+ watchTrade('btcusdc'),
14
+ watchTrade('ethusdc'),
15
+ watchTrade('ethbtc')
16
+ ]).pipe(
17
+ tap(([btcusdc, ethusdc, ethbtc]) => {
18
+ const spread = ethusdc.div(btcusdc).sub(ethbtc).abs();
19
+
20
+ info(`spread: ${spread.toFixed(6)}`);
21
+ })
16
22
  );
17
23
  }
18
24
 
19
- export default app().use(sqlite()).start(strategy);
25
+ export default app().use(sqlite()).start(triangularArbitrageInefficiency);
@@ -1,7 +1,7 @@
1
- import { watchAggTrade } from './watch-agg-trade';
1
+ import { watchTrade } from './watch-trade';
2
2
 
3
3
  export function useBinance() {
4
4
  return {
5
- watchAggTrade
5
+ watchTrade
6
6
  };
7
7
  }
@@ -1,4 +1,4 @@
1
- import { map } from 'rxjs';
1
+ import { map, retry } from 'rxjs';
2
2
  import { z } from 'zod';
3
3
 
4
4
  import { d, useSocket } from '@quantform/core';
@@ -11,12 +11,13 @@ const schema = z.object({
11
11
  })
12
12
  });
13
13
 
14
- export function watchAggTradeLive(symbol: string) {
14
+ export function watchTradeLive(symbol: string) {
15
15
  const { watch } = useSocket(
16
- `wss://fstream.binance.com/stream?streams=${symbol.toLowerCase()}@aggTrade`
16
+ `wss://fstream.binance.com/stream?streams=${symbol.toLowerCase()}@trade`
17
17
  );
18
18
 
19
19
  return watch().pipe(
20
+ retry(),
20
21
  map(({ timestamp, payload }) => {
21
22
  const { data } = schema.parse(payload);
22
23
 
@@ -0,0 +1,48 @@
1
+ import csv from 'csv-parser';
2
+ import { map } from 'rxjs';
3
+ import { Readable } from 'stream';
4
+ import unzipper from 'unzipper';
5
+ import { z } from 'zod';
6
+
7
+ import { add, convert, d, uri, us, useReplayStorage } from '@quantform/core';
8
+
9
+ const schema = z.object({ 1: z.string(), 2: z.string() });
10
+ const DAY_NS = convert(us(86_400_000_000n), 'us', 'ns');
11
+
12
+ export function watchTradeReplay(symbol: string) {
13
+ const { watch } = useReplayStorage(uri(`binance://trade`, { symbol }), {
14
+ sync: async (query, storage) => {
15
+ const { min, max } = query.where.timestamp;
16
+
17
+ for (let time = min; time <= max; time = add(time, DAY_NS)) {
18
+ const date = new Date(Number(convert(time, 'ns', 'ms')));
19
+
20
+ for await (const payload of queryTradeHistory(date, symbol)) {
21
+ const timestamp = convert(us(BigInt(payload[4])), 'us', 'ns');
22
+
23
+ await storage.save([{ timestamp, payload }]);
24
+ }
25
+ }
26
+ }
27
+ });
28
+
29
+ return watch().pipe(
30
+ map(({ timestamp, payload }) => {
31
+ const { 1: price, 2: quantity } = schema.parse(payload);
32
+
33
+ return { timestamp, payload: { price: d(price), size: d(quantity) } };
34
+ })
35
+ );
36
+ }
37
+
38
+ async function* queryTradeHistory(date: Date, symbol: string) {
39
+ const yyyyMMdd = date.toISOString().slice(0, 10);
40
+
41
+ const response = await fetch(
42
+ `https://data.binance.vision/data/spot/daily/trades/${symbol.toUpperCase()}/${symbol.toUpperCase()}-trades-${yyyyMMdd}.zip`
43
+ );
44
+
45
+ yield* Readable.fromWeb(response.body as any)
46
+ .pipe(unzipper.ParseOne(/\.csv$/))
47
+ .pipe(csv({ headers: false }));
48
+ }
@@ -0,0 +1,13 @@
1
+ import { map } from 'rxjs';
2
+
3
+ import { useReplay } from '@quantform/core';
4
+
5
+ import { watchTradeLive } from './watch-trade.live';
6
+ import { watchTradeReplay } from './watch-trade.replay';
7
+
8
+ export function watchTrade(symbol: string) {
9
+ return useReplay(
10
+ watchTradeReplay,
11
+ watchTradeLive
12
+ )(symbol).pipe(map(it => it.payload.price));
13
+ }
@@ -1,50 +0,0 @@
1
- import csv from 'csv-parser';
2
- import { map } from 'rxjs';
3
- import { Readable } from 'stream';
4
- import unzipper from 'unzipper';
5
- import { z } from 'zod';
6
-
7
- import { add, convert, d, uri, us, useReplayStorage } from '@quantform/core';
8
-
9
- const schema = z.object({
10
- 1: z.string(),
11
- 2: z.string()
12
- });
13
-
14
- const DAY_NS = convert(us(24 * 60 * 60 * 1000 * 1000 * 1000), 'us', 'ns');
15
-
16
- export function watchAggTradeReplay(symbol: string) {
17
- const { watch } = useReplayStorage(uri(`binance://aggTrade`, { symbol }), {
18
- sync: async (query, storage) => {
19
- let timestamp = query.where.timestamp.min;
20
-
21
- while (timestamp <= query.where.timestamp.max) {
22
- const date = new Date(Number(convert(timestamp, 'ns', 'ms')))
23
- .toISOString()
24
- .slice(0, 10);
25
-
26
- const response = await fetch(
27
- `https://data.binance.vision/data/spot/daily/aggTrades/${symbol.toUpperCase()}/${symbol.toUpperCase()}-aggTrades-${date}.zip`
28
- );
29
-
30
- for await (const row of Readable.fromWeb(response.body as any)
31
- .pipe(unzipper.ParseOne(/\.csv$/))
32
- .pipe(csv({ headers: false }))) {
33
- await storage.save([
34
- { timestamp: convert(us(BigInt(row[5])), 'us', 'ns'), payload: row }
35
- ]);
36
- }
37
-
38
- timestamp = add(timestamp, DAY_NS);
39
- }
40
- }
41
- });
42
-
43
- return watch().pipe(
44
- map(({ timestamp, payload }) => {
45
- const { 1: price, 2: quantity } = schema.parse(payload);
46
-
47
- return { timestamp, payload: { price: d(price), size: d(quantity) } };
48
- })
49
- );
50
- }
@@ -1,8 +0,0 @@
1
- import { useReplay } from '@quantform/core';
2
-
3
- import { watchAggTradeLive } from './watch-agg-trade.live';
4
- import { watchAggTradeReplay } from './watch-agg-trade.replay';
5
-
6
- export function watchAggTrade(symbol: string) {
7
- return useReplay(watchAggTradeReplay, watchAggTradeLive)(symbol);
8
- }