@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.
Files changed (137) hide show
  1. package/LICENSE +201 -0
  2. package/bin/run.js +22 -0
  3. package/dist/commands/algo.d.ts +32 -0
  4. package/dist/commands/algo.js +719 -0
  5. package/dist/commands/audit.d.ts +13 -0
  6. package/dist/commands/audit.js +37 -0
  7. package/dist/commands/auth-status.d.ts +14 -0
  8. package/dist/commands/auth-status.js +118 -0
  9. package/dist/commands/auth.d.ts +14 -0
  10. package/dist/commands/auth.js +275 -0
  11. package/dist/commands/backtest.d.ts +26 -0
  12. package/dist/commands/backtest.js +283 -0
  13. package/dist/commands/collect/start.d.ts +11 -0
  14. package/dist/commands/collect/start.js +78 -0
  15. package/dist/commands/collect/status.d.ts +6 -0
  16. package/dist/commands/collect/status.js +60 -0
  17. package/dist/commands/compare.d.ts +16 -0
  18. package/dist/commands/compare.js +130 -0
  19. package/dist/commands/config.d.ts +16 -0
  20. package/dist/commands/config.js +143 -0
  21. package/dist/commands/cost.d.ts +20 -0
  22. package/dist/commands/cost.js +104 -0
  23. package/dist/commands/cross-asset.d.ts +14 -0
  24. package/dist/commands/cross-asset.js +39 -0
  25. package/dist/commands/data/fetch.d.ts +15 -0
  26. package/dist/commands/data/fetch.js +82 -0
  27. package/dist/commands/data/list.d.ts +6 -0
  28. package/dist/commands/data/list.js +28 -0
  29. package/dist/commands/data-inventory.d.ts +9 -0
  30. package/dist/commands/data-inventory.js +24 -0
  31. package/dist/commands/deposit.d.ts +10 -0
  32. package/dist/commands/deposit.js +222 -0
  33. package/dist/commands/doctor.d.ts +6 -0
  34. package/dist/commands/doctor.js +87 -0
  35. package/dist/commands/funding-browser.d.ts +12 -0
  36. package/dist/commands/funding-browser.js +33 -0
  37. package/dist/commands/guide.d.ts +6 -0
  38. package/dist/commands/guide.js +15 -0
  39. package/dist/commands/home.d.ts +23 -0
  40. package/dist/commands/home.js +210 -0
  41. package/dist/commands/init.d.ts +7 -0
  42. package/dist/commands/init.js +122 -0
  43. package/dist/commands/install.d.ts +9 -0
  44. package/dist/commands/install.js +89 -0
  45. package/dist/commands/interactive.d.ts +17 -0
  46. package/dist/commands/interactive.js +179 -0
  47. package/dist/commands/lessons.d.ts +12 -0
  48. package/dist/commands/lessons.js +33 -0
  49. package/dist/commands/montecarlo.d.ts +19 -0
  50. package/dist/commands/montecarlo.js +168 -0
  51. package/dist/commands/more.d.ts +11 -0
  52. package/dist/commands/more.js +227 -0
  53. package/dist/commands/new.d.ts +14 -0
  54. package/dist/commands/new.js +306 -0
  55. package/dist/commands/pairs.d.ts +22 -0
  56. package/dist/commands/pairs.js +147 -0
  57. package/dist/commands/perp/close.d.ts +12 -0
  58. package/dist/commands/perp/close.js +57 -0
  59. package/dist/commands/perp/long.d.ts +14 -0
  60. package/dist/commands/perp/long.js +38 -0
  61. package/dist/commands/perp/short.d.ts +14 -0
  62. package/dist/commands/perp/short.js +27 -0
  63. package/dist/commands/perp/status.d.ts +9 -0
  64. package/dist/commands/perp/status.js +26 -0
  65. package/dist/commands/portfolio/alerts.d.ts +6 -0
  66. package/dist/commands/portfolio/alerts.js +47 -0
  67. package/dist/commands/portfolio/backtest.d.ts +12 -0
  68. package/dist/commands/portfolio/backtest.js +178 -0
  69. package/dist/commands/portfolio/create.d.ts +7 -0
  70. package/dist/commands/portfolio/create.js +195 -0
  71. package/dist/commands/portfolio/start.d.ts +9 -0
  72. package/dist/commands/portfolio/start.js +64 -0
  73. package/dist/commands/portfolio/status.d.ts +6 -0
  74. package/dist/commands/portfolio/status.js +128 -0
  75. package/dist/commands/portfolio/stop.d.ts +6 -0
  76. package/dist/commands/portfolio/stop.js +81 -0
  77. package/dist/commands/portfolio-backtest.d.ts +13 -0
  78. package/dist/commands/portfolio-backtest.js +37 -0
  79. package/dist/commands/portfolio-matrix.d.ts +12 -0
  80. package/dist/commands/portfolio-matrix.js +30 -0
  81. package/dist/commands/quick-test.d.ts +17 -0
  82. package/dist/commands/quick-test.js +45 -0
  83. package/dist/commands/research.d.ts +57 -0
  84. package/dist/commands/research.js +1976 -0
  85. package/dist/commands/scout.d.ts +14 -0
  86. package/dist/commands/scout.js +184 -0
  87. package/dist/commands/serve.d.ts +9 -0
  88. package/dist/commands/serve.js +1176 -0
  89. package/dist/commands/setup/proxy.d.ts +10 -0
  90. package/dist/commands/setup/proxy.js +267 -0
  91. package/dist/commands/spot/buy.d.ts +14 -0
  92. package/dist/commands/spot/buy.js +38 -0
  93. package/dist/commands/spot/sell.d.ts +14 -0
  94. package/dist/commands/spot/sell.js +39 -0
  95. package/dist/commands/strategies/list.d.ts +6 -0
  96. package/dist/commands/strategies/list.js +34 -0
  97. package/dist/commands/sweep.d.ts +19 -0
  98. package/dist/commands/sweep.js +137 -0
  99. package/dist/commands/sync.d.ts +17 -0
  100. package/dist/commands/sync.js +54 -0
  101. package/dist/commands/test-trade.d.ts +6 -0
  102. package/dist/commands/test-trade.js +97 -0
  103. package/dist/commands/trade.d.ts +26 -0
  104. package/dist/commands/trade.js +274 -0
  105. package/dist/commands/transfer.d.ts +13 -0
  106. package/dist/commands/transfer.js +65 -0
  107. package/dist/commands/verify.d.ts +16 -0
  108. package/dist/commands/verify.js +38 -0
  109. package/dist/commands/walkforward.d.ts +20 -0
  110. package/dist/commands/walkforward.js +191 -0
  111. package/dist/commands/withdraw.d.ts +12 -0
  112. package/dist/commands/withdraw.js +55 -0
  113. package/dist/commands/workbench-create.d.ts +13 -0
  114. package/dist/commands/workbench-create.js +39 -0
  115. package/dist/lib/account-mode.d.ts +44 -0
  116. package/dist/lib/account-mode.js +96 -0
  117. package/dist/lib/analyzer.d.ts +4 -0
  118. package/dist/lib/analyzer.js +62 -0
  119. package/dist/lib/base-command.d.ts +35 -0
  120. package/dist/lib/base-command.js +49 -0
  121. package/dist/lib/credentials.d.ts +46 -0
  122. package/dist/lib/credentials.js +137 -0
  123. package/dist/lib/engine-passthrough.d.ts +28 -0
  124. package/dist/lib/engine-passthrough.js +60 -0
  125. package/dist/lib/fees.d.ts +52 -0
  126. package/dist/lib/fees.js +97 -0
  127. package/dist/lib/python-bridge.d.ts +24 -0
  128. package/dist/lib/python-bridge.js +182 -0
  129. package/dist/lib/setup-status.d.ts +32 -0
  130. package/dist/lib/setup-status.js +121 -0
  131. package/dist/lib/status-footer.d.ts +35 -0
  132. package/dist/lib/status-footer.js +101 -0
  133. package/dist/lib/tui.d.ts +130 -0
  134. package/dist/lib/tui.js +300 -0
  135. package/dist/lib/walletconnect.d.ts +70 -0
  136. package/dist/lib/walletconnect.js +407 -0
  137. package/package.json +49 -0
@@ -0,0 +1,147 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { GatedCommand } from '../lib/base-command.js';
3
+ import { runEngine } from '../lib/python-bridge.js';
4
+ const green = (s) => `\x1b[32m${s}\x1b[0m`;
5
+ const red = (s) => `\x1b[31m${s}\x1b[0m`;
6
+ const yellow = (s) => `\x1b[33m${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 colorNum(val, suffix = '') {
11
+ const str = `${val}${suffix}`;
12
+ if (val > 0)
13
+ return green(str);
14
+ if (val < 0)
15
+ return red(str);
16
+ return yellow(str);
17
+ }
18
+ export default class Pairs extends GatedCommand {
19
+ static description = 'Backtest a pairs/spread trade between two assets (e.g. BTC/ETH)';
20
+ // `pairs-backtest` is the engine command name and was the previous
21
+ // wrapper name; alias kept so scripts and docs that referenced it
22
+ // continue to work.
23
+ static aliases = ['pairs-backtest'];
24
+ static examples = [
25
+ '$ rift pairs --a BTC --b ETH --tf 1h',
26
+ '$ rift pairs --a BTC --b ETH --entry-z 2.5 --lookback 336',
27
+ '$ rift pairs --a BTC --b ETH --json # raw JSON for pipelines',
28
+ ];
29
+ static flags = {
30
+ a: Flags.string({ description: 'First asset', default: 'BTC' }),
31
+ b: Flags.string({ description: 'Second asset', default: 'ETH' }),
32
+ tf: Flags.string({ description: 'Timeframe', default: '1h' }),
33
+ equity: Flags.integer({ description: 'Starting equity', default: 10000 }),
34
+ lookback: Flags.integer({ description: 'Rolling z-score window (candles)', default: 168 }),
35
+ 'entry-z': Flags.string({ description: 'Z-score entry threshold', default: '2.0' }),
36
+ 'exit-z': Flags.string({ description: 'Z-score exit threshold', default: '0.5' }),
37
+ 'stop-z': Flags.string({ description: 'Z-score stop loss', default: '4.0' }),
38
+ 'max-hold': Flags.integer({ description: 'Max hold time (candles)', default: 72 }),
39
+ json: Flags.boolean({ description: 'Emit raw JSON result instead of the rendered panel', default: false }),
40
+ };
41
+ async run() {
42
+ const { flags } = await this.parse(Pairs);
43
+ if (!flags.json) {
44
+ this.log('');
45
+ this.log(` ${bold('Pairs Trading Backtest')}`);
46
+ this.log(` ${dim(`${flags.a}/${flags.b} spread on ${flags.tf} — z-score entry: ${flags['entry-z']}, exit: ${flags['exit-z']}`)}`);
47
+ this.log('');
48
+ }
49
+ const engineArgs = [
50
+ '--a', flags.a,
51
+ '--b', flags.b,
52
+ '--tf', flags.tf,
53
+ '--equity', String(flags.equity),
54
+ '--lookback', String(flags.lookback),
55
+ '--entry-z', flags['entry-z'],
56
+ '--exit-z', flags['exit-z'],
57
+ '--stop-z', flags['stop-z'],
58
+ '--max-hold', String(flags['max-hold']),
59
+ ];
60
+ await runEngine('pairs-backtest', engineArgs, (msg) => {
61
+ if (flags.json) {
62
+ // JSON mode: emit the result payload only, silently consume
63
+ // progress/status so the output is pipe-safe.
64
+ if (msg.type === 'result') {
65
+ const { type: _t, ...rest } = msg;
66
+ this.log(JSON.stringify(rest, null, 2));
67
+ }
68
+ else if (msg.type === 'error') {
69
+ this.error(msg.msg);
70
+ }
71
+ return;
72
+ }
73
+ if (msg.type === 'progress' && msg.msg) {
74
+ process.stdout.write(`\r ${dim(String(msg.msg))}${''.padEnd(20)}`);
75
+ }
76
+ else if (msg.type === 'result') {
77
+ process.stdout.write('\r' + ' '.repeat(80) + '\r');
78
+ this.renderResult(msg);
79
+ }
80
+ else if (msg.type === 'error') {
81
+ process.stdout.write('\r' + ' '.repeat(80) + '\r');
82
+ this.error(msg.msg);
83
+ }
84
+ });
85
+ }
86
+ renderResult(msg) {
87
+ const w = 55;
88
+ const hr = '─'.repeat(w - 2);
89
+ this.log(` ${dim('┌' + hr + '┐')}`);
90
+ this.log(` ${dim('│')} ${bold('PAIRS TRADING RESULTS')}${' '.repeat(w - 24)}${dim('│')}`);
91
+ this.log(` ${dim('├' + hr + '┤')}`);
92
+ this.log(this.row('Spread', `${msg.asset_a}/${msg.asset_b}`, w));
93
+ this.log(this.row('Interval', String(msg.interval), w));
94
+ this.log(this.row('Avg Hold', `${msg.avg_hold_candles}h`, w));
95
+ this.log(` ${dim('├' + hr + '┤')}`);
96
+ this.log(this.row('Initial', `$${Number(msg.initial_equity).toLocaleString()}`, w));
97
+ this.log(this.row('Final', `$${Number(msg.final_equity).toLocaleString()}`, w));
98
+ this.log(this.rowColored('Return', msg.total_return_pct, '%', w));
99
+ this.log(` ${dim('├' + hr + '┤')}`);
100
+ this.log(this.row('Trades', String(msg.num_trades), w));
101
+ this.log(this.row('Win Rate', `${msg.win_rate}%`, w, msg.win_rate >= 50 ? 'green' : 'red'));
102
+ this.log(this.rowColored('Avg Win', msg.avg_win_pct, '%', w));
103
+ this.log(this.rowColored('Avg Loss', msg.avg_loss_pct, '%', w));
104
+ this.log(` ${dim('├' + hr + '┤')}`);
105
+ this.log(this.rowColored('Max Drawdown', msg.max_drawdown_pct, '%', w));
106
+ this.log(this.row('Sharpe Ratio', String(msg.sharpe_ratio), w, msg.sharpe_ratio > 0.5 ? 'green' : msg.sharpe_ratio > 0 ? 'yellow' : 'red'));
107
+ this.log(this.row('Profit Factor', String(msg.profit_factor), w, msg.profit_factor > 1.5 ? 'green' : msg.profit_factor > 1 ? 'yellow' : 'red'));
108
+ this.log(this.rowColored('Funding P&L', msg.total_funding, '', w));
109
+ this.log(` ${dim('└' + hr + '┘')}`);
110
+ // Interpretation
111
+ this.log('');
112
+ const sharpe = msg.sharpe_ratio;
113
+ const pf = msg.profit_factor;
114
+ const dd = msg.max_drawdown_pct;
115
+ if (sharpe > 0.5 && pf > 1.5 && dd > -10) {
116
+ this.log(` ${green('Strong pairs trade.')} Positive Sharpe with controlled drawdown.`);
117
+ this.log(` ${dim('The spread shows mean-reverting behavior — the edge is structural.')}`);
118
+ }
119
+ else if (sharpe > 0 && pf > 1) {
120
+ this.log(` ${yellow('Moderate pairs trade.')} Positive but consider optimizing parameters.`);
121
+ }
122
+ else {
123
+ this.log(` ${red('Weak or negative.')} The spread may not be mean-reverting in this period.`);
124
+ }
125
+ this.log('');
126
+ }
127
+ row(label, value, width, color) {
128
+ const labelStr = ` ${label}:`;
129
+ const cleanVal = value.replace(/\x1b\[[0-9;]*m/g, '');
130
+ const padding = width - labelStr.length - cleanVal.length - 3;
131
+ let coloredVal = value;
132
+ if (color === 'green')
133
+ coloredVal = green(value);
134
+ else if (color === 'red')
135
+ coloredVal = red(value);
136
+ else if (color === 'yellow')
137
+ coloredVal = yellow(value);
138
+ return ` ${dim('│')}${labelStr}${' '.repeat(Math.max(1, padding))}${coloredVal} ${dim('│')}`;
139
+ }
140
+ rowColored(label, value, suffix, width) {
141
+ const coloredStr = colorNum(value, suffix);
142
+ const cleanStr = `${value}${suffix}`;
143
+ const labelStr = ` ${label}:`;
144
+ const padding = width - labelStr.length - cleanStr.length - 3;
145
+ return ` ${dim('│')}${labelStr}${' '.repeat(Math.max(1, padding))}${coloredStr} ${dim('│')}`;
146
+ }
147
+ }
@@ -0,0 +1,12 @@
1
+ import { GatedCommand } from '../../lib/base-command.js';
2
+ export default class PerpClose extends GatedCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ coin: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,57 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { GatedCommand } from '../../lib/base-command.js';
3
+ import { passthroughToEngine } from '../../lib/engine-passthrough.js';
4
+ import { loadCredentials, hasFullSetup, getAccountAddress } from '../../lib/credentials.js';
5
+ export default class PerpClose extends GatedCommand {
6
+ static description = 'Close an open perp position (and cancel any orders for the coin) via reduce-only IOC market order.';
7
+ static examples = [
8
+ '$ rift perp close BTC # close BTC perp position + cancel BTC orders',
9
+ '$ rift perp close # close ALL perp positions + cancel ALL orders',
10
+ ];
11
+ static args = {
12
+ coin: Args.string({
13
+ description: 'Coin to close (omit to close all open positions)',
14
+ required: false,
15
+ }),
16
+ };
17
+ static flags = {
18
+ json: Flags.boolean({ description: 'Emit raw JSON only', default: false }),
19
+ };
20
+ async run() {
21
+ const { args, flags } = await this.parse(PerpClose);
22
+ if (!hasFullSetup()) {
23
+ this.error('Perp close requires wallet setup. Run: rift auth setup');
24
+ }
25
+ const creds = loadCredentials();
26
+ if (!creds) {
27
+ this.error('No credentials. Run: rift auth setup');
28
+ }
29
+ // The engine's `close-all` command reads HYPERLIQUID_PRIVATE_KEY
30
+ // from env (not CLI args, for security — see comment in close-all
31
+ // source). The spawned engine subprocess inherits process.env, so
32
+ // we set it here before passthroughToEngine spawns.
33
+ process.env.HYPERLIQUID_PRIVATE_KEY = creds.private_key;
34
+ try {
35
+ // Delegates to the engine's `close-all` command, which is the
36
+ // direct close path (reduce-only IOC market order). Distinct from
37
+ // `close-position` which writes a command file for a running algo
38
+ // daemon — that's the right call when you have an algo session
39
+ // managing a position; `perp close` is for the manual-trade lifecycle
40
+ // or for emergency cleanup.
41
+ const engineArgs = ['--account', getAccountAddress(creds)];
42
+ if (args.coin)
43
+ engineArgs.push('--coin', args.coin);
44
+ await passthroughToEngine({
45
+ command: 'close-all',
46
+ args: engineArgs,
47
+ log: (m) => this.log(m),
48
+ error: (m) => this.error(m),
49
+ exit: (c) => this.exit(c),
50
+ jsonOnly: flags.json,
51
+ });
52
+ }
53
+ finally {
54
+ delete process.env.HYPERLIQUID_PRIVATE_KEY;
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,14 @@
1
+ import { GatedCommand } from '../../lib/base-command.js';
2
+ export default class PerpLong extends GatedCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ coin: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ size: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
10
+ stop: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ leverage: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,38 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { GatedCommand } from '../../lib/base-command.js';
3
+ export default class PerpLong extends GatedCommand {
4
+ static description = 'Open a LONG position on Hyperliquid perps with a stop loss and live monitor.';
5
+ static examples = [
6
+ '$ rift perp long BTC --size 50 --stop 2 # $50 long, 2% stop',
7
+ '$ rift perp long ETH --size 25 --leverage 3 # $25 long at 3x',
8
+ ];
9
+ static args = {
10
+ coin: Args.string({ description: 'Coin (e.g. BTC, ETH, SOL)', required: true }),
11
+ };
12
+ static flags = {
13
+ size: Flags.integer({ description: 'Position size in USD', required: true }),
14
+ stop: Flags.string({ description: 'Stop loss % (default: 2)', default: '2' }),
15
+ leverage: Flags.integer({ description: 'Leverage multiplier', default: 1 }),
16
+ };
17
+ async run() {
18
+ const { args, flags } = await this.parse(PerpLong);
19
+ // Delegate to `rift trade` which already has the rich live monitor
20
+ // dashboard for the manual-trade lifecycle. This wrapper exists so
21
+ // `rift perp long BTC` reads naturally without the user having to
22
+ // type "long" twice or remember the `rift trade <pair> <direction>`
23
+ // positional-arg order.
24
+ // --yes skips the "Type GO" prompt that `rift trade` shows for the
25
+ // legacy `rift trade BTC long` form — `rift perp long BTC` already
26
+ // requires the user to type the direction in the verb, so a second
27
+ // confirmation is redundant. The position-size + stop are still on
28
+ // the command line and the manual-trade daemon's risk disclaimers
29
+ // remain in place.
30
+ await this.config.runCommand('trade', [
31
+ args.coin, 'long',
32
+ '--size', String(flags.size),
33
+ '--stop', flags.stop,
34
+ '--leverage', String(flags.leverage),
35
+ '--yes',
36
+ ]);
37
+ }
38
+ }
@@ -0,0 +1,14 @@
1
+ import { GatedCommand } from '../../lib/base-command.js';
2
+ export default class PerpShort extends GatedCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ coin: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ size: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
10
+ stop: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ leverage: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,27 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { GatedCommand } from '../../lib/base-command.js';
3
+ export default class PerpShort extends GatedCommand {
4
+ static description = 'Open a SHORT position on Hyperliquid perps with a stop loss and live monitor.';
5
+ static examples = [
6
+ '$ rift perp short BTC --size 50 --stop 2 # $50 short, 2% stop',
7
+ '$ rift perp short ETH --size 25 --leverage 3 # $25 short at 3x',
8
+ ];
9
+ static args = {
10
+ coin: Args.string({ description: 'Coin (e.g. BTC, ETH, SOL)', required: true }),
11
+ };
12
+ static flags = {
13
+ size: Flags.integer({ description: 'Position size in USD', required: true }),
14
+ stop: Flags.string({ description: 'Stop loss % (default: 2)', default: '2' }),
15
+ leverage: Flags.integer({ description: 'Leverage multiplier', default: 1 }),
16
+ };
17
+ async run() {
18
+ const { args, flags } = await this.parse(PerpShort);
19
+ await this.config.runCommand('trade', [
20
+ args.coin, 'short',
21
+ '--size', String(flags.size),
22
+ '--stop', flags.stop,
23
+ '--leverage', String(flags.leverage),
24
+ '--yes',
25
+ ]);
26
+ }
27
+ }
@@ -0,0 +1,9 @@
1
+ import { GatedCommand } from '../../lib/base-command.js';
2
+ export default class PerpStatus extends GatedCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ };
8
+ run(): Promise<void>;
9
+ }
@@ -0,0 +1,26 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { GatedCommand } from '../../lib/base-command.js';
3
+ import { passthroughToEngine } from '../../lib/engine-passthrough.js';
4
+ export default class PerpStatus extends GatedCommand {
5
+ static description = 'Show current perp account state — positions, margin used, unrealized PnL, open orders.';
6
+ static examples = [
7
+ '$ rift perp status',
8
+ '$ rift perp status --json',
9
+ ];
10
+ static flags = {
11
+ json: Flags.boolean({ description: 'Emit raw JSON only', default: false }),
12
+ };
13
+ async run() {
14
+ const { flags } = await this.parse(PerpStatus);
15
+ // `state` is the canonical engine command for "what's my account
16
+ // doing right now": positions, equity, margin, algo sessions, etc.
17
+ await passthroughToEngine({
18
+ command: 'state',
19
+ args: [],
20
+ log: (m) => this.log(m),
21
+ error: (m) => this.error(m),
22
+ exit: (c) => this.exit(c),
23
+ jsonOnly: flags.json,
24
+ });
25
+ }
26
+ }
@@ -0,0 +1,6 @@
1
+ import { GatedCommand } from '../../lib/base-command.js';
2
+ export default class PortfolioAlerts extends GatedCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ run(): Promise<void>;
6
+ }
@@ -0,0 +1,47 @@
1
+ import { GatedCommand } from '../../lib/base-command.js';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import { getDataDir } from '../../lib/python-bridge.js';
5
+ import { red, yellow, cyan, bold, dim, } from '../../lib/tui.js';
6
+ export default class PortfolioAlerts extends GatedCommand {
7
+ static description = 'Show recent portfolio alerts';
8
+ static examples = [
9
+ '$ rift portfolio alerts',
10
+ ];
11
+ async run() {
12
+ const alertsFile = path.join(getDataDir(), 'algo', 'alerts.log');
13
+ if (!fs.existsSync(alertsFile)) {
14
+ this.log(`\n ${dim('No alerts yet.')}\n`);
15
+ return;
16
+ }
17
+ const lines = fs.readFileSync(alertsFile, 'utf-8').trim().split('\n').filter(l => l.trim());
18
+ if (lines.length === 0) {
19
+ this.log(`\n ${dim('No alerts yet.')}\n`);
20
+ return;
21
+ }
22
+ this.log('');
23
+ this.log(` ${bold('Recent Alerts')} ${dim(`(${lines.length} total)`)}`);
24
+ this.log(` ${dim('─'.repeat(60))}`);
25
+ this.log('');
26
+ // Show last 20
27
+ const recent = lines.slice(-20);
28
+ for (const line of recent) {
29
+ try {
30
+ const alert = JSON.parse(line);
31
+ const event = (alert.event || '').padEnd(18);
32
+ const eventColor = event.includes('trade') ? cyan
33
+ : event.includes('health') ? yellow
34
+ : event.includes('drawdown') ? red
35
+ : event.includes('session_died') ? red
36
+ : event.includes('schedule') ? dim
37
+ : event.includes('risk') ? yellow
38
+ : dim;
39
+ this.log(` ${dim(alert.time || '??:??')} ${eventColor(event)} ${alert.message || ''}`);
40
+ }
41
+ catch {
42
+ // Skip unparseable lines
43
+ }
44
+ }
45
+ this.log('');
46
+ }
47
+ }
@@ -0,0 +1,12 @@
1
+ import { GatedCommand } from '../../lib/base-command.js';
2
+ export default class PortfolioBacktest extends GatedCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ config: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ run(): Promise<void>;
9
+ private renderResult;
10
+ private row;
11
+ private rowColored;
12
+ }
@@ -0,0 +1,178 @@
1
+ import { Args } from '@oclif/core';
2
+ import * as path from 'node:path';
3
+ import { GatedCommand } from '../../lib/base-command.js';
4
+ import { runEngine } from '../../lib/python-bridge.js';
5
+ const green = (s) => `\x1b[32m${s}\x1b[0m`;
6
+ const red = (s) => `\x1b[31m${s}\x1b[0m`;
7
+ const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
8
+ const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
9
+ const bold = (s) => `\x1b[1m${s}\x1b[0m`;
10
+ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
11
+ function colorNum(val, suffix = '') {
12
+ const str = `${val}${suffix}`;
13
+ if (val > 0)
14
+ return green(str);
15
+ if (val < 0)
16
+ return red(str);
17
+ return yellow(str);
18
+ }
19
+ export default class PortfolioBacktest extends GatedCommand {
20
+ static description = 'Backtest a portfolio of multiple strategies simultaneously';
21
+ static examples = [
22
+ '$ rift portfolio backtest strategies/configs/portfolio_btc.yaml',
23
+ ];
24
+ static args = {
25
+ config: Args.string({ description: 'Path to portfolio.yaml config file', required: true }),
26
+ };
27
+ async run() {
28
+ const { args } = await this.parse(PortfolioBacktest);
29
+ this.log('');
30
+ this.log(` ${bold('Portfolio Backtest')}`);
31
+ this.log(` ${dim(`Config: ${args.config}`)}`);
32
+ this.log('');
33
+ const configPath = path.resolve(args.config);
34
+ await runEngine('portfolio-backtest', [configPath], (msg) => {
35
+ if (msg.type === 'progress' && msg.msg) {
36
+ process.stdout.write(`\r ${dim(String(msg.msg))}${''.padEnd(20)}`);
37
+ }
38
+ else if (msg.type === 'result') {
39
+ process.stdout.write('\r' + ' '.repeat(80) + '\r');
40
+ this.renderResult(msg);
41
+ }
42
+ else if (msg.type === 'error') {
43
+ process.stdout.write('\r' + ' '.repeat(80) + '\r');
44
+ this.error(msg.msg);
45
+ }
46
+ });
47
+ }
48
+ renderResult(msg) {
49
+ const strategies = msg.strategies;
50
+ const corrMatrix = msg.correlation_matrix;
51
+ // Per-strategy results table
52
+ this.log(dim(' ── Per-Strategy Results ──'));
53
+ this.log('');
54
+ const hdr = ` ${dim('│')} ${'Strategy'.padEnd(20)} ${'Alloc'.padEnd(8)} ${'Return'.padEnd(12)} ${'Sharpe'.padEnd(10)} ${'PF'.padEnd(8)} ${'MaxDD'.padEnd(10)} ${'Trades'.padEnd(8)} ${'Win%'.padEnd(8)} ${dim('│')}`;
55
+ const hrLen = hdr.replace(/\x1b\[[0-9;]*m/g, '').length;
56
+ const hr = ` ${dim('─'.repeat(hrLen - 2))}`;
57
+ this.log(hr);
58
+ this.log(hdr);
59
+ this.log(hr);
60
+ for (const s of strategies) {
61
+ const retStr = `${s.return_pct}%`;
62
+ const ddStr = `${s.max_drawdown_pct}%`;
63
+ let row = ` ${dim('│')} ${bold(String(s.name).padEnd(20))} `;
64
+ row += `${String(s.allocation).padEnd(8)} `;
65
+ row += `${colorNum(s.return_pct, '%')}${' '.repeat(Math.max(1, 12 - retStr.length))} `;
66
+ row += `${colorNum(s.sharpe)}${' '.repeat(Math.max(1, 10 - String(s.sharpe).length))} `;
67
+ row += `${String(s.profit_factor).padEnd(8)} `;
68
+ row += `${colorNum(s.max_drawdown_pct, '%')}${' '.repeat(Math.max(1, 10 - ddStr.length))} `;
69
+ row += `${String(s.num_trades).padEnd(8)} `;
70
+ row += `${String(s.win_rate).padEnd(8)} `;
71
+ row += dim('│');
72
+ this.log(row);
73
+ }
74
+ this.log(hr);
75
+ this.log('');
76
+ // Portfolio summary box
77
+ const w = 55;
78
+ const boxHr = '─'.repeat(w - 2);
79
+ this.log(` ${dim('┌' + boxHr + '┐')}`);
80
+ this.log(` ${dim('│')} ${bold('PORTFOLIO SUMMARY')}${' '.repeat(w - 20)}${dim('│')}`);
81
+ this.log(` ${dim('├' + boxHr + '┤')}`);
82
+ this.log(this.row('Initial Equity', `$${Number(msg.initial_equity).toLocaleString()}`, w));
83
+ this.log(this.row('Final Equity', `$${Number(msg.final_equity).toLocaleString()}`, w));
84
+ this.log(this.rowColored('Portfolio Return', msg.total_return_pct, '%', w));
85
+ this.log(this.rowColored('Portfolio Sharpe', msg.portfolio_sharpe, '', w));
86
+ this.log(this.rowColored('Portfolio Max Drawdown', msg.portfolio_max_drawdown_pct, '%', w));
87
+ this.log(this.row('Total Trades', String(msg.total_trades), w));
88
+ this.log(this.row('Strategies', String(strategies.length), w));
89
+ this.log(` ${dim('└' + boxHr + '┘')}`);
90
+ this.log('');
91
+ // Correlation matrix
92
+ if (corrMatrix && corrMatrix.strategies.length > 1) {
93
+ this.log(dim(' ── Strategy Correlation ──'));
94
+ this.log('');
95
+ const names = corrMatrix.strategies;
96
+ const matrix = corrMatrix.matrix;
97
+ // Header
98
+ let corrHdr = ` ${''.padEnd(20)}`;
99
+ for (const n of names) {
100
+ corrHdr += `${n.slice(0, 12).padEnd(14)}`;
101
+ }
102
+ this.log(dim(corrHdr));
103
+ // Rows
104
+ for (let i = 0; i < names.length; i++) {
105
+ let corrRow = ` ${names[i].padEnd(20)}`;
106
+ for (let j = 0; j < names.length; j++) {
107
+ const val = matrix[i][j];
108
+ const valStr = val.toFixed(3);
109
+ if (i === j) {
110
+ corrRow += dim(valStr.padEnd(14));
111
+ }
112
+ else if (val > 0.7) {
113
+ corrRow += red(valStr.padEnd(14)); // high correlation = warning
114
+ }
115
+ else if (val < 0.3) {
116
+ corrRow += green(valStr.padEnd(14)); // low correlation = good
117
+ }
118
+ else {
119
+ corrRow += yellow(valStr.padEnd(14));
120
+ }
121
+ }
122
+ this.log(corrRow);
123
+ }
124
+ this.log('');
125
+ // Check for high correlations
126
+ for (let i = 0; i < names.length; i++) {
127
+ for (let j = i + 1; j < names.length; j++) {
128
+ if (matrix[i][j] > 0.7) {
129
+ this.log(` ${yellow('!')} ${names[i]} and ${names[j]} are highly correlated (${matrix[i][j].toFixed(3)}) — diversification benefit is limited`);
130
+ }
131
+ }
132
+ }
133
+ const allLow = matrix.every((row, i) => row.every((val, j) => i === j || val < 0.5));
134
+ if (allLow) {
135
+ this.log(` ${green('✔')} Strategy correlations are low — good portfolio diversification`);
136
+ }
137
+ this.log('');
138
+ }
139
+ // Interpretation
140
+ const portfolioReturn = msg.total_return_pct;
141
+ const portfolioSharpe = msg.portfolio_sharpe;
142
+ const portfolioDD = msg.portfolio_max_drawdown_pct;
143
+ this.log(dim(' ── Interpretation ──'));
144
+ this.log('');
145
+ if (portfolioSharpe > 0.5 && portfolioDD > -10) {
146
+ this.log(` ${green('Strong portfolio.')} Positive Sharpe with controlled drawdown.`);
147
+ }
148
+ else if (portfolioSharpe > 0 && portfolioDD > -20) {
149
+ this.log(` ${yellow('Moderate portfolio.')} Positive returns but drawdown needs attention.`);
150
+ }
151
+ else {
152
+ this.log(` ${red('Weak portfolio.')} Consider adjusting allocations or strategy selection.`);
153
+ }
154
+ // Compare portfolio to best individual strategy
155
+ const bestStrat = strategies.reduce((best, s) => (s.sharpe > best.sharpe ? s : best), strategies[0]);
156
+ if (portfolioSharpe > bestStrat.sharpe) {
157
+ this.log(` ${green('Portfolio Sharpe (' + portfolioSharpe.toFixed(3) + ') exceeds best individual strategy (' + bestStrat.name + ': ' + bestStrat.sharpe.toFixed(3) + ').')}`);
158
+ this.log(` ${dim('Diversification is adding value.')}`);
159
+ }
160
+ else {
161
+ this.log(` ${dim(`Best individual Sharpe: ${bestStrat.name} (${bestStrat.sharpe.toFixed(3)}) vs portfolio (${portfolioSharpe.toFixed(3)})`)}`);
162
+ }
163
+ this.log('');
164
+ }
165
+ row(label, value, width) {
166
+ const labelStr = ` ${label}:`;
167
+ const cleanVal = value.replace(/\x1b\[[0-9;]*m/g, '');
168
+ const padding = width - labelStr.length - cleanVal.length - 3;
169
+ return ` ${dim('│')}${labelStr}${' '.repeat(Math.max(1, padding))}${value} ${dim('│')}`;
170
+ }
171
+ rowColored(label, value, suffix, width) {
172
+ const coloredStr = colorNum(value, suffix);
173
+ const cleanStr = `${value}${suffix}`;
174
+ const labelStr = ` ${label}:`;
175
+ const padding = width - labelStr.length - cleanStr.length - 3;
176
+ return ` ${dim('│')}${labelStr}${' '.repeat(Math.max(1, padding))}${coloredStr} ${dim('│')}`;
177
+ }
178
+ }
@@ -0,0 +1,7 @@
1
+ import { GatedCommand } from '../../lib/base-command.js';
2
+ export default class PortfolioCreate extends GatedCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ private readonly availableStrategies;
6
+ run(): Promise<void>;
7
+ }