From 490db6800f038f0ad90dd7e2afd1357534e75424 Mon Sep 17 00:00:00 2001 From: xnerhu Date: Sat, 12 Jul 2025 16:24:03 +0200 Subject: [PATCH 1/2] test --- .github/workflows/cd.yaml | 24 +++++++++++++----------- content/.qpace.json | 8 +------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 067a816..3ac3526 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -1,17 +1,19 @@ name: CD -on: - workflow_dispatch: - inputs: - target: - description: "Triplet to build (linux-x64-gnu / win32-x64-msvc)" - required: false - type: string # on: -# pull_request: -# push: -# branches: -# - master +# workflow_dispatch: +# inputs: +# target: +# description: "Triplet to build (linux-x64-gnu / win32-x64-msvc)" +# required: false +# type: string + +on: + pull_request: + push: + branches: + - master + # workflow_run: # workflows: ["CI"] # types: [completed] diff --git a/content/.qpace.json b/content/.qpace.json index 7a3931a..8310e75 100644 --- a/content/.qpace.json +++ b/content/.qpace.json @@ -1,16 +1,10 @@ { "install": false, "test": false, - "python": { + "py": { "package": "qpace_content", "checkVersion": false }, - "node": { - "package": "qpace_content" - }, - "web": { - "package": "qpace_content" - }, "js": { "package": "qpace_content" }, From 2900820b62adf218b488ce3ef7588b4871a5489f Mon Sep 17 00:00:00 2001 From: xnerhu Date: Sat, 12 Jul 2025 18:19:20 +0200 Subject: [PATCH 2/2] aa --- .github/workflows/cd.yaml | 24 ++-- algoalpha/.qpace.json | 18 ++- algoalpha/BUILD.bazed | 12 ++ algoalpha/README.md | 77 +++++++++++ algoalpha/amazing_oscillator.pine | 4 +- algoalpha/build.ts | 52 ++++++++ algoalpha/supertrended_rsi.pine | 4 +- ...nals.pine => triple_smoothed_signals.pine} | 4 +- base/node/exec.ts | 124 +++++++++--------- base/node/fs.ts | 24 +++- 10 files changed, 262 insertions(+), 81 deletions(-) create mode 100644 algoalpha/BUILD.bazed create mode 100644 algoalpha/README.md create mode 100644 algoalpha/build.ts rename algoalpha/{tripple_smoothed_signals.pine => triple_smoothed_signals.pine} (98%) diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 3ac3526..eb5fa0e 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -1,18 +1,18 @@ name: CD -# on: -# workflow_dispatch: -# inputs: -# target: -# description: "Triplet to build (linux-x64-gnu / win32-x64-msvc)" -# required: false -# type: string - on: - pull_request: - push: - branches: - - master + workflow_dispatch: + inputs: + target: + description: "Triplet to build (linux-x64-gnu / win32-x64-msvc)" + required: false + type: string + +# on: +# pull_request: +# push: +# branches: +# - master # workflow_run: # workflows: ["CI"] diff --git a/algoalpha/.qpace.json b/algoalpha/.qpace.json index 0a6868c..0505034 100644 --- a/algoalpha/.qpace.json +++ b/algoalpha/.qpace.json @@ -2,10 +2,22 @@ "install": false, "test": false, "py": { - "package": "algoalpha" + "package": { + "name": "algoalpha", + "version": "0.0.1" + } }, "js": { - "package": "algoalpha" + "package": { + "name": "algoalpha", + "version": "0.0.1" + } }, - "include": ["supertrended_rsi.pine"] + "include": [ + "adaptive_schaff_trend_cycle.pine", + "amazing_oscillator.pine", + "exponential_trend.pine", + "supertrended_rsi.pine", + "triple_smoothed_signals.pine" + ] } diff --git a/algoalpha/BUILD.bazed b/algoalpha/BUILD.bazed new file mode 100644 index 0000000..0b2cb49 --- /dev/null +++ b/algoalpha/BUILD.bazed @@ -0,0 +1,12 @@ +fileSet("pine", { + srcs: [".qpace_json", glob("*.pine")], +}); + +tsLibrary("src", { + srcs: ["build.ts"], +}); + +nodeEntry("algoalpha", { + main: "build.js", + deps: ["//base/node", ":src", ":pine"], +}); diff --git a/algoalpha/README.md b/algoalpha/README.md new file mode 100644 index 0000000..1d1e69a --- /dev/null +++ b/algoalpha/README.md @@ -0,0 +1,77 @@ +# AlgoAlpha x QPACE + +QPACE: + +- [Website](https://qpace.dev) +- [Documentation](/DOCS.md) + +AlgoAlpha: + +- [Website](https://www.algoalpha.io/) + +## Indicators + +- [Adaptive Schaff Trend Cycle (STC)](https://www.tradingview.com/v/yOxili7R/) +- [Amazing Oscillator (AO)](https://www.tradingview.com/v/g9j9piQE/) +- [Exponential Trend](https://www.tradingview.com/v/CDb3oR6A/) +- [Supertrended RSI](https://www.tradingview.com/v/tjP35RG5/) +- [Triple Smoothed Signals](https://www.tradingview.com/v/FoMINXVf/) + +## Python + +```bash +pip install qpace algoalpha +``` + +```python +import qpace as qp +import algoalpha as aa + +ohlcv = qp.Ohlcv.read_csv("btc_csv", qp.Timeframe.Days(1)) +ctx = qp.Ctx(ohlcv, qp.Sym.BTC_USD()) + +res = aa.supertrended_rsi.main(ctx.copy()) +print(res["locals"]["rsi_value"][0:30]) +``` + +## JavaScript + +```bash +npm add qpace algoalpha +``` + +Node.js: + +```ts +import * as qp from "qpace/node"; +import * as aa from "algoalpha/node"; + +const ohlcv = qp.Ohlcv.readCsv("btc_csv", qp.Timeframe.Days(1)) +const ctx = qp.Ctx(ohlcv, qp.BTC_USD()) + +const res = aa.supertrended_rsi.main(ctx.copy()) +console.log(res.locals.rsi_value.slice(0, 30)) +``` + +Browser: + +```ts +import * as qp from "qpace/web"; +import * as aa from "algoalpha/web"; + +window.onload = async () => { + // Initialize WASM Modules + { + await qp.init(); + await qp.ta.init(); + await aa.init(); + } + + const ohlcv = qp.Ohlcv.fromBars([ ... ]) + const ctx = qp.Ctx(ohlcv, qp.BTC_USD()) + + const res = aa.supertrended_rsi.main(ctx.copy()) + console.log(res.locals.rsi_value.slice(0, 30)) +} + +``` diff --git a/algoalpha/amazing_oscillator.pine b/algoalpha/amazing_oscillator.pine index 95f2e03..7246969 100644 --- a/algoalpha/amazing_oscillator.pine +++ b/algoalpha/amazing_oscillator.pine @@ -20,11 +20,11 @@ rise = ta.rma(math.max(ta.change(amazingOsc), 0), oscPeriod) fall = ta.rma(-math.min(ta.change(amazingOsc), 0), oscPeriod) customRSI = (fall == 0 ? 100 : rise == 0 ? 0 : 100 - (100 / (1 + rise / fall))) - 50 opacityLevel = customRSI > 0 and customRSI > customRSI[1] or customRSI < 0 and customRSI < customRSI[1] ? 30 : 80 -barColor = customRSI > 0 ? color.new(upColor, opacityLevel) : color.new(downColor, opacityLevel) +// barColor = customRSI > 0 ? color.new(upColor, opacityLevel) : color.new(downColor, opacityLevel) // --------------------- EXPORTED --------------------- var float custom_rsi = na -stc := customRSI +custom_rsi := customRSI // ---------------------------------------------------- // Plots diff --git a/algoalpha/build.ts b/algoalpha/build.ts new file mode 100644 index 0000000..8cb3879 --- /dev/null +++ b/algoalpha/build.ts @@ -0,0 +1,52 @@ +import chalk from "chalk"; +import { mkdir } from "fs/promises"; +import { resolve } from "path"; +import { exec } from "~/base/node/exec"; +import { clearDir } from "~/base/node/fs"; +import { BuildTarget } from "~/cli/compiler"; + +const WORKSPACE_PATH = process.env["BAZED_WORKSPACE_ROOT"] ?? process.cwd(); + +const main = async (): Promise => { + const root = resolve(WORKSPACE_PATH, "algoalpha"); + const baseCommand = "pnpm bazed run //cli:main --verbose --"; + await exec({ + command: `${baseCommand} --version`, + io: true, + }); + const outDir = resolve(WORKSPACE_PATH, "out/algoalpha"); + console.log(chalk.magentaBright(outDir)); + await clearDir(outDir); + await mkdir(outDir, { recursive: true }); + + const targets: BuildTarget[] = [ + "python-x86_64-windows", + "python-x86_64-macos", + "python-x86_64-linux", + "python-arm64-macos", + "python-arm64-linux", + "js", + // + // "wasm-unknown-unknown", + // "node-x86_64-windows", + // "node-x86_64-linux", + // "node-x86_64-macos", + // "node-arm64-linux", + // "node-arm64-macos", + ]; + + for (const target of targets) { + await exec({ + command: `${baseCommand} build --target ${target} --out-dir ${outDir} --verbose --cwd ${root}`, + verbose: true, + cwd: root, + throw: true, + env: { + ...process.env, + // DEV: true, + }, + }); + } +}; + +main(); diff --git a/algoalpha/supertrended_rsi.pine b/algoalpha/supertrended_rsi.pine index dfd3862..51d3292 100644 --- a/algoalpha/supertrended_rsi.pine +++ b/algoalpha/supertrended_rsi.pine @@ -10,8 +10,8 @@ smoothingLength = input.int(21, title="RSI Smoothing Length", group="RSI Setting rsiInputSource = input.source(close, title="RSI Source", group="RSI Settings") isSmoothed = input.bool(false, "Smooth RSI?", group="RSI Settings") -movingAverageLength = input.int(14, title="MA Length", group="MA Settings", display = display.data_window) -movingAverageType = input.string("HMA", title="MA Type", options=["SMA", "HMA", "EMA", "SMMA (RMA)", "WMA", "VWMA"], group="MA Settings", display = display.data_window) +movingAverageLength = input.int(14, title="MA Length", group="MA Settings") // , display = display.data_window +movingAverageType = input.string("HMA", title="MA Type", options=["SMA", "HMA", "EMA", "SMMA (RMA)", "WMA", "VWMA"], group="MA Settings") // , display = display.data_window // showMovingAverage = input.bool(true, "Show RSI MA?", group="MA Settings") trendFactor = input.float(0.8, title="Factor", group="Super Trend Settings") diff --git a/algoalpha/tripple_smoothed_signals.pine b/algoalpha/triple_smoothed_signals.pine similarity index 98% rename from algoalpha/tripple_smoothed_signals.pine rename to algoalpha/triple_smoothed_signals.pine index 9a2962b..0873885 100644 --- a/algoalpha/tripple_smoothed_signals.pine +++ b/algoalpha/triple_smoothed_signals.pine @@ -18,8 +18,8 @@ ma(source, length, MAtype) => "RMA" => ta.rma(source, length) "WMA" => ta.wma(source, length) -var float m1 = na -var float m2 = na +var float v1 = na +var float v2 = na var float dist = na var float ndist = na var float h = na diff --git a/base/node/exec.ts b/base/node/exec.ts index c5016ff..490ea9e 100644 --- a/base/node/exec.ts +++ b/base/node/exec.ts @@ -18,6 +18,7 @@ export interface ExecOptions { returnChild?: boolean; detached?: boolean; verbose?: boolean; + throw?: boolean; } export interface ExecResult { @@ -54,7 +55,7 @@ export async function exec(options: ExecOptions): Promise { const req = { command: commandStr, args: args ?? [], - env: options.env ?? process.env, + env: options.env ?? {}, shell: options.shell ?? true, cwd: options.cwd ?? undefined, stdio: @@ -66,70 +67,75 @@ export async function exec(options: ExecOptions): Promise { returnChild: options.returnChild, }; - const execRes = await new Promise((resolvePromise) => { - const env = req.env; - const cwd = req.cwd != null ? resolve(req.cwd) : undefined; + const execRes = await new Promise( + (resolvePromise, rejectPromise) => { + const env = req.env; + const cwd = req.cwd != null ? resolve(req.cwd) : undefined; - if (tmpScriptPath) { - commandStr = `bash "${tmpScriptPath}"`; - } - - const child = spawn(commandStr, req.args, { - env, - shell: req.shell, - cwd, - stdio: req.stdio as any, - detached: options.detached, - }); - - if (req.returnChild) { - return resolvePromise(child as any); - } - - let stdout = ""; - const stderr = ""; - - let exitCode: number | null = null; - let signal: NodeJS.Signals | null = null; - let killTimer: NodeJS.Timeout | undefined; + if (tmpScriptPath) { + commandStr = `bash "${tmpScriptPath}"`; + } - let alreadyFinished = false; + const child = spawn(commandStr, req.args, { + env, + shell: req.shell, + cwd, + stdio: req.stdio as any, + detached: options.detached, + }); - const finish = (): void => { - if (alreadyFinished) { - throw new Error("Already finished. This should not happen"); + if (req.returnChild) { + return resolvePromise(child as any); } - alreadyFinished = true; - clearTimeout(killTimer); - const res: ExecResult = { - stdout, - stderr, - command: commandStr, - exitCode: exitCode ?? 0, - signal: signal ?? undefined, + + let stdout = ""; + const stderr = ""; + + let exitCode: number | null = null; + let signal: NodeJS.Signals | null = null; + let killTimer: NodeJS.Timeout | undefined; + + let alreadyFinished = false; + + const finish = (): void => { + if (alreadyFinished) { + throw new Error("Already finished. This should not happen"); + } + alreadyFinished = true; + clearTimeout(killTimer); + const res: ExecResult = { + stdout, + stderr, + command: commandStr, + exitCode: exitCode ?? 0, + signal: signal ?? undefined, + }; + if (exitCode !== 0 && options.throw) { + return rejectPromise(res); + } + resolvePromise(res); }; - resolvePromise(res); - }; - child.stdout?.on("data", (data) => { - stdout += data; - if (options.verbose) { - process.stdout.write(data); - } - }); - child.stderr?.on("data", (data) => { - stdout += data; - if (options.verbose) { - process.stderr.write(data); - } - }); - - child.on("exit", (_exitCode, _signal) => { - exitCode = _exitCode; - signal = _signal; - finish(); - }); - }); + child.stdout?.on("data", (data) => { + stdout += data; + if (options.verbose) { + process.stdout.write(data); + } + }); + child.stderr?.on("data", (data) => { + stdout += data; + if (options.verbose) { + process.stderr.write(data); + } + }); + + child.on("exit", (_exitCode, _signal) => { + exitCode = _exitCode; + signal = _signal; + finish(); + }); + }, + ); execRes.command = req.command; return execRes; diff --git a/base/node/fs.ts b/base/node/fs.ts index 85230a2..0d6ff55 100644 --- a/base/node/fs.ts +++ b/base/node/fs.ts @@ -1,6 +1,6 @@ import { FSWatcher, Stats } from "fs"; import { mkdtemp, readFile, stat, writeFile } from "fs/promises"; -import { FileHandle, mkdir as _mkdir } from "node:fs/promises"; +import { FileHandle, mkdir as _mkdir, readdir, unlink } from "node:fs/promises"; import { tmpdir } from "os"; import { resolve } from "path"; @@ -75,3 +75,25 @@ export const watchPaths = async ( return { cancel: (): void => watcher?.close() }; }; + +export const clearDir = async (path: string): Promise => { + if (!(await exists(path))) { + return; + } + + const files = await readdir(path); + + await Promise.all( + files.map(async (file) => await deleteFile(resolve(path, file))), + ); +}; + +export const deleteFile = async (path: string): Promise => { + try { + await unlink(path); + } catch (err) { + if ((err as any)?.code !== "ENOENT") { + throw err; + } + } +};