Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bdd20f83a | ||
|
|
3bef7dea9e | ||
|
|
fc08a41c4b |
@@ -98,7 +98,7 @@ export async function GET() {
|
|||||||
fundTransactions: displayFundTxns,
|
fundTransactions: displayFundTxns,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return { firm: f.name, connected: true, accounts, perContractFees: client.perContractFees };
|
return { firm: f.name, connected: true, accounts };
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json(state);
|
return NextResponse.json(state);
|
||||||
|
|||||||
+2
-1
@@ -7,7 +7,7 @@
|
|||||||
* signal but have since exited and are now eligible.
|
* signal but have since exited and are now eligible.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getFirms, isSymbolBanned, getInstruments } from './db';
|
import { getFirms, isSymbolBanned, getInstruments, getBannedSymbols } from './db';
|
||||||
import { getClients } from './clients';
|
import { getClients } from './clients';
|
||||||
import { computeDailyTarget, resolveEffectiveConfig, POINT_VALUES } from './trading-logic';
|
import { computeDailyTarget, resolveEffectiveConfig, POINT_VALUES } from './trading-logic';
|
||||||
import { getSetting } from './db';
|
import { getSetting } from './db';
|
||||||
@@ -33,6 +33,7 @@ function mapFirmConfig(firm: FirmWithAccounts): FirmConfig {
|
|||||||
firm: firm.name,
|
firm: firm.name,
|
||||||
username: firm.username,
|
username: firm.username,
|
||||||
password: firm.password,
|
password: firm.password,
|
||||||
|
bannedSymbols: getBannedSymbols(firm.id),
|
||||||
accounts: firm.accounts.map((a) => ({
|
accounts: firm.accounts.map((a) => ({
|
||||||
prefix: a.prefix,
|
prefix: a.prefix,
|
||||||
profitTarget: a.profit_target,
|
profitTarget: a.profit_target,
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ function ensureMap(): Map<number, TradovateClient> {
|
|||||||
|
|
||||||
export function initClient(id: number, username: string, password: string, firmName: string): TradovateClient {
|
export function initClient(id: number, username: string, password: string, firmName: string): TradovateClient {
|
||||||
const map = ensureMap();
|
const map = ensureMap();
|
||||||
const client = new TradovateClient(username, password, () => {
|
const client = new TradovateClient(username, password, async () => {
|
||||||
console.log(`[${firmName}] sync complete — ${client.accountList.length} account(s)`);
|
console.log(`[${firmName}] sync complete — ${client.accountList.length} account(s)`);
|
||||||
});
|
});
|
||||||
map.set(id, client);
|
map.set(id, client);
|
||||||
|
|||||||
@@ -199,11 +199,15 @@ export function setSetting(key: string, value: string): void {
|
|||||||
|
|
||||||
const SYMBOLS = ['NQ','MNQ','ES','MES','YM','MYM','RTY','M2K','GC','MGC','SI','CL','MCL','NG','ZB','ZN','ZF','6E','6J','6B'];
|
const SYMBOLS = ['NQ','MNQ','ES','MES','YM','MYM','RTY','M2K','GC','MGC','SI','CL','MCL','NG','ZB','ZN','ZF','6E','6J','6B'];
|
||||||
|
|
||||||
|
// Enabled on a fresh install. The rest still seed, so they can be switched on
|
||||||
|
// from the Instruments page, they just start off.
|
||||||
|
const DEFAULT_ENABLED = new Set(['NQ', 'GC', 'CL']);
|
||||||
|
|
||||||
// Seed instruments table if empty
|
// Seed instruments table if empty
|
||||||
const instrCount = (db.prepare('SELECT COUNT(*) as count FROM instruments').get() as { count: number }).count;
|
const instrCount = (db.prepare('SELECT COUNT(*) as count FROM instruments').get() as { count: number }).count;
|
||||||
if (instrCount === 0) {
|
if (instrCount === 0) {
|
||||||
const ins = db.prepare('INSERT INTO instruments (symbol, enabled) VALUES (?, 1)');
|
const ins = db.prepare('INSERT INTO instruments (symbol, enabled) VALUES (?, ?)');
|
||||||
for (const s of SYMBOLS) ins.run(s);
|
for (const s of SYMBOLS) ins.run(s, DEFAULT_ENABLED.has(s) ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InstrumentRow {
|
export interface InstrumentRow {
|
||||||
|
|||||||
+21
-17
@@ -7,10 +7,27 @@ import { POINT_VALUES } from './trading-logic';
|
|||||||
import { getCachedContract, resolveContracts } from './contract-resolver';
|
import { getCachedContract, resolveContracts } from './contract-resolver';
|
||||||
import { saveDailyPnL, loadDailyPnL, saveAccountMeta, loadAccountMeta, saveFundTransactions, loadFundTransactions } from './db';
|
import { saveDailyPnL, loadDailyPnL, saveAccountMeta, loadAccountMeta, saveFundTransactions, loadFundTransactions } from './db';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity types Tradovate sends over the user-sync socket. Keep this as the
|
||||||
|
* single source of truth: the inline socket-payload type and the indirect
|
||||||
|
* callback list previously declared it separately and fell out of sync, which
|
||||||
|
* made the 'position' and 'cashBalance' handlers unreachable to the compiler.
|
||||||
|
*/
|
||||||
|
type TradovateEntityType =
|
||||||
|
| 'order'
|
||||||
|
| 'orderVersion'
|
||||||
|
| 'auditUserAction'
|
||||||
|
| 'command'
|
||||||
|
| 'commandReport'
|
||||||
|
| 'fill'
|
||||||
|
| 'executionReport'
|
||||||
|
| 'position'
|
||||||
|
| 'cashBalance';
|
||||||
|
|
||||||
export class TradovateClient {
|
export class TradovateClient {
|
||||||
private name: string;
|
private name: string;
|
||||||
private password: string;
|
private password: string;
|
||||||
private accessInfo: AuthLoginResponse;
|
private accessInfo!: AuthLoginResponse;
|
||||||
private deviceId = randomUUIDV4();
|
private deviceId = randomUUIDV4();
|
||||||
|
|
||||||
public accountList: AccountItem[] = [];
|
public accountList: AccountItem[] = [];
|
||||||
@@ -70,7 +87,7 @@ export class TradovateClient {
|
|||||||
public syncComplete = false;
|
public syncComplete = false;
|
||||||
|
|
||||||
|
|
||||||
private ws: WebSocket;
|
private ws!: WebSocket;
|
||||||
private callbackOnSyncRequest: () => Promise<void>;
|
private callbackOnSyncRequest: () => Promise<void>;
|
||||||
|
|
||||||
/** Incrementing ID for outgoing WebSocket messages — ensures concurrent orders don't clobber each other's callbacks. */
|
/** Incrementing ID for outgoing WebSocket messages — ensures concurrent orders don't clobber each other's callbacks. */
|
||||||
@@ -82,15 +99,7 @@ export class TradovateClient {
|
|||||||
[id: number]: (response: any) => void;
|
[id: number]: (response: any) => void;
|
||||||
} = {};
|
} = {};
|
||||||
private indirectEventCallbacks: {
|
private indirectEventCallbacks: {
|
||||||
entityType:
|
entityType: TradovateEntityType;
|
||||||
| 'order'
|
|
||||||
| 'orderVersion'
|
|
||||||
| 'auditUserAction'
|
|
||||||
| 'command'
|
|
||||||
| 'commandReport'
|
|
||||||
| 'fill'
|
|
||||||
| 'executionReport'
|
|
||||||
| 'cashBalance';
|
|
||||||
eventType: 'Created' | 'Updated';
|
eventType: 'Created' | 'Updated';
|
||||||
// Since the entity is not always the same, we need a validator to check if the response is the one we are looking for
|
// Since the entity is not always the same, we need a validator to check if the response is the one we are looking for
|
||||||
validator: (response: any) => boolean;
|
validator: (response: any) => boolean;
|
||||||
@@ -167,12 +176,7 @@ export class TradovateClient {
|
|||||||
| {
|
| {
|
||||||
e?: string;
|
e?: string;
|
||||||
d?: {
|
d?: {
|
||||||
entityType:
|
entityType: TradovateEntityType;
|
||||||
| 'order'
|
|
||||||
| 'orderVersion'
|
|
||||||
| 'auditUserAction'
|
|
||||||
| 'command'
|
|
||||||
| 'commandReport';
|
|
||||||
eventType: 'Created' | 'Updated';
|
eventType: 'Created' | 'Updated';
|
||||||
entity: any;
|
entity: any;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Writes the reporter settings into autotrader.sqlite before first launch.
|
||||||
|
*
|
||||||
|
* lib/db.ts seeds these keys with INSERT OR IGNORE, so values written here
|
||||||
|
* survive the app's own startup seeding. Must be run from the project root —
|
||||||
|
* lib/db.ts opens the database at process.cwd().
|
||||||
|
*
|
||||||
|
* node scripts/seed-settings.js <master_dashboard_url> <instance_name>
|
||||||
|
*/
|
||||||
|
const path = require('path');
|
||||||
|
const Database = require('better-sqlite3');
|
||||||
|
|
||||||
|
const [, , url, name] = process.argv;
|
||||||
|
|
||||||
|
const db = new Database(path.join(process.cwd(), 'autotrader.sqlite'));
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS settings (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT NOT NULL
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
const upsert = db.prepare(
|
||||||
|
'INSERT INTO settings (key, value) VALUES (?, ?) ' +
|
||||||
|
'ON CONFLICT(key) DO UPDATE SET value = excluded.value'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (url) { upsert.run('master_dashboard_url', url); console.log(' master_dashboard_url = ' + url); }
|
||||||
|
if (name) { upsert.run('instance_name', name); console.log(' instance_name = ' + name); }
|
||||||
|
|
||||||
|
db.close();
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal EnableExtensions
|
||||||
|
title AutoFirmer - Windows setup
|
||||||
|
|
||||||
|
REM ============================================================================
|
||||||
|
REM AutoFirmer instance setup for Windows.
|
||||||
|
REM
|
||||||
|
REM Standalone: drop this file anywhere and run it. It clones the repo into a
|
||||||
|
REM subfolder next to itself, installs, builds, and points the instance at the
|
||||||
|
REM master dashboard. Safe to re-run - it pulls and rebuilds instead of cloning.
|
||||||
|
REM ============================================================================
|
||||||
|
|
||||||
|
set "REPO_URL=https://git.juicerroom.com/senofy/autofirmer-expanded.git"
|
||||||
|
set "DEFAULT_MASTER=https://master.juicerroom.com"
|
||||||
|
set "TARGET=%~dp0autofirmer"
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ============================================
|
||||||
|
echo AutoFirmer - Windows setup
|
||||||
|
echo ============================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM ---------------------------------------------------------------- git check
|
||||||
|
where git >nul 2>&1
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [X] Git is not installed, or not on PATH.
|
||||||
|
echo Install it from https://git-scm.com/download/win then re-run.
|
||||||
|
goto :fail
|
||||||
|
)
|
||||||
|
echo [ok] git
|
||||||
|
|
||||||
|
REM --------------------------------------------------------------- node check
|
||||||
|
where node >nul 2>&1
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [X] Node.js is not installed, or not on PATH.
|
||||||
|
echo Install Node 22 LTS from https://nodejs.org/ then re-run.
|
||||||
|
goto :fail
|
||||||
|
)
|
||||||
|
for /f "tokens=* usebackq" %%v in (`node -p "process.versions.node"`) do set "NODEVER=%%v"
|
||||||
|
for /f "tokens=1 delims=." %%m in ("%NODEVER%") do set "NODEMAJOR=%%m"
|
||||||
|
echo [ok] node v%NODEVER%
|
||||||
|
|
||||||
|
REM better-sqlite3 ships prebuilt binaries only for released Node ABIs. On a
|
||||||
|
REM newer major it falls back to node-gyp, which needs Visual Studio Build
|
||||||
|
REM Tools - a long, confusing failure if it is not installed.
|
||||||
|
if %NODEMAJOR% GEQ 23 goto :node_warn
|
||||||
|
goto :node_ok
|
||||||
|
|
||||||
|
:node_warn
|
||||||
|
echo.
|
||||||
|
echo [!] Node %NODEMAJOR% is newer than better-sqlite3's prebuilt binaries.
|
||||||
|
echo Install may try to compile from source and fail without Visual
|
||||||
|
echo Studio Build Tools. Node 22 LTS is the safe choice here.
|
||||||
|
echo.
|
||||||
|
set "GOON="
|
||||||
|
set /p "GOON= Continue anyway? [y/N] "
|
||||||
|
if /i not "%GOON%"=="y" goto :fail
|
||||||
|
:node_ok
|
||||||
|
|
||||||
|
REM ------------------------------------------------------------------- python
|
||||||
|
where python >nul 2>&1
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [--] python not found - the dashboard will still work.
|
||||||
|
echo Needed only for the clicker ^(clicker\runner.py^).
|
||||||
|
set "HAVE_PY=0"
|
||||||
|
) else (
|
||||||
|
echo [ok] python
|
||||||
|
set "HAVE_PY=1"
|
||||||
|
)
|
||||||
|
|
||||||
|
REM ------------------------------------------------------------------- prompts
|
||||||
|
echo.
|
||||||
|
set "MASTER_URL="
|
||||||
|
set /p "MASTER_URL= Master dashboard URL [%DEFAULT_MASTER%]: "
|
||||||
|
if "%MASTER_URL%"=="" set "MASTER_URL=%DEFAULT_MASTER%"
|
||||||
|
|
||||||
|
set "INSTANCE="
|
||||||
|
set /p "INSTANCE= Instance name [%COMPUTERNAME%]: "
|
||||||
|
if "%INSTANCE%"=="" set "INSTANCE=%COMPUTERNAME%"
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Installing to: %TARGET%
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM -------------------------------------------------------------- clone / pull
|
||||||
|
if exist "%TARGET%\.git" (
|
||||||
|
echo [1/5] Existing checkout found - pulling latest...
|
||||||
|
pushd "%TARGET%"
|
||||||
|
git pull --ff-only
|
||||||
|
if errorlevel 1 (
|
||||||
|
popd
|
||||||
|
echo [X] git pull failed. Resolve local changes and re-run.
|
||||||
|
goto :fail
|
||||||
|
)
|
||||||
|
popd
|
||||||
|
) else (
|
||||||
|
echo [1/5] Cloning %REPO_URL% ...
|
||||||
|
git clone "%REPO_URL%" "%TARGET%"
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [X] Clone failed. Check network access to git.juicerroom.com.
|
||||||
|
goto :fail
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
pushd "%TARGET%"
|
||||||
|
|
||||||
|
REM ------------------------------------------------------------------- install
|
||||||
|
echo.
|
||||||
|
echo [2/5] Installing npm dependencies ^(this takes a few minutes^)...
|
||||||
|
REM playwright is declared but unreferenced anywhere in the source; skipping its
|
||||||
|
REM browser download saves several hundred MB and a lot of time.
|
||||||
|
set "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1"
|
||||||
|
call npm install
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo.
|
||||||
|
echo [X] npm install failed.
|
||||||
|
echo If the error mentions node-gyp, MSBuild, or better_sqlite3.cpp,
|
||||||
|
echo the native module could not find a prebuilt binary. Either switch
|
||||||
|
echo to Node 22 LTS, or install "Desktop development with C++" from the
|
||||||
|
echo Visual Studio Build Tools installer.
|
||||||
|
popd
|
||||||
|
goto :fail
|
||||||
|
)
|
||||||
|
|
||||||
|
REM --------------------------------------------------------------------- build
|
||||||
|
echo.
|
||||||
|
echo [3/5] Building...
|
||||||
|
call npm run build
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [X] Build failed.
|
||||||
|
popd
|
||||||
|
goto :fail
|
||||||
|
)
|
||||||
|
|
||||||
|
REM ------------------------------------------------------------------ settings
|
||||||
|
echo.
|
||||||
|
echo [4/5] Writing instance settings...
|
||||||
|
node scripts\seed-settings.js "%MASTER_URL%" "%INSTANCE%"
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [X] Could not write settings to autotrader.sqlite.
|
||||||
|
popd
|
||||||
|
goto :fail
|
||||||
|
)
|
||||||
|
|
||||||
|
REM ------------------------------------------------------ python deps (option)
|
||||||
|
if "%HAVE_PY%"=="1" (
|
||||||
|
echo.
|
||||||
|
echo [5/5] Installing clicker Python dependencies...
|
||||||
|
python -m pip install --quiet --disable-pip-version-check -r clicker\requirements.txt
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [!] pip install failed - the dashboard still works, the clicker will not.
|
||||||
|
)
|
||||||
|
) else (
|
||||||
|
echo.
|
||||||
|
echo [5/5] Skipping Python dependencies ^(python not found^).
|
||||||
|
)
|
||||||
|
|
||||||
|
REM ---------------------------------------------------------------- start file
|
||||||
|
> "%~dp0start-autofirmer.bat" (
|
||||||
|
echo @echo off
|
||||||
|
echo title AutoFirmer - %INSTANCE%
|
||||||
|
echo cd /d "%TARGET%"
|
||||||
|
echo echo Dashboard starting on http://localhost:3000
|
||||||
|
echo call npm run start
|
||||||
|
echo pause
|
||||||
|
)
|
||||||
|
|
||||||
|
popd
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ============================================
|
||||||
|
echo Done.
|
||||||
|
echo ============================================
|
||||||
|
echo.
|
||||||
|
echo Instance name : %INSTANCE%
|
||||||
|
echo Reporting to : %MASTER_URL%
|
||||||
|
echo Installed in : %TARGET%
|
||||||
|
echo.
|
||||||
|
echo Start it with : start-autofirmer.bat
|
||||||
|
echo Dashboard at : http://localhost:3000
|
||||||
|
echo.
|
||||||
|
echo Still manual:
|
||||||
|
echo - Load the Chrome extension: chrome://extensions, enable Developer
|
||||||
|
echo mode, "Load unpacked", select %TARGET%\extension
|
||||||
|
echo - Add your firm credentials on the dashboard's Settings page
|
||||||
|
echo - Run the clicker when needed: python clicker\runner.py
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
:fail
|
||||||
|
echo.
|
||||||
|
echo Setup did not complete.
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
Reference in New Issue
Block a user