@nexstone/rift-cli 0.1.1
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/LICENSE +201 -0
- package/bin/run.js +22 -0
- package/dist/commands/algo.d.ts +32 -0
- package/dist/commands/algo.js +719 -0
- package/dist/commands/audit.d.ts +13 -0
- package/dist/commands/audit.js +37 -0
- package/dist/commands/auth-status.d.ts +14 -0
- package/dist/commands/auth-status.js +118 -0
- package/dist/commands/auth.d.ts +14 -0
- package/dist/commands/auth.js +275 -0
- package/dist/commands/backtest.d.ts +26 -0
- package/dist/commands/backtest.js +283 -0
- package/dist/commands/collect/start.d.ts +11 -0
- package/dist/commands/collect/start.js +78 -0
- package/dist/commands/collect/status.d.ts +6 -0
- package/dist/commands/collect/status.js +60 -0
- package/dist/commands/compare.d.ts +16 -0
- package/dist/commands/compare.js +130 -0
- package/dist/commands/config.d.ts +16 -0
- package/dist/commands/config.js +143 -0
- package/dist/commands/cost.d.ts +20 -0
- package/dist/commands/cost.js +104 -0
- package/dist/commands/cross-asset.d.ts +14 -0
- package/dist/commands/cross-asset.js +39 -0
- package/dist/commands/data/fetch.d.ts +15 -0
- package/dist/commands/data/fetch.js +82 -0
- package/dist/commands/data/list.d.ts +6 -0
- package/dist/commands/data/list.js +28 -0
- package/dist/commands/data-inventory.d.ts +9 -0
- package/dist/commands/data-inventory.js +24 -0
- package/dist/commands/deposit.d.ts +10 -0
- package/dist/commands/deposit.js +222 -0
- package/dist/commands/doctor.d.ts +6 -0
- package/dist/commands/doctor.js +87 -0
- package/dist/commands/funding-browser.d.ts +12 -0
- package/dist/commands/funding-browser.js +33 -0
- package/dist/commands/guide.d.ts +6 -0
- package/dist/commands/guide.js +15 -0
- package/dist/commands/home.d.ts +23 -0
- package/dist/commands/home.js +210 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.js +122 -0
- package/dist/commands/install.d.ts +9 -0
- package/dist/commands/install.js +89 -0
- package/dist/commands/interactive.d.ts +17 -0
- package/dist/commands/interactive.js +179 -0
- package/dist/commands/lessons.d.ts +12 -0
- package/dist/commands/lessons.js +33 -0
- package/dist/commands/montecarlo.d.ts +19 -0
- package/dist/commands/montecarlo.js +168 -0
- package/dist/commands/more.d.ts +11 -0
- package/dist/commands/more.js +227 -0
- package/dist/commands/new.d.ts +14 -0
- package/dist/commands/new.js +306 -0
- package/dist/commands/pairs.d.ts +22 -0
- package/dist/commands/pairs.js +147 -0
- package/dist/commands/perp/close.d.ts +12 -0
- package/dist/commands/perp/close.js +57 -0
- package/dist/commands/perp/long.d.ts +14 -0
- package/dist/commands/perp/long.js +38 -0
- package/dist/commands/perp/short.d.ts +14 -0
- package/dist/commands/perp/short.js +27 -0
- package/dist/commands/perp/status.d.ts +9 -0
- package/dist/commands/perp/status.js +26 -0
- package/dist/commands/portfolio/alerts.d.ts +6 -0
- package/dist/commands/portfolio/alerts.js +47 -0
- package/dist/commands/portfolio/backtest.d.ts +12 -0
- package/dist/commands/portfolio/backtest.js +178 -0
- package/dist/commands/portfolio/create.d.ts +7 -0
- package/dist/commands/portfolio/create.js +195 -0
- package/dist/commands/portfolio/start.d.ts +9 -0
- package/dist/commands/portfolio/start.js +64 -0
- package/dist/commands/portfolio/status.d.ts +6 -0
- package/dist/commands/portfolio/status.js +128 -0
- package/dist/commands/portfolio/stop.d.ts +6 -0
- package/dist/commands/portfolio/stop.js +81 -0
- package/dist/commands/portfolio-backtest.d.ts +13 -0
- package/dist/commands/portfolio-backtest.js +37 -0
- package/dist/commands/portfolio-matrix.d.ts +12 -0
- package/dist/commands/portfolio-matrix.js +30 -0
- package/dist/commands/quick-test.d.ts +17 -0
- package/dist/commands/quick-test.js +45 -0
- package/dist/commands/research.d.ts +57 -0
- package/dist/commands/research.js +1976 -0
- package/dist/commands/scout.d.ts +14 -0
- package/dist/commands/scout.js +184 -0
- package/dist/commands/serve.d.ts +9 -0
- package/dist/commands/serve.js +1176 -0
- package/dist/commands/setup/proxy.d.ts +10 -0
- package/dist/commands/setup/proxy.js +267 -0
- package/dist/commands/spot/buy.d.ts +14 -0
- package/dist/commands/spot/buy.js +38 -0
- package/dist/commands/spot/sell.d.ts +14 -0
- package/dist/commands/spot/sell.js +39 -0
- package/dist/commands/strategies/list.d.ts +6 -0
- package/dist/commands/strategies/list.js +34 -0
- package/dist/commands/sweep.d.ts +19 -0
- package/dist/commands/sweep.js +137 -0
- package/dist/commands/sync.d.ts +17 -0
- package/dist/commands/sync.js +54 -0
- package/dist/commands/test-trade.d.ts +6 -0
- package/dist/commands/test-trade.js +97 -0
- package/dist/commands/trade.d.ts +26 -0
- package/dist/commands/trade.js +274 -0
- package/dist/commands/transfer.d.ts +13 -0
- package/dist/commands/transfer.js +65 -0
- package/dist/commands/verify.d.ts +16 -0
- package/dist/commands/verify.js +38 -0
- package/dist/commands/walkforward.d.ts +20 -0
- package/dist/commands/walkforward.js +191 -0
- package/dist/commands/withdraw.d.ts +12 -0
- package/dist/commands/withdraw.js +55 -0
- package/dist/commands/workbench-create.d.ts +13 -0
- package/dist/commands/workbench-create.js +39 -0
- package/dist/lib/account-mode.d.ts +44 -0
- package/dist/lib/account-mode.js +96 -0
- package/dist/lib/analyzer.d.ts +4 -0
- package/dist/lib/analyzer.js +62 -0
- package/dist/lib/base-command.d.ts +35 -0
- package/dist/lib/base-command.js +49 -0
- package/dist/lib/credentials.d.ts +46 -0
- package/dist/lib/credentials.js +137 -0
- package/dist/lib/engine-passthrough.d.ts +28 -0
- package/dist/lib/engine-passthrough.js +60 -0
- package/dist/lib/fees.d.ts +52 -0
- package/dist/lib/fees.js +97 -0
- package/dist/lib/python-bridge.d.ts +24 -0
- package/dist/lib/python-bridge.js +182 -0
- package/dist/lib/setup-status.d.ts +32 -0
- package/dist/lib/setup-status.js +121 -0
- package/dist/lib/status-footer.d.ts +35 -0
- package/dist/lib/status-footer.js +101 -0
- package/dist/lib/tui.d.ts +130 -0
- package/dist/lib/tui.js +300 -0
- package/dist/lib/walletconnect.d.ts +70 -0
- package/dist/lib/walletconnect.js +407 -0
- package/package.json +49 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
2
|
+
import { loadCredentials, hasFullSetup, getAccountAddress } from '../lib/credentials.js';
|
|
3
|
+
import { runEngine } from '../lib/python-bridge.js';
|
|
4
|
+
import { createInterface } from 'node:readline';
|
|
5
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
6
|
+
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
7
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
8
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
9
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
10
|
+
function ask(question) {
|
|
11
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
12
|
+
return new Promise(resolve => {
|
|
13
|
+
rl.question(question, answer => { rl.close(); resolve(answer.trim()); });
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export default class TestTrade extends GatedCommand {
|
|
17
|
+
static description = 'Place a minimum-size test trade to verify exchange connectivity';
|
|
18
|
+
static examples = [
|
|
19
|
+
'$ rift test-trade',
|
|
20
|
+
];
|
|
21
|
+
async run() {
|
|
22
|
+
if (!hasFullSetup()) {
|
|
23
|
+
this.log('');
|
|
24
|
+
this.log(` ${red('Not set up.')} Run ${cyan('rift auth setup')} first.`);
|
|
25
|
+
this.log('');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const creds = loadCredentials();
|
|
29
|
+
this.log('');
|
|
30
|
+
this.log(` ${bold('╔═══════════════════════════════════════════╗')}`);
|
|
31
|
+
this.log(` ${bold('║ RIFT Exchange Test ║')}`);
|
|
32
|
+
this.log(` ${bold('╚═══════════════════════════════════════════╝')}`);
|
|
33
|
+
this.log('');
|
|
34
|
+
this.log(` This will:`);
|
|
35
|
+
this.log(` ${cyan('1.')} Connect to Hyperliquid`);
|
|
36
|
+
this.log(` ${cyan('2.')} Place a minimum-size BTC long ($10)`);
|
|
37
|
+
this.log(` ${cyan('3.')} Verify the stop loss is placed`);
|
|
38
|
+
this.log(` ${cyan('4.')} Wait 10 seconds`);
|
|
39
|
+
this.log(` ${cyan('5.')} Close the position`);
|
|
40
|
+
this.log(` ${cyan('6.')} Report results`);
|
|
41
|
+
this.log('');
|
|
42
|
+
const mainAddr = getAccountAddress(creds);
|
|
43
|
+
this.log(` ${dim('Wallet:')} ${mainAddr}`);
|
|
44
|
+
this.log(` ${dim('Cost:')} ~$0.07 in fees (2x $0.035 per side)`);
|
|
45
|
+
this.log('');
|
|
46
|
+
const confirm = await ask(` ${cyan('Run test?')} ${dim('(yes/no)')}: `);
|
|
47
|
+
if (confirm.toLowerCase() !== 'yes' && confirm.toLowerCase() !== 'y') {
|
|
48
|
+
this.log(dim('\n Cancelled.\n'));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
this.log('');
|
|
52
|
+
const engineArgs = [
|
|
53
|
+
'--private-key', creds.private_key,
|
|
54
|
+
'--account', mainAddr,
|
|
55
|
+
];
|
|
56
|
+
try {
|
|
57
|
+
await runEngine('test-trade', [
|
|
58
|
+
'--account', mainAddr,
|
|
59
|
+
], (msg) => {
|
|
60
|
+
if (msg.type === 'status') {
|
|
61
|
+
const icon = String(msg.msg).includes('✔') ? '' : ' ';
|
|
62
|
+
this.log(`${icon}${msg.msg}`);
|
|
63
|
+
}
|
|
64
|
+
else if (msg.type === 'error') {
|
|
65
|
+
this.log(` ${red('✘')} ${msg.msg}`);
|
|
66
|
+
}
|
|
67
|
+
else if (msg.type === 'result') {
|
|
68
|
+
this.log('');
|
|
69
|
+
this.log(` ${bold('═'.repeat(45))}`);
|
|
70
|
+
this.log('');
|
|
71
|
+
if (msg.success) {
|
|
72
|
+
this.log(` ${green('✔ TEST PASSED')} — Exchange connectivity verified`);
|
|
73
|
+
this.log('');
|
|
74
|
+
this.log(` ${dim('Entry price:')} $${msg.entry_price}`);
|
|
75
|
+
this.log(` ${dim('Exit price:')} $${msg.exit_price}`);
|
|
76
|
+
this.log(` ${dim('P&L:')} $${msg.pnl}`);
|
|
77
|
+
this.log(` ${dim('Stop loss:')} ${msg.stop_placed ? green('✔ placed') : red('✘ failed')}`);
|
|
78
|
+
this.log(` ${dim('Close:')} ${msg.close_success ? green('✔ clean') : red('✘ failed')}`);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
this.log(` ${red('✘ TEST FAILED')} — ${msg.error || 'Unknown error'}`);
|
|
82
|
+
}
|
|
83
|
+
this.log('');
|
|
84
|
+
this.log(` ${bold('═'.repeat(45))}`);
|
|
85
|
+
this.log('');
|
|
86
|
+
}
|
|
87
|
+
}, { HYPERLIQUID_PRIVATE_KEY: creds.private_key });
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
this.log(` ${red('✘')} Test failed: ${error?.message}`);
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
// Clean up private key from environment
|
|
94
|
+
delete process.env.HYPERLIQUID_PRIVATE_KEY;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
2
|
+
export default class Trade extends GatedCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
pair: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
direction: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
size: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
stop: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
leverage: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
};
|
|
15
|
+
private dashboardActive;
|
|
16
|
+
private ds;
|
|
17
|
+
private tickTimer;
|
|
18
|
+
private sessionStart;
|
|
19
|
+
private static readonly DASHBOARD_HEIGHT;
|
|
20
|
+
run(): Promise<void>;
|
|
21
|
+
private executeTrade;
|
|
22
|
+
private handleTradeEvent;
|
|
23
|
+
private renderTradeReplay;
|
|
24
|
+
private clearDashboard;
|
|
25
|
+
private renderDashboard;
|
|
26
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { createInterface } from 'node:readline';
|
|
3
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
4
|
+
import { runEngine } from '../lib/python-bridge.js';
|
|
5
|
+
import { loadCredentials, hasFullSetup, getAccountAddress } from '../lib/credentials.js';
|
|
6
|
+
import { BUILDER_FEE_DISPLAY } from '../lib/fees.js';
|
|
7
|
+
import { green, red, yellow, cyan, bold, dim, greenBg, colorPnl, sparkline, proximityBar, fundingCountdown, createDashboardState, updateDashboardState, } from '../lib/tui.js';
|
|
8
|
+
function ask(question) {
|
|
9
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
10
|
+
return new Promise(resolve => {
|
|
11
|
+
rl.question(question, answer => { rl.close(); resolve(answer.trim()); });
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export default class Trade extends GatedCommand {
|
|
15
|
+
static description = 'Place a manual trade with stop loss and live monitoring';
|
|
16
|
+
static examples = [
|
|
17
|
+
'$ rift trade ETH long --size 500',
|
|
18
|
+
'$ rift trade SOL short --size 1000 --stop 3',
|
|
19
|
+
'$ rift trade',
|
|
20
|
+
];
|
|
21
|
+
static args = {
|
|
22
|
+
pair: Args.string({ description: 'Coin (e.g. BTC, ETH, SOL)', required: false }),
|
|
23
|
+
direction: Args.string({ description: 'long or short', required: false }),
|
|
24
|
+
};
|
|
25
|
+
static flags = {
|
|
26
|
+
size: Flags.integer({ description: 'Position size in USD', default: 0 }),
|
|
27
|
+
stop: Flags.string({ description: 'Stop loss % (default: 2)', default: '2' }),
|
|
28
|
+
leverage: Flags.integer({ description: 'Leverage', default: 1 }),
|
|
29
|
+
// Internal: skip the "Type GO to execute" confirmation prompt. Used
|
|
30
|
+
// by `rift perp long/short` which already require the user to type
|
|
31
|
+
// the direction explicitly in the verb (e.g. `rift perp long BTC
|
|
32
|
+
// --size 10`), so the second confirmation is redundant. The flag is
|
|
33
|
+
// intentionally undocumented in the user-facing description — it
|
|
34
|
+
// exists for internal delegation, not for users to type directly.
|
|
35
|
+
yes: Flags.boolean({ description: 'Skip the GO confirmation prompt (for internal delegation)', default: false, hidden: true }),
|
|
36
|
+
};
|
|
37
|
+
dashboardActive = false;
|
|
38
|
+
ds = createDashboardState();
|
|
39
|
+
tickTimer = null;
|
|
40
|
+
sessionStart = 0;
|
|
41
|
+
static DASHBOARD_HEIGHT = 14;
|
|
42
|
+
async run() {
|
|
43
|
+
const { args, flags } = await this.parse(Trade);
|
|
44
|
+
if (!hasFullSetup()) {
|
|
45
|
+
this.log(`\n ${red('✘')} Trade requires wallet setup. Run: ${cyan('rift auth setup')}\n`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const creds = loadCredentials();
|
|
49
|
+
if (!creds) {
|
|
50
|
+
this.log(`\n ${red('✘')} No credentials.\n`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Interactive mode if no args
|
|
54
|
+
let pair = args.pair || '';
|
|
55
|
+
let direction = args.direction || '';
|
|
56
|
+
let sizeUsd = flags.size || 0;
|
|
57
|
+
const stopPct = parseFloat(flags.stop) / 100;
|
|
58
|
+
if (!pair) {
|
|
59
|
+
this.log('');
|
|
60
|
+
this.log(` ${bold('Quick Trade')}`);
|
|
61
|
+
this.log(` ${dim('─'.repeat(40))}`);
|
|
62
|
+
this.log('');
|
|
63
|
+
pair = (await ask(` ${cyan('Coin')} ${dim('(BTC)')}: `)) || 'BTC';
|
|
64
|
+
}
|
|
65
|
+
if (!direction) {
|
|
66
|
+
direction = await ask(` ${cyan('Direction')} ${dim('(long/short)')}: `);
|
|
67
|
+
if (!direction || !['long', 'short'].includes(direction.toLowerCase())) {
|
|
68
|
+
this.log(` ${dim('Cancelled.')}`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!sizeUsd) {
|
|
73
|
+
const sizeStr = await ask(` ${cyan('Size in USD')} ${dim('(500)')}: `);
|
|
74
|
+
sizeUsd = parseInt(sizeStr) || 500;
|
|
75
|
+
}
|
|
76
|
+
pair = pair.toUpperCase();
|
|
77
|
+
direction = direction.toLowerCase();
|
|
78
|
+
const dirColor = direction === 'long' ? green : red;
|
|
79
|
+
// Confirmation
|
|
80
|
+
this.log('');
|
|
81
|
+
this.log(` ${bold('╔════════════════════════════════════════╗')}`);
|
|
82
|
+
this.log(` ${bold('║')} ${dirColor(direction.toUpperCase())} ${bold(pair)} $${sizeUsd.toLocaleString()}${' '.repeat(Math.max(1, 22 - pair.length - String(sizeUsd).length))}${bold('║')}`);
|
|
83
|
+
this.log(` ${bold('║')} Stop: ${(stopPct * 100).toFixed(1)}% Fee: ${BUILDER_FEE_DISPLAY}${' '.repeat(17)}${bold('║')}`);
|
|
84
|
+
this.log(` ${bold('║')} Leverage: ${flags.leverage}x${' '.repeat(27)}${bold('║')}`);
|
|
85
|
+
this.log(` ${bold('╚════════════════════════════════════════╝')}`);
|
|
86
|
+
this.log('');
|
|
87
|
+
if (!flags.yes) {
|
|
88
|
+
const confirm = await ask(` ${cyan('Type "GO" to execute')}: `);
|
|
89
|
+
if (confirm !== 'GO') {
|
|
90
|
+
this.log(dim('\n Cancelled.\n'));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
this.log('');
|
|
95
|
+
await this.executeTrade(pair, direction, sizeUsd, stopPct, flags.leverage, creds);
|
|
96
|
+
}
|
|
97
|
+
async executeTrade(pair, direction, sizeUsd, stopPct, leverage, creds) {
|
|
98
|
+
this.sessionStart = Date.now();
|
|
99
|
+
this.dashboardActive = false;
|
|
100
|
+
this.ds = createDashboardState();
|
|
101
|
+
this.ds.isLive = true;
|
|
102
|
+
process.env.HYPERLIQUID_PRIVATE_KEY = creds.private_key;
|
|
103
|
+
const engineArgs = [
|
|
104
|
+
pair, direction,
|
|
105
|
+
'--size', String(sizeUsd),
|
|
106
|
+
'--stop', String(stopPct),
|
|
107
|
+
'--leverage', String(leverage),
|
|
108
|
+
'--account', getAccountAddress(creds),
|
|
109
|
+
];
|
|
110
|
+
// Dashboard tick
|
|
111
|
+
this.tickTimer = setInterval(() => {
|
|
112
|
+
if (this.dashboardActive)
|
|
113
|
+
this.renderDashboard();
|
|
114
|
+
}, 200);
|
|
115
|
+
// Ctrl+C closes position (not detach — this is manual trading)
|
|
116
|
+
const { getEngineProcess } = await import('../lib/python-bridge.js');
|
|
117
|
+
let enginePromise = null;
|
|
118
|
+
const sigintHandler = () => {
|
|
119
|
+
if (enginePromise) {
|
|
120
|
+
const proc = getEngineProcess(enginePromise);
|
|
121
|
+
if (proc && !proc.killed)
|
|
122
|
+
proc.kill('SIGTERM');
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
process.on('SIGINT', sigintHandler);
|
|
126
|
+
try {
|
|
127
|
+
enginePromise = runEngine('manual-trade', engineArgs, (msg) => {
|
|
128
|
+
const state = msg.state;
|
|
129
|
+
if (msg.type === 'status') {
|
|
130
|
+
if (!this.dashboardActive) {
|
|
131
|
+
this.log(` ${dim(String(msg.msg))}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else if (msg.type === 'trade') {
|
|
135
|
+
this.handleTradeEvent(msg);
|
|
136
|
+
}
|
|
137
|
+
else if (msg.type === 'heartbeat') {
|
|
138
|
+
if (state) {
|
|
139
|
+
updateDashboardState(this.ds, state, msg, 'heartbeat');
|
|
140
|
+
this.renderDashboard();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else if (msg.type === 'shutdown') {
|
|
144
|
+
if (this.tickTimer) {
|
|
145
|
+
clearInterval(this.tickTimer);
|
|
146
|
+
this.tickTimer = null;
|
|
147
|
+
}
|
|
148
|
+
if (this.dashboardActive)
|
|
149
|
+
this.clearDashboard();
|
|
150
|
+
if (msg.trade_replay) {
|
|
151
|
+
this.renderTradeReplay(msg.trade_replay);
|
|
152
|
+
}
|
|
153
|
+
// Shareable card
|
|
154
|
+
const card = msg.shareable_card;
|
|
155
|
+
if (card) {
|
|
156
|
+
this.log('');
|
|
157
|
+
this.log(dim(' Shareable:'));
|
|
158
|
+
this.log('');
|
|
159
|
+
for (const line of card.split('\n')) {
|
|
160
|
+
this.log(` ${line}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
this.log('');
|
|
164
|
+
}
|
|
165
|
+
else if (msg.type === 'error') {
|
|
166
|
+
this.log(` ${red('✘')} ${msg.msg}`);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
await enginePromise;
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
const errMsg = String(error?.message ?? '');
|
|
173
|
+
if (!errMsg.includes('SIGTERM') && !errMsg.includes('SIGINT') && !errMsg.includes('null')) {
|
|
174
|
+
this.log(` ${red('✘')} ${errMsg.split('\n')[0] || 'Unknown error'}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
if (this.tickTimer) {
|
|
179
|
+
clearInterval(this.tickTimer);
|
|
180
|
+
this.tickTimer = null;
|
|
181
|
+
}
|
|
182
|
+
process.removeListener('SIGINT', sigintHandler);
|
|
183
|
+
delete process.env.HYPERLIQUID_PRIVATE_KEY;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
handleTradeEvent(msg) {
|
|
187
|
+
const action = msg.action;
|
|
188
|
+
if (this.dashboardActive)
|
|
189
|
+
this.clearDashboard();
|
|
190
|
+
if (action === 'open') {
|
|
191
|
+
const side = msg.side ?? '';
|
|
192
|
+
const price = msg.price ?? 0;
|
|
193
|
+
const size = msg.size ?? 0;
|
|
194
|
+
const sl = msg.stop_loss ?? 0;
|
|
195
|
+
const sideColor = side === 'long' ? green : red;
|
|
196
|
+
this.log(` ${greenBg(' TRADE ')} ${sideColor('▶')} ${bold(side.toUpperCase())} ${size.toFixed(4)} @ $${price.toLocaleString()}`);
|
|
197
|
+
this.log(` ${dim('Stop:')} $${sl.toLocaleString()} ${dim('(on Hyperliquid)')}`);
|
|
198
|
+
this.log(` ${dim('Press Ctrl+C to close position')}`);
|
|
199
|
+
this.log('');
|
|
200
|
+
}
|
|
201
|
+
else if (action === 'stop_loss') {
|
|
202
|
+
this.log(` ${red('✘')} Stop loss triggered ${dim('(Hyperliquid server-side)')}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
renderTradeReplay(replay) {
|
|
206
|
+
const isWin = replay.result === 'WIN';
|
|
207
|
+
const resultColor = isWin ? green : red;
|
|
208
|
+
const side = replay.side.toUpperCase();
|
|
209
|
+
this.log('');
|
|
210
|
+
this.log(` ${greenBg(' RESULT ')} ${resultColor(side)} — ${resultColor(replay.result)}`);
|
|
211
|
+
this.log(` Entry: $${replay.entry_price} Exit: $${replay.exit_price}`);
|
|
212
|
+
this.log(` ${bold('P&L:')} ${colorPnl(replay.total_pnl)} (${colorPnl(replay.pnl_pct, '%')})`);
|
|
213
|
+
if (replay.funding_pnl)
|
|
214
|
+
this.log(` Funding: ${colorPnl(replay.funding_pnl)}`);
|
|
215
|
+
this.log(` Duration: ${replay.duration}`);
|
|
216
|
+
}
|
|
217
|
+
clearDashboard() {
|
|
218
|
+
if (this.dashboardActive) {
|
|
219
|
+
const h = Trade.DASHBOARD_HEIGHT;
|
|
220
|
+
process.stdout.write(`\x1b[${h}A`);
|
|
221
|
+
for (let i = 0; i < h; i++)
|
|
222
|
+
process.stdout.write('\x1b[2K\n');
|
|
223
|
+
process.stdout.write(`\x1b[${h}A`);
|
|
224
|
+
this.dashboardActive = false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
renderDashboard() {
|
|
228
|
+
const ds = this.ds;
|
|
229
|
+
const pos = ds.position;
|
|
230
|
+
const elapsed = Math.floor((Date.now() - this.sessionStart) / 1000);
|
|
231
|
+
const mins = Math.floor(elapsed / 60);
|
|
232
|
+
const secs = elapsed % 60;
|
|
233
|
+
const duration = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
|
|
234
|
+
const lines = [];
|
|
235
|
+
// Header
|
|
236
|
+
lines.push(` ${greenBg(' TRADE ')} ${dim(ds.pair || '')} ${dim('│')} ${dim(duration)} ${dim('│ Ctrl+C to close')}`);
|
|
237
|
+
// Price
|
|
238
|
+
const priceStr = ds.price > 0 ? `$${ds.price.toFixed(2)}` : '$...';
|
|
239
|
+
const deltaRaw = ds.priceDelta > 0 ? green(`+$${ds.priceDelta.toFixed(0)}`)
|
|
240
|
+
: ds.priceDelta < 0 ? red(`-$${Math.abs(ds.priceDelta).toFixed(0)}`)
|
|
241
|
+
: dim('$0');
|
|
242
|
+
const chart = sparkline(ds.priceHistory, 28);
|
|
243
|
+
lines.push(` ${bold(priceStr)} ${deltaRaw} ${chart}`);
|
|
244
|
+
lines.push('');
|
|
245
|
+
// Position
|
|
246
|
+
if (pos) {
|
|
247
|
+
const sideColor = pos.side === 'long' ? green : red;
|
|
248
|
+
lines.push(` ${sideColor('●')} ${sideColor(String(pos.side).toUpperCase())} ${pos.size?.toFixed(4)} @ $${pos.entry_price?.toLocaleString()}`);
|
|
249
|
+
lines.push(` ${dim('Unrealized:')} ${colorPnl(ds.unrealizedPnl)} ${dim('Funding:')} ${colorPnl(ds.totalFunding)}`);
|
|
250
|
+
if (ds.stopProximity > 0.1) {
|
|
251
|
+
const proxLabel = ds.stopProximity > 0.8 ? red('DANGER') : ds.stopProximity > 0.5 ? yellow('CAUTION') : dim('safe');
|
|
252
|
+
lines.push(` ${dim('SL')} ${proximityBar(ds.stopProximity)} ${proxLabel}`);
|
|
253
|
+
}
|
|
254
|
+
if (ds.fundingCountdownMin > 0) {
|
|
255
|
+
lines.push(` ${fundingCountdown(ds.fundingCountdownMin, ds.predictedFunding)}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
lines.push('');
|
|
259
|
+
lines.push(` ${dim('EQUITY')} ${bold('$' + ds.totalEquity.toLocaleString())} ${colorPnl(ds.totalPnlPct, '%')}`);
|
|
260
|
+
// Fixed height
|
|
261
|
+
const H = Trade.DASHBOARD_HEIGHT;
|
|
262
|
+
while (lines.length < H)
|
|
263
|
+
lines.push('');
|
|
264
|
+
if (lines.length > H)
|
|
265
|
+
lines.length = H;
|
|
266
|
+
if (this.dashboardActive) {
|
|
267
|
+
process.stdout.write(`\x1b[${H}A`);
|
|
268
|
+
}
|
|
269
|
+
for (const line of lines) {
|
|
270
|
+
process.stdout.write(`\x1b[2K${line}\n`);
|
|
271
|
+
}
|
|
272
|
+
this.dashboardActive = true;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
2
|
+
export default class Transfer extends GatedCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
amount: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
'to-perps': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
'to-spot': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
3
|
+
import { requestTransfer, postToHyperliquid, getExistingSession } from '../lib/walletconnect.js';
|
|
4
|
+
import { loadCredentials } from '../lib/credentials.js';
|
|
5
|
+
import { green, red, cyan, dim } from '../lib/tui.js';
|
|
6
|
+
export default class Transfer extends GatedCommand {
|
|
7
|
+
static description = 'Transfer USDC between spot and perps on Hyperliquid';
|
|
8
|
+
static examples = [
|
|
9
|
+
'$ rift transfer 100 --to-perps',
|
|
10
|
+
'$ rift transfer 50 --to-spot',
|
|
11
|
+
];
|
|
12
|
+
static args = {
|
|
13
|
+
amount: Args.string({ description: 'USDC amount to transfer', required: true }),
|
|
14
|
+
};
|
|
15
|
+
static flags = {
|
|
16
|
+
'to-perps': Flags.boolean({ description: 'Transfer from Spot → Perps', default: false }),
|
|
17
|
+
'to-spot': Flags.boolean({ description: 'Transfer from Perps → Spot', default: false }),
|
|
18
|
+
};
|
|
19
|
+
async run() {
|
|
20
|
+
const { args, flags } = await this.parse(Transfer);
|
|
21
|
+
const amount = args.amount;
|
|
22
|
+
const isMainnet = true;
|
|
23
|
+
// Determine direction
|
|
24
|
+
let toPerp = true;
|
|
25
|
+
if (flags['to-spot']) {
|
|
26
|
+
toPerp = false;
|
|
27
|
+
}
|
|
28
|
+
else if (!flags['to-perps'] && !flags['to-spot']) {
|
|
29
|
+
// Default to to-perps if neither specified
|
|
30
|
+
toPerp = true;
|
|
31
|
+
}
|
|
32
|
+
const direction = toPerp ? 'Spot → Perps' : 'Perps → Spot';
|
|
33
|
+
const creds = loadCredentials();
|
|
34
|
+
if (!creds) {
|
|
35
|
+
this.log(` ${red('✘')} No wallet configured. Run: ${cyan('rift auth setup')}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// Check for existing WalletConnect session
|
|
39
|
+
const session = await getExistingSession();
|
|
40
|
+
if (!session) {
|
|
41
|
+
this.log(` ${red('✘')} No wallet session. Run: ${cyan('rift auth setup')} to reconnect.`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this.log('');
|
|
45
|
+
this.log(` Transferring $${amount} (${direction})...`);
|
|
46
|
+
this.log(` ${dim('→ Approve in your wallet (check your phone)')}`);
|
|
47
|
+
this.log('');
|
|
48
|
+
const result = await requestTransfer(amount, toPerp, isMainnet);
|
|
49
|
+
if (!result.success) {
|
|
50
|
+
this.log(` ${red('✘')} Transfer failed: ${result.error}`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Post to Hyperliquid
|
|
54
|
+
try {
|
|
55
|
+
const response = await postToHyperliquid(result.action, result.signature, result.nonce, isMainnet);
|
|
56
|
+
this.log(` ${green('✔')} Transfer complete`);
|
|
57
|
+
this.log(` ${dim(`$${amount} moved ${direction}`)}`);
|
|
58
|
+
this.log(` ${dim('Run: rift balance to verify')}`);
|
|
59
|
+
this.log('');
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
this.log(` ${red('✘')} Failed to submit transfer: ${error.message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
2
|
+
export default class Verify extends GatedCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
strategy: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
pair: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
tf: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
from: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
to: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
};
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
3
|
+
import { passthroughToEngine } from '../lib/engine-passthrough.js';
|
|
4
|
+
export default class Verify extends GatedCommand {
|
|
5
|
+
static description = 'Verify a strategy beats buy-and-hold over a date range — sanity check before going live';
|
|
6
|
+
static examples = [
|
|
7
|
+
'$ rift verify trend_follow',
|
|
8
|
+
'$ rift verify trend_follow --pair BTC --tf 4h --from 2024-01-01 --to 2024-12-31',
|
|
9
|
+
];
|
|
10
|
+
static args = {
|
|
11
|
+
strategy: Args.string({ description: 'Strategy name', required: true }),
|
|
12
|
+
};
|
|
13
|
+
static flags = {
|
|
14
|
+
pair: Flags.string({ description: 'Trading pair', default: 'BTC' }),
|
|
15
|
+
tf: Flags.string({ description: 'Timeframe', default: '' }),
|
|
16
|
+
from: Flags.string({ description: 'Start date YYYY-MM-DD', default: '' }),
|
|
17
|
+
to: Flags.string({ description: 'End date YYYY-MM-DD', default: '' }),
|
|
18
|
+
json: Flags.boolean({ description: 'Emit raw JSON only', default: false }),
|
|
19
|
+
};
|
|
20
|
+
async run() {
|
|
21
|
+
const { args, flags } = await this.parse(Verify);
|
|
22
|
+
const engineArgs = [args.strategy, '--pair', flags.pair];
|
|
23
|
+
if (flags.tf)
|
|
24
|
+
engineArgs.push('--tf', flags.tf);
|
|
25
|
+
if (flags.from)
|
|
26
|
+
engineArgs.push('--from', flags.from);
|
|
27
|
+
if (flags.to)
|
|
28
|
+
engineArgs.push('--to', flags.to);
|
|
29
|
+
await passthroughToEngine({
|
|
30
|
+
command: 'verify',
|
|
31
|
+
args: engineArgs,
|
|
32
|
+
log: (m) => this.log(m),
|
|
33
|
+
error: (m) => this.error(m),
|
|
34
|
+
exit: (c) => this.exit(c),
|
|
35
|
+
jsonOnly: flags.json,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { GatedCommand } from '../lib/base-command.js';
|
|
2
|
+
export default class WalkForward extends GatedCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static aliases: string[];
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static args: {
|
|
7
|
+
strategy: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
pair: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
tf: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
wf: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
equity: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
leverage: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
};
|
|
16
|
+
run(): Promise<void>;
|
|
17
|
+
private renderResult;
|
|
18
|
+
private row;
|
|
19
|
+
private rowColored;
|
|
20
|
+
}
|