@backtest-kit/sidekick 0.0.6 → 0.0.8
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 +15 -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 +24 -0
- package/template/package.mustache +9 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backtest-kit/sidekick",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
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
|
@@ -211,6 +211,21 @@ async function main() {
|
|
|
211
211
|
console.log();
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
// Create jsconfig.json from template
|
|
215
|
+
{
|
|
216
|
+
log.info('Creating jsconfig.json...');
|
|
217
|
+
const jsconfigContent = await renderTemplate(
|
|
218
|
+
path.join(templateDir, 'jsconfig.json.mustache'),
|
|
219
|
+
templateData
|
|
220
|
+
);
|
|
221
|
+
await fs.writeFile(
|
|
222
|
+
path.join(projectPath, 'jsconfig.json'),
|
|
223
|
+
jsconfigContent,
|
|
224
|
+
'utf-8'
|
|
225
|
+
);
|
|
226
|
+
log.success('Created jsconfig.json');
|
|
227
|
+
}
|
|
228
|
+
|
|
214
229
|
// Install dependencies
|
|
215
230
|
{
|
|
216
231
|
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,24 @@
|
|
|
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
|
+
"node_modules/functools-kit/types.d.ts",
|
|
17
|
+
"node_modules/backtest-kit/types.d.ts",
|
|
18
|
+
"node_modules/@backtest-kit/ollama/types.d.ts",
|
|
19
|
+
"node_modules/@backtest-kit/signals/types.d.ts"
|
|
20
|
+
],
|
|
21
|
+
"exclude": [
|
|
22
|
+
"node_modules"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -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
|
-
"@
|
|
12
|
+
"@backtest-kit/ollama": "^0.0.5",
|
|
13
|
+
"@backtest-kit/signals": "^0.0.6",
|
|
14
|
+
"@huggingface/inference": "^4.7.1",
|
|
15
|
+
"@langchain/core": "^0.3.57",
|
|
16
|
+
"@langchain/xai": "^0.0.2",
|
|
15
17
|
"agent-swarm-kit": "^1.1.180",
|
|
18
|
+
"backtest-kit": "^1.12.3",
|
|
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"
|