@backtest-kit/sidekick 0.0.7 → 0.0.9
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/package.json +1 -1
- package/scripts/init.mjs +23 -0
- package/src/config/ccxt.mjs +15 -0
- package/src/config/setup.mjs +19 -0
- package/src/config/validate.mjs +12 -0
- package/src/enum/ExchangeName.mjs +3 -0
- package/src/enum/FrameName.mjs +5 -0
- package/src/enum/RiskName.mjs +4 -0
- package/src/enum/StrategyName.mjs +3 -0
- package/src/func/market.func.mjs +46 -0
- package/src/index.mjs +6 -1
- package/src/logic/exchange/binance.exchange.mjs +59 -0
- package/src/logic/frame/dec_2025.frame.mjs +10 -0
- package/src/logic/frame/nov_2025.frame.mjs +10 -0
- package/src/logic/frame/oct_2025.frame.mjs +10 -0
- package/src/logic/index.mjs +10 -0
- package/src/logic/risk/rr_ratio.risk.mjs +39 -0
- package/src/logic/risk/tp_distance.risk.mjs +30 -0
- package/src/logic/strategy/main.strategy.mjs +29 -0
- package/src/main/bootstrap.mjs +0 -0
- package/src/utils/getArgs.mjs +25 -0
- package/template/jsconfig.json.mustache +25 -0
- package/template/package.mustache +10 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backtest-kit/sidekick",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "The easiest way to create a new Backtest Kit trading bot project. Like create-react-app, but for algorithmic trading with LLM integration and technical analysis.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Petr Tripolsky",
|
package/scripts/init.mjs
CHANGED
|
@@ -110,6 +110,7 @@ async function main() {
|
|
|
110
110
|
|
|
111
111
|
const projectPath = path.resolve(process.cwd(), projectName);
|
|
112
112
|
const srcTemplatePath = path.resolve(__dirname, '..', 'src');
|
|
113
|
+
const typesTemplatePath = path.resolve(__dirname, '..', 'types');
|
|
113
114
|
const templateDir = path.resolve(__dirname, '..', 'template');
|
|
114
115
|
|
|
115
116
|
// Template data for Mustache
|
|
@@ -145,6 +146,13 @@ async function main() {
|
|
|
145
146
|
log.success('Copied template files');
|
|
146
147
|
}
|
|
147
148
|
|
|
149
|
+
// Copy types template files
|
|
150
|
+
{
|
|
151
|
+
log.info('Copying types files...');
|
|
152
|
+
await copyFiles(typesTemplatePath, path.join(projectPath, 'types'));
|
|
153
|
+
log.success('Copied types files');
|
|
154
|
+
}
|
|
155
|
+
|
|
148
156
|
// Create package.json from template
|
|
149
157
|
{
|
|
150
158
|
log.info('Creating package.json...');
|
|
@@ -211,6 +219,21 @@ async function main() {
|
|
|
211
219
|
console.log();
|
|
212
220
|
}
|
|
213
221
|
|
|
222
|
+
// Create jsconfig.json from template
|
|
223
|
+
{
|
|
224
|
+
log.info('Creating jsconfig.json...');
|
|
225
|
+
const jsconfigContent = await renderTemplate(
|
|
226
|
+
path.join(templateDir, 'jsconfig.json.mustache'),
|
|
227
|
+
templateData
|
|
228
|
+
);
|
|
229
|
+
await fs.writeFile(
|
|
230
|
+
path.join(projectPath, 'jsconfig.json'),
|
|
231
|
+
jsconfigContent,
|
|
232
|
+
'utf-8'
|
|
233
|
+
);
|
|
234
|
+
log.success('Created jsconfig.json');
|
|
235
|
+
}
|
|
236
|
+
|
|
214
237
|
// Install dependencies
|
|
215
238
|
{
|
|
216
239
|
await runNpmInstall(projectPath);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { singleshot } from "functools-kit";
|
|
2
|
+
import ccxt from "ccxt";
|
|
3
|
+
|
|
4
|
+
export const getExchange = singleshot(async () => {
|
|
5
|
+
const exchange = new ccxt.binance({
|
|
6
|
+
options: {
|
|
7
|
+
defaultType: "spot",
|
|
8
|
+
adjustForTimeDifference: true,
|
|
9
|
+
recvWindow: 60000,
|
|
10
|
+
},
|
|
11
|
+
enableRateLimit: true,
|
|
12
|
+
});
|
|
13
|
+
await exchange.loadMarkets();
|
|
14
|
+
return exchange;
|
|
15
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Markdown, Report, setLogger } from "backtest-kit"
|
|
2
|
+
import { createLogger } from "pinolog";
|
|
3
|
+
|
|
4
|
+
{
|
|
5
|
+
const logger = createLogger(`backtest-kit.log`);
|
|
6
|
+
setLogger({
|
|
7
|
+
log: (...args) => logger.log(...args),
|
|
8
|
+
debug: (...args) => logger.info(...args),
|
|
9
|
+
info: (...args) => logger.info(...args),
|
|
10
|
+
warn: (...args) => logger.warn(...args),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
{
|
|
15
|
+
Markdown.disable();
|
|
16
|
+
Report.enable();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { validate } from "backtest-kit";
|
|
2
|
+
import ExchangeName from "../enum/ExchangeName.mjs";
|
|
3
|
+
import FrameName from "../enum/FrameName.mjs";
|
|
4
|
+
import RiskName from "../enum/RiskName.mjs";
|
|
5
|
+
import StrategyName from "../enum/StrategyName.mjs";
|
|
6
|
+
|
|
7
|
+
validate({
|
|
8
|
+
ExchangeName,
|
|
9
|
+
FrameName,
|
|
10
|
+
RiskName,
|
|
11
|
+
StrategyName,
|
|
12
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
commitFifteenMinuteHistory,
|
|
3
|
+
commitHourHistory,
|
|
4
|
+
commitLongTermMath,
|
|
5
|
+
commitMicroTermMath,
|
|
6
|
+
commitOneMinuteHistory,
|
|
7
|
+
commitShortTermMath,
|
|
8
|
+
commitSwingTermMath,
|
|
9
|
+
commitThirtyMinuteHistory,
|
|
10
|
+
} from "@backtest-kit/signals";
|
|
11
|
+
import { formatPrice, getAveragePrice, getDate } from "backtest-kit";
|
|
12
|
+
import { str } from "functools-kit";
|
|
13
|
+
|
|
14
|
+
const commitHistorySetup = async (symbol, history) => {
|
|
15
|
+
// Candle histories across timeframes
|
|
16
|
+
{
|
|
17
|
+
await commitOneMinuteHistory(symbol, history);
|
|
18
|
+
await commitFifteenMinuteHistory(symbol, history);
|
|
19
|
+
await commitThirtyMinuteHistory(symbol, history);
|
|
20
|
+
await commitHourHistory(symbol, history);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Technical indicators across timeframes
|
|
24
|
+
{
|
|
25
|
+
await commitMicroTermMath(symbol, history);
|
|
26
|
+
await commitShortTermMath(symbol, history);
|
|
27
|
+
await commitSwingTermMath(symbol, history);
|
|
28
|
+
await commitLongTermMath(symbol, history);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const displayName = await String(symbol).toUpperCase();
|
|
32
|
+
|
|
33
|
+
const currentPrice = await getAveragePrice(symbol);
|
|
34
|
+
const currentData = await getDate();
|
|
35
|
+
|
|
36
|
+
await history.push({
|
|
37
|
+
role: "system",
|
|
38
|
+
content: str.newline(
|
|
39
|
+
`Trading symbol: ${displayName}`,
|
|
40
|
+
`Current price: ${await formatPrice(symbol, currentPrice)} USD`,
|
|
41
|
+
`Current time: ${currentData.toISOString()}`
|
|
42
|
+
),
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export { commitHistorySetup };
|
package/src/index.mjs
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { addExchange, roundTicks } from "backtest-kit";
|
|
2
|
+
import { getExchange } from "../../config/ccxt.mjs";
|
|
3
|
+
import ExchangeName from "../../enum/ExchangeName.mjs";
|
|
4
|
+
|
|
5
|
+
const MAX_DEPTH_LEVELS = 1_000;
|
|
6
|
+
|
|
7
|
+
addExchange({
|
|
8
|
+
exchangeName: ExchangeName.BinanceExchange,
|
|
9
|
+
getCandles: async (symbol, interval, since, limit) => {
|
|
10
|
+
const exchange = await getExchange();
|
|
11
|
+
const candles = await exchange.fetchOHLCV(
|
|
12
|
+
symbol,
|
|
13
|
+
interval,
|
|
14
|
+
since.getTime(),
|
|
15
|
+
limit
|
|
16
|
+
);
|
|
17
|
+
return candles.map(([timestamp, open, high, low, close, volume]) => ({
|
|
18
|
+
timestamp,
|
|
19
|
+
open,
|
|
20
|
+
high,
|
|
21
|
+
low,
|
|
22
|
+
close,
|
|
23
|
+
volume,
|
|
24
|
+
}));
|
|
25
|
+
},
|
|
26
|
+
formatPrice: async (symbol, price) => {
|
|
27
|
+
const exchange = await getExchange();
|
|
28
|
+
const market = exchange.market(symbol);
|
|
29
|
+
const tickSize = market.limits?.price?.min || market.precision?.price;
|
|
30
|
+
if (tickSize !== undefined) {
|
|
31
|
+
return roundTicks(price, tickSize);
|
|
32
|
+
}
|
|
33
|
+
return exchange.priceToPrecision(symbol, price);
|
|
34
|
+
},
|
|
35
|
+
formatQuantity: async (symbol, quantity) => {
|
|
36
|
+
const exchange = await getExchange();
|
|
37
|
+
const market = exchange.market(symbol);
|
|
38
|
+
const stepSize = market.limits?.amount?.min || market.precision?.amount;
|
|
39
|
+
if (stepSize !== undefined) {
|
|
40
|
+
return roundTicks(quantity, stepSize);
|
|
41
|
+
}
|
|
42
|
+
return exchange.amountToPrecision(symbol, quantity);
|
|
43
|
+
},
|
|
44
|
+
getOrderBook: async (symbol) => {
|
|
45
|
+
const exchange = await getExchange();
|
|
46
|
+
const bookData = await exchange.fetchOrderBook(symbol, MAX_DEPTH_LEVELS);
|
|
47
|
+
return {
|
|
48
|
+
symbol,
|
|
49
|
+
asks: bookData.asks.map(([price, quantity]) => ({
|
|
50
|
+
price: String(price),
|
|
51
|
+
quantity: String(quantity),
|
|
52
|
+
})),
|
|
53
|
+
bids: bookData.bids.map(([price, quantity]) => ({
|
|
54
|
+
price: String(price),
|
|
55
|
+
quantity: String(quantity),
|
|
56
|
+
})),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { addFrame } from "backtest-kit";
|
|
2
|
+
import FrameName from "../../enum/FrameName.mjs";
|
|
3
|
+
|
|
4
|
+
addFrame({
|
|
5
|
+
frameName: FrameName.December2025,
|
|
6
|
+
interval: "1m",
|
|
7
|
+
startDate: new Date("2025-12-01T00:00:00Z"),
|
|
8
|
+
endDate: new Date("2025-12-31T23:59:59Z"),
|
|
9
|
+
note: "Боковик без явного роста или падения",
|
|
10
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { addFrame } from "backtest-kit";
|
|
2
|
+
import FrameName from "../../enum/FrameName.mjs";
|
|
3
|
+
|
|
4
|
+
addFrame({
|
|
5
|
+
frameName: FrameName.November2025,
|
|
6
|
+
interval: "1m",
|
|
7
|
+
startDate: new Date("2025-11-01T00:00:00Z"),
|
|
8
|
+
endDate: new Date("2025-11-30T23:59:59Z"),
|
|
9
|
+
note: "Боковик с общим трендом вниз и незначительными отскоками",
|
|
10
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { addFrame } from "backtest-kit";
|
|
2
|
+
import FrameName from "../../enum/FrameName.mjs";
|
|
3
|
+
|
|
4
|
+
addFrame({
|
|
5
|
+
frameName: FrameName.October2025,
|
|
6
|
+
interval: "1m",
|
|
7
|
+
startDate: new Date("2025-10-01T00:00:00Z"),
|
|
8
|
+
endDate: new Date("2025-10-31T23:59:59Z"),
|
|
9
|
+
note: "Резкое падение рынка с 9 по 11 число",
|
|
10
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import "./exchange/binance.exchange.mjs";
|
|
2
|
+
|
|
3
|
+
import "./frame/dec_2025.frame.mjs";
|
|
4
|
+
import "./frame/nov_2025.frame.mjs";
|
|
5
|
+
import "./frame/oct_2025.frame.mjs";
|
|
6
|
+
|
|
7
|
+
import "./risk/rr_ratio.risk.mjs";
|
|
8
|
+
import "./risk/tp_distance.risk.mjs";
|
|
9
|
+
|
|
10
|
+
import "./strategy/main.strategy.mjs";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { addRisk } from "backtest-kit";
|
|
2
|
+
import RiskName from "../../enum/RiskName.mjs";
|
|
3
|
+
|
|
4
|
+
addRisk({
|
|
5
|
+
riskName: RiskName.RiskRewardRatioRisk,
|
|
6
|
+
validations: [
|
|
7
|
+
{
|
|
8
|
+
validate: ({ pendingSignal, currentPrice }) => {
|
|
9
|
+
const {
|
|
10
|
+
priceOpen = currentPrice,
|
|
11
|
+
priceTakeProfit,
|
|
12
|
+
priceStopLoss,
|
|
13
|
+
position,
|
|
14
|
+
} = pendingSignal;
|
|
15
|
+
if (!priceOpen) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
// Calculate reward (TP distance)
|
|
19
|
+
const reward =
|
|
20
|
+
position === "long"
|
|
21
|
+
? priceTakeProfit - priceOpen
|
|
22
|
+
: priceOpen - priceTakeProfit;
|
|
23
|
+
// Calculate risk (SL distance)
|
|
24
|
+
const risk =
|
|
25
|
+
position === "long"
|
|
26
|
+
? priceOpen - priceStopLoss
|
|
27
|
+
: priceStopLoss - priceOpen;
|
|
28
|
+
if (risk <= 0) {
|
|
29
|
+
throw new Error("Invalid SL: risk must be positive");
|
|
30
|
+
}
|
|
31
|
+
const rrRatio = reward / risk;
|
|
32
|
+
if (rrRatio < 2) {
|
|
33
|
+
throw new Error(`RR ratio ${rrRatio.toFixed(2)} < 2:1`);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
note: "Risk-Reward ratio must be at least 1:2",
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { addRisk } from "backtest-kit";
|
|
2
|
+
import RiskName from "../../enum/RiskName.mjs";
|
|
3
|
+
|
|
4
|
+
addRisk({
|
|
5
|
+
riskName: RiskName.TakeProfitDistanceRisk,
|
|
6
|
+
validations: [
|
|
7
|
+
{
|
|
8
|
+
validate: ({ pendingSignal, currentPrice }) => {
|
|
9
|
+
const {
|
|
10
|
+
priceOpen = currentPrice,
|
|
11
|
+
priceTakeProfit,
|
|
12
|
+
position,
|
|
13
|
+
} = pendingSignal;
|
|
14
|
+
if (!priceOpen) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
// Calculate TP distance percentage
|
|
18
|
+
const tpDistance =
|
|
19
|
+
position === "long"
|
|
20
|
+
? ((priceTakeProfit - priceOpen) / priceOpen) * 100
|
|
21
|
+
: ((priceOpen - priceTakeProfit) / priceOpen) * 100;
|
|
22
|
+
|
|
23
|
+
if (tpDistance < 1) {
|
|
24
|
+
throw new Error(`TP distance ${tpDistance.toFixed(2)}% < 1%`);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
note: "TP distance must be at least 1%",
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { addStrategy } from "backtest-kit";
|
|
2
|
+
import { ollama } from "@backtest-kit/ollama";
|
|
3
|
+
|
|
4
|
+
import { commitHistorySetup } from "../../func/market.func.mjs";
|
|
5
|
+
|
|
6
|
+
import StrategyName from "../../enum/StrategyName.mjs";
|
|
7
|
+
import RiskName from "../../enum/RiskName.mjs";
|
|
8
|
+
|
|
9
|
+
addStrategy({
|
|
10
|
+
strategyName: StrategyName.MainStrategy,
|
|
11
|
+
interval: "5m",
|
|
12
|
+
getSignal: async (symbol) => {
|
|
13
|
+
const messages = [];
|
|
14
|
+
|
|
15
|
+
{
|
|
16
|
+
await commitHistorySetup(symbol, messages);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return await ollama(
|
|
20
|
+
messages,
|
|
21
|
+
"glm-4.6:cloud",
|
|
22
|
+
process.env.CC_OLLAMA_API_KEY
|
|
23
|
+
);
|
|
24
|
+
},
|
|
25
|
+
riskList: [
|
|
26
|
+
RiskName.TakeProfitDistanceRisk,
|
|
27
|
+
RiskName.RiskRewardRatioRisk
|
|
28
|
+
],
|
|
29
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { singleshot } from "functools-kit";
|
|
2
|
+
import { parseArgs } from "util";
|
|
3
|
+
|
|
4
|
+
export const getArgs = singleshot(() => {
|
|
5
|
+
const { values } = parseArgs({
|
|
6
|
+
args: process.argv,
|
|
7
|
+
options: {
|
|
8
|
+
strategy: {
|
|
9
|
+
type: "string",
|
|
10
|
+
},
|
|
11
|
+
backtest: {
|
|
12
|
+
type: "boolean",
|
|
13
|
+
},
|
|
14
|
+
paper: {
|
|
15
|
+
type: "boolean",
|
|
16
|
+
},
|
|
17
|
+
live: {
|
|
18
|
+
type: "boolean",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
strict: false,
|
|
22
|
+
allowPositionals: true,
|
|
23
|
+
});
|
|
24
|
+
return values;
|
|
25
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "ESNext",
|
|
4
|
+
"moduleResolution": "node",
|
|
5
|
+
"target": "ES2020",
|
|
6
|
+
"checkJs": true,
|
|
7
|
+
"allowSyntheticDefaultImports": true,
|
|
8
|
+
"ignoreDeprecations": "6.0",
|
|
9
|
+
"baseUrl": ".",
|
|
10
|
+
"paths": {
|
|
11
|
+
"*": ["node_modules/*"]
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"include": [
|
|
15
|
+
"src/**/*",
|
|
16
|
+
"types/**/*",
|
|
17
|
+
"node_modules/functools-kit/types.d.ts",
|
|
18
|
+
"node_modules/backtest-kit/types.d.ts",
|
|
19
|
+
"node_modules/@backtest-kit/ollama/types.d.ts",
|
|
20
|
+
"node_modules/@backtest-kit/signals/types.d.ts"
|
|
21
|
+
],
|
|
22
|
+
"exclude": [
|
|
23
|
+
"node_modules"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -9,20 +9,20 @@
|
|
|
9
9
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"backtest-kit": "^
|
|
13
|
-
"@backtest-kit/signals": "^0.0.
|
|
14
|
-
"@
|
|
15
|
-
"
|
|
12
|
+
"@backtest-kit/ollama": "^0.0.7",
|
|
13
|
+
"@backtest-kit/signals": "^0.0.7",
|
|
14
|
+
"@huggingface/inference": "^4.7.1",
|
|
15
|
+
"@langchain/core": "^0.3.57",
|
|
16
|
+
"@langchain/xai": "^0.0.2",
|
|
17
|
+
"agent-swarm-kit": "^1.1.181",
|
|
18
|
+
"backtest-kit": "^1.13.2",
|
|
16
19
|
"ccxt": "^4.4.41",
|
|
17
|
-
"uuid": "^11.0.3",
|
|
18
20
|
"dotenv": "^16.4.7",
|
|
21
|
+
"functools-kit": "^1.0.95",
|
|
19
22
|
"ollama": "^0.6.0",
|
|
20
23
|
"openai": "^4.97.0",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"@langchain/xai": "^0.0.2",
|
|
24
|
-
"@huggingface/inference": "^4.7.1",
|
|
25
|
-
"sanitize-html": "^2.17.0"
|
|
24
|
+
"pinolog": "^1.0.5",
|
|
25
|
+
"uuid": "^11.0.3"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"dotenv-cli": "^7.4.2"
|