@talocode/tradia 0.1.0 → 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 +21 -0
- package/README.md +138 -46
- package/dist/auth.d.ts +8 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +36 -0
- package/dist/auth.js.map +1 -0
- package/dist/billing.d.ts +14 -0
- package/dist/billing.d.ts.map +1 -0
- package/dist/billing.js +64 -0
- package/dist/billing.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +269 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +77 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +187 -0
- package/dist/client.js.map +1 -0
- package/dist/engine.d.ts +124 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +474 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +10 -2
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -602
- package/dist/index.js.map +1 -0
- package/dist/journal.d.ts +7 -0
- package/dist/journal.d.ts.map +1 -0
- package/dist/journal.js +84 -0
- package/dist/journal.js.map +1 -0
- package/dist/reporting.d.ts +10 -0
- package/dist/reporting.d.ts.map +1 -0
- package/dist/reporting.js +97 -0
- package/dist/reporting.js.map +1 -0
- package/dist/risk.d.ts +24 -0
- package/dist/risk.d.ts.map +1 -0
- package/dist/risk.js +162 -0
- package/dist/risk.js.map +1 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +194 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +238 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/package.json +41 -30
package/dist/journal.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export function createJournalEntryInternal(input) {
|
|
2
|
+
const pnl = input.profitLoss >= 0 ? `+$${input.profitLoss.toFixed(2)}` : `-$${Math.abs(input.profitLoss).toFixed(2)}`;
|
|
3
|
+
const outcome = input.profitLoss >= 0 ? 'winning' : 'losing';
|
|
4
|
+
const lines = [
|
|
5
|
+
`# Trade Journal: ${input.symbol} ${input.direction.toUpperCase()}`,
|
|
6
|
+
'',
|
|
7
|
+
`**Date**: ${new Date().toISOString().split('T')[0]}`,
|
|
8
|
+
`**Symbol**: ${input.symbol}`,
|
|
9
|
+
`**Direction**: ${input.direction}`,
|
|
10
|
+
`**Entry**: ${input.entry}`,
|
|
11
|
+
`**Exit**: ${input.exit}`,
|
|
12
|
+
`**P&L**: ${pnl} (${input.rMultiple.toFixed(2)}R)`,
|
|
13
|
+
`**Risk Amount**: $${input.riskAmount.toFixed(2)}`,
|
|
14
|
+
'',
|
|
15
|
+
`**Reason for Trade**: ${input.reason}`,
|
|
16
|
+
'',
|
|
17
|
+
`**Outcome**: ${outcome} trade`,
|
|
18
|
+
];
|
|
19
|
+
if (input.mistakes && input.mistakes.length > 0) {
|
|
20
|
+
lines.push('', '**Mistakes**:');
|
|
21
|
+
for (const m of input.mistakes) {
|
|
22
|
+
lines.push(`- ${m}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
lines.push('', `**Rules Followed**: ${input.rulesFollowed ? 'Yes' : 'No'}`);
|
|
26
|
+
lines.push('', '---', '*Not financial advice. Educational journal entry for personal review.*');
|
|
27
|
+
return lines.join('\n');
|
|
28
|
+
}
|
|
29
|
+
export function extractTradeLessonInternal(input) {
|
|
30
|
+
if (input.mistakes && input.mistakes.length > 0) {
|
|
31
|
+
return `Key lesson: ${input.mistakes[0]}. Remember to ${input.mistakes[0].toLowerCase().includes('not') ? 'stay disciplined' : 'follow your trading plan'} in future trades.`;
|
|
32
|
+
}
|
|
33
|
+
if (input.rulesFollowed && input.rMultiple > 0) {
|
|
34
|
+
return `Following the trading plan works. The trade aligned with the analysis and produced a ${input.rMultiple.toFixed(2)}R result. Keep executing with discipline.`;
|
|
35
|
+
}
|
|
36
|
+
if (!input.rulesFollowed) {
|
|
37
|
+
return 'Rules were broken. Review your process and identify why the plan was not followed.';
|
|
38
|
+
}
|
|
39
|
+
return 'Document your lessons and review this trade as part of your ongoing improvement.';
|
|
40
|
+
}
|
|
41
|
+
export function classifyTradeMistake(mistake) {
|
|
42
|
+
const lower = mistake.toLowerCase();
|
|
43
|
+
if (lower.includes('entry') || lower.includes('entered') || lower.includes('early') || lower.includes('late'))
|
|
44
|
+
return 'entry_timing';
|
|
45
|
+
if (lower.includes('stop') || lower.includes('sl') || lower.includes('exit') || lower.includes('cut'))
|
|
46
|
+
return 'risk_management';
|
|
47
|
+
if (lower.includes('size') || lower.includes('lot') || lower.includes('over'))
|
|
48
|
+
return 'position_sizing';
|
|
49
|
+
if (lower.includes('revenge') || lower.includes('fomo') || lower.includes('emotion') || lower.includes('greed') || lower.includes('fear'))
|
|
50
|
+
return 'emotional';
|
|
51
|
+
if (lower.includes('rule') || lower.includes('plan') || lower.includes('discipline') || lower.includes('skip'))
|
|
52
|
+
return 'discipline';
|
|
53
|
+
if (lower.includes('news') || lower.includes('event') || lower.includes('data'))
|
|
54
|
+
return 'news_avoidance';
|
|
55
|
+
if (lower.includes('confirmation') || lower.includes('signal') || lower.includes('verify'))
|
|
56
|
+
return 'confirmation';
|
|
57
|
+
return 'other';
|
|
58
|
+
}
|
|
59
|
+
export function scoreDiscipline(input) {
|
|
60
|
+
let score = 50;
|
|
61
|
+
if (input.rulesFollowed)
|
|
62
|
+
score += 25;
|
|
63
|
+
if (input.reason && input.reason.length > 10)
|
|
64
|
+
score += 10;
|
|
65
|
+
if (input.mistakes && input.mistakes.length === 0)
|
|
66
|
+
score += 10;
|
|
67
|
+
if (input.rMultiple > 0 && input.rulesFollowed)
|
|
68
|
+
score += 5;
|
|
69
|
+
if (input.riskAmount > 0)
|
|
70
|
+
score += 5;
|
|
71
|
+
return Math.min(score, 100);
|
|
72
|
+
}
|
|
73
|
+
export function summarizeTradeOutcome(input) {
|
|
74
|
+
const pnl = input.profitLoss >= 0 ? `profitable (+${input.rMultiple.toFixed(2)}R)` : `loss (${input.rMultiple.toFixed(2)}R)`;
|
|
75
|
+
const lesson = extractTradeLessonInternal(input);
|
|
76
|
+
return [
|
|
77
|
+
`Trade: ${input.symbol} ${input.direction.toUpperCase()}`,
|
|
78
|
+
`Outcome: ${pnl}`,
|
|
79
|
+
`P&L: $${input.profitLoss.toFixed(2)}`,
|
|
80
|
+
`Lesson: ${lesson}`,
|
|
81
|
+
'⚠️ Not financial advice.',
|
|
82
|
+
].join(' | ');
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=journal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal.js","sourceRoot":"","sources":["../src/tradia/journal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TradePosition, PerformanceResult } from './types.js';
|
|
2
|
+
export declare function computeWinRate(trades: TradePosition[]): number;
|
|
3
|
+
export declare function computeAverageR(trades: TradePosition[]): number;
|
|
4
|
+
export declare function computeProfitFactor(trades: TradePosition[]): number;
|
|
5
|
+
export declare function computeExpectancy(trades: TradePosition[]): number;
|
|
6
|
+
export declare function computeMaxDrawdown(trades: TradePosition[]): number;
|
|
7
|
+
export declare function computePerformanceStats(trades: TradePosition[], startingBalance: number): PerformanceResult;
|
|
8
|
+
export declare function compute7d28d365dPerformance(trades: TradePosition[], days: 7 | 28 | 365): number | null;
|
|
9
|
+
export declare function computeSinceInception(trades: TradePosition[], startingBalance: number): number | null;
|
|
10
|
+
//# sourceMappingURL=reporting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporting.d.ts","sourceRoot":"","sources":["../src/tradia/reporting.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAElE,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,CAK9D;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,CAK/D;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,CAMnE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,CASjE;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,CAelE;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,eAAe,EAAE,MAAM,GAAG,iBAAiB,CA+B3G;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,MAAM,GAAG,IAAI,CAKtG;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASrG"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export function computeWinRate(trades) {
|
|
2
|
+
const closed = trades.filter(t => t.rMultiple !== undefined);
|
|
3
|
+
if (closed.length === 0)
|
|
4
|
+
return 0;
|
|
5
|
+
const wins = closed.filter(t => (t.rMultiple || 0) > 0).length;
|
|
6
|
+
return Math.round((wins / closed.length) * 10000) / 100;
|
|
7
|
+
}
|
|
8
|
+
export function computeAverageR(trades) {
|
|
9
|
+
const closed = trades.filter(t => t.rMultiple !== undefined);
|
|
10
|
+
if (closed.length === 0)
|
|
11
|
+
return 0;
|
|
12
|
+
const totalR = closed.reduce((sum, t) => sum + (t.rMultiple || 0), 0);
|
|
13
|
+
return Math.round((totalR / closed.length) * 100) / 100;
|
|
14
|
+
}
|
|
15
|
+
export function computeProfitFactor(trades) {
|
|
16
|
+
const closed = trades.filter(t => t.rMultiple !== undefined);
|
|
17
|
+
const grossProfit = closed.filter(t => (t.rMultiple || 0) > 0).reduce((sum, t) => sum + (t.rMultiple || 0) * (t.riskAmount || 0), 0);
|
|
18
|
+
const grossLoss = Math.abs(closed.filter(t => (t.rMultiple || 0) < 0).reduce((sum, t) => sum + (t.rMultiple || 0) * (t.riskAmount || 0), 0));
|
|
19
|
+
if (grossLoss === 0)
|
|
20
|
+
return grossProfit > 0 ? Infinity : 0;
|
|
21
|
+
return Math.round((grossProfit / grossLoss) * 100) / 100;
|
|
22
|
+
}
|
|
23
|
+
export function computeExpectancy(trades) {
|
|
24
|
+
const closed = trades.filter(t => t.rMultiple !== undefined);
|
|
25
|
+
if (closed.length === 0)
|
|
26
|
+
return 0;
|
|
27
|
+
const totalR = closed.reduce((sum, t) => sum + (t.rMultiple || 0), 0);
|
|
28
|
+
const avgWin = closed.filter(t => (t.rMultiple || 0) > 0).reduce((sum, t) => sum + (t.rMultiple || 0), 0) / Math.max(closed.filter(t => (t.rMultiple || 0) > 0).length, 1);
|
|
29
|
+
const avgLoss = Math.abs(closed.filter(t => (t.rMultiple || 0) < 0).reduce((sum, t) => sum + (t.rMultiple || 0), 0)) / Math.max(closed.filter(t => (t.rMultiple || 0) < 0).length, 1);
|
|
30
|
+
const winRate = closed.filter(t => (t.rMultiple || 0) > 0).length / closed.length;
|
|
31
|
+
const expectancy = (winRate * avgWin) - ((1 - winRate) * avgLoss);
|
|
32
|
+
return Math.round(expectancy * 100) / 100;
|
|
33
|
+
}
|
|
34
|
+
export function computeMaxDrawdown(trades) {
|
|
35
|
+
let peak = 0;
|
|
36
|
+
let maxDd = 0;
|
|
37
|
+
let balance = 0;
|
|
38
|
+
for (const t of trades) {
|
|
39
|
+
const r = t.rMultiple || 0;
|
|
40
|
+
const riskAmt = t.riskAmount || 0;
|
|
41
|
+
balance += r * riskAmt;
|
|
42
|
+
if (balance > peak)
|
|
43
|
+
peak = balance;
|
|
44
|
+
const dd = peak > 0 ? ((peak - balance) / peak) * 100 : 0;
|
|
45
|
+
if (dd > maxDd)
|
|
46
|
+
maxDd = dd;
|
|
47
|
+
}
|
|
48
|
+
return Math.round(maxDd * 100) / 100;
|
|
49
|
+
}
|
|
50
|
+
export function computePerformanceStats(trades, startingBalance) {
|
|
51
|
+
const closed = trades.filter(t => t.rMultiple !== undefined);
|
|
52
|
+
const wins = closed.filter(t => (t.rMultiple || 0) > 0).length;
|
|
53
|
+
const losses = closed.filter(t => (t.rMultiple || 0) < 0).length;
|
|
54
|
+
const totalTrades = closed.length;
|
|
55
|
+
let currentBalance = startingBalance;
|
|
56
|
+
for (const t of closed) {
|
|
57
|
+
const r = t.rMultiple || 0;
|
|
58
|
+
const riskAmt = t.riskAmount || 0;
|
|
59
|
+
currentBalance += r * riskAmt;
|
|
60
|
+
}
|
|
61
|
+
const sinceInception = startingBalance > 0
|
|
62
|
+
? Math.round(((currentBalance - startingBalance) / startingBalance) * 10000) / 100
|
|
63
|
+
: null;
|
|
64
|
+
return {
|
|
65
|
+
winRate: computeWinRate(trades),
|
|
66
|
+
averageR: computeAverageR(trades),
|
|
67
|
+
profitFactor: computeProfitFactor(trades),
|
|
68
|
+
expectancy: computeExpectancy(trades),
|
|
69
|
+
maxDrawdown: computeMaxDrawdown(trades),
|
|
70
|
+
sevenDay: null,
|
|
71
|
+
twentyEightDay: null,
|
|
72
|
+
threeSixtyFiveDay: null,
|
|
73
|
+
sinceInception,
|
|
74
|
+
totalTrades,
|
|
75
|
+
wins,
|
|
76
|
+
losses,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export function compute7d28d365dPerformance(trades, days) {
|
|
80
|
+
if (trades.length === 0)
|
|
81
|
+
return null;
|
|
82
|
+
const relevantTrades = trades.slice(-Math.max(Math.floor(trades.length * (days / 365)), 1));
|
|
83
|
+
const total = relevantTrades.reduce((sum, t) => sum + ((t.rMultiple || 0) * (t.riskAmount || 0)), 0);
|
|
84
|
+
return Math.round(total * 100) / 100;
|
|
85
|
+
}
|
|
86
|
+
export function computeSinceInception(trades, startingBalance) {
|
|
87
|
+
if (trades.length === 0 || startingBalance <= 0)
|
|
88
|
+
return null;
|
|
89
|
+
let currentBalance = startingBalance;
|
|
90
|
+
for (const t of trades) {
|
|
91
|
+
const r = t.rMultiple || 0;
|
|
92
|
+
const riskAmt = t.riskAmount || 0;
|
|
93
|
+
currentBalance += r * riskAmt;
|
|
94
|
+
}
|
|
95
|
+
return Math.round(((currentBalance - startingBalance) / startingBalance) * 10000) / 100;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=reporting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporting.js","sourceRoot":"","sources":["../src/tradia/reporting.ts"],"names":[],"mappings":"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"}
|
package/dist/risk.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RiskCheckInput, RiskResult, RiskRewardResult, PositionSizeResult, DrawdownResult, ExposureResult, TradePosition } from './types.js';
|
|
2
|
+
export declare function calculatePositionSize(accountBalance: number, riskPercent: number, entry: number, stopLoss: number): PositionSizeResult;
|
|
3
|
+
export declare function calculateRiskReward(entry: number, stopLoss: number, takeProfit: number, accountBalance?: number, riskPercent?: number): RiskRewardResult;
|
|
4
|
+
export declare function calculateDrawdown(currentBalance: number, peakBalance: number): DrawdownResult;
|
|
5
|
+
export declare function calculateExposure(openTrades: TradePosition[]): ExposureResult;
|
|
6
|
+
export declare function validateRiskRules(input: RiskCheckInput): {
|
|
7
|
+
approved: boolean;
|
|
8
|
+
violations: string[];
|
|
9
|
+
warnings: string[];
|
|
10
|
+
};
|
|
11
|
+
export declare function detectRevengeTrading(trades: TradePosition[]): {
|
|
12
|
+
detected: boolean;
|
|
13
|
+
reason: string | null;
|
|
14
|
+
};
|
|
15
|
+
export declare function detectOverleveraging(openTrades: TradePosition[], accountBalance: number): {
|
|
16
|
+
detected: boolean;
|
|
17
|
+
reason: string | null;
|
|
18
|
+
};
|
|
19
|
+
export declare function detectRuleViolation(input: RiskCheckInput): {
|
|
20
|
+
detected: boolean;
|
|
21
|
+
violations: string[];
|
|
22
|
+
};
|
|
23
|
+
export declare function checkRiskInternal(input: RiskCheckInput): RiskResult;
|
|
24
|
+
//# sourceMappingURL=risk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"risk.d.ts","sourceRoot":"","sources":["../src/tradia/risk.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAEjJ,wBAAgB,qBAAqB,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CAStI;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAWxJ;AAED,wBAAgB,iBAAiB,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,cAAc,CAQ7F;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,cAAc,CAS7E;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CA2CxH;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAuB1G;AAeD,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,aAAa,EAAE,EAAE,cAAc,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAqBtI;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAMtG;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,UAAU,CAuBnE"}
|
package/dist/risk.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
export function calculatePositionSize(accountBalance, riskPercent, entry, stopLoss) {
|
|
2
|
+
const riskAmount = accountBalance * (riskPercent / 100);
|
|
3
|
+
const priceRisk = Math.abs(entry - stopLoss);
|
|
4
|
+
const units = priceRisk > 0 ? riskAmount / priceRisk : 0;
|
|
5
|
+
return {
|
|
6
|
+
units: Math.round(units * 10000) / 10000,
|
|
7
|
+
riskAmount: Math.round(riskAmount * 100) / 100,
|
|
8
|
+
riskPercent,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function calculateRiskReward(entry, stopLoss, takeProfit, accountBalance, riskPercent) {
|
|
12
|
+
const riskAmount = Math.abs(entry - stopLoss);
|
|
13
|
+
const rewardAmount = Math.abs(takeProfit - entry);
|
|
14
|
+
const ratio = riskAmount > 0 ? Math.round((rewardAmount / riskAmount) * 100) / 100 : 0;
|
|
15
|
+
const riskPercentOfAccount = accountBalance && riskPercent ? riskPercent : riskAmount > 0 && accountBalance ? Math.round((riskAmount / accountBalance) * 10000) / 100 : 0;
|
|
16
|
+
return {
|
|
17
|
+
riskAmount: Math.round(riskAmount * 100) / 100,
|
|
18
|
+
rewardAmount: Math.round(rewardAmount * 100) / 100,
|
|
19
|
+
ratio,
|
|
20
|
+
riskPercentOfAccount,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function calculateDrawdown(currentBalance, peakBalance) {
|
|
24
|
+
const drawdown = peakBalance - currentBalance;
|
|
25
|
+
const drawdownPercent = peakBalance > 0 ? (drawdown / peakBalance) * 100 : 0;
|
|
26
|
+
return {
|
|
27
|
+
currentDrawdown: Math.round(drawdown * 100) / 100,
|
|
28
|
+
maxDrawdown: Math.round(drawdown * 100) / 100,
|
|
29
|
+
drawdownPercent: Math.round(drawdownPercent * 100) / 100,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function calculateExposure(openTrades) {
|
|
33
|
+
const totalExposure = openTrades.reduce((sum, t) => sum + Math.abs(t.entry - t.stopLoss), 0);
|
|
34
|
+
const totalRiskAmount = openTrades.reduce((sum, t) => sum + t.riskAmount, 0);
|
|
35
|
+
return {
|
|
36
|
+
totalExposure: Math.round(totalExposure * 100) / 100,
|
|
37
|
+
totalRiskAmount: Math.round(totalRiskAmount * 100) / 100,
|
|
38
|
+
totalRiskPercent: 0,
|
|
39
|
+
tradeCount: openTrades.length,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function validateRiskRules(input) {
|
|
43
|
+
const violations = [];
|
|
44
|
+
const warnings = [];
|
|
45
|
+
const riskAmount = input.accountBalance * (input.riskPercent / 100);
|
|
46
|
+
const maxRisk = input.maxRiskPerTradePercent
|
|
47
|
+
? input.accountBalance * (input.maxRiskPerTradePercent / 100)
|
|
48
|
+
: input.accountBalance * 0.01;
|
|
49
|
+
if (riskAmount > maxRisk) {
|
|
50
|
+
violations.push(`Risk amount (${riskAmount.toFixed(2)}) exceeds max risk per trade (${maxRisk.toFixed(2)})`);
|
|
51
|
+
}
|
|
52
|
+
if (input.riskPercent > 5) {
|
|
53
|
+
violations.push(`Risk per trade (${input.riskPercent}%) exceeds 5% maximum`);
|
|
54
|
+
}
|
|
55
|
+
if (input.openTrades && input.openTrades.length > 0) {
|
|
56
|
+
const totalOpenRisk = input.openTrades.reduce((sum, t) => sum + t.riskAmount, 0) + riskAmount;
|
|
57
|
+
const dailyLimit = input.dailyLossLimitPercent
|
|
58
|
+
? input.accountBalance * (input.dailyLossLimitPercent / 100)
|
|
59
|
+
: input.accountBalance * 0.03;
|
|
60
|
+
if (totalOpenRisk > dailyLimit) {
|
|
61
|
+
violations.push(`Total open risk (${totalOpenRisk.toFixed(2)}) exceeds daily loss limit (${dailyLimit.toFixed(2)})`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (input.riskPercent > 2) {
|
|
65
|
+
warnings.push('Risk per trade exceeds 2%. Consider reducing position size.');
|
|
66
|
+
}
|
|
67
|
+
const rr = input.stopLoss !== input.entry
|
|
68
|
+
? Math.abs(input.takeProfit - input.entry) / Math.abs(input.stopLoss - input.entry)
|
|
69
|
+
: 0;
|
|
70
|
+
if (rr < 1) {
|
|
71
|
+
warnings.push('Risk-reward ratio is below 1. This trade may not be favorable.');
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
approved: violations.length === 0,
|
|
75
|
+
violations,
|
|
76
|
+
warnings,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export function detectRevengeTrading(trades) {
|
|
80
|
+
if (trades.length < 3)
|
|
81
|
+
return { detected: false, reason: null };
|
|
82
|
+
const recentTrades = trades.slice(-5);
|
|
83
|
+
const losses = recentTrades.filter(t => t.rMultiple !== undefined && t.rMultiple < 0);
|
|
84
|
+
const consecutiveLosses = countConsecutiveLosses(recentTrades);
|
|
85
|
+
if (consecutiveLosses >= 3) {
|
|
86
|
+
return {
|
|
87
|
+
detected: true,
|
|
88
|
+
reason: `Detected ${consecutiveLosses} consecutive losing trades. Possible revenge trading pattern.`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const lossRate = losses.length / recentTrades.length;
|
|
92
|
+
if (lossRate > 0.7 && recentTrades.length >= 4) {
|
|
93
|
+
return {
|
|
94
|
+
detected: true,
|
|
95
|
+
reason: `High loss rate (${Math.round(lossRate * 100)}%) in recent trades. Review if emotional trading is occurring.`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return { detected: false, reason: null };
|
|
99
|
+
}
|
|
100
|
+
function countConsecutiveLosses(trades) {
|
|
101
|
+
let count = 0;
|
|
102
|
+
for (let i = trades.length - 1; i >= 0; i--) {
|
|
103
|
+
const t = trades[i];
|
|
104
|
+
if (t.rMultiple !== undefined && t.rMultiple < 0) {
|
|
105
|
+
count++;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return count;
|
|
112
|
+
}
|
|
113
|
+
export function detectOverleveraging(openTrades, accountBalance) {
|
|
114
|
+
if (openTrades.length === 0)
|
|
115
|
+
return { detected: false, reason: null };
|
|
116
|
+
const totalRiskAmount = openTrades.reduce((sum, t) => sum + t.riskAmount, 0);
|
|
117
|
+
const totalRiskPercent = accountBalance > 0 ? (totalRiskAmount / accountBalance) * 100 : 0;
|
|
118
|
+
if (totalRiskPercent > 15) {
|
|
119
|
+
return {
|
|
120
|
+
detected: true,
|
|
121
|
+
reason: `Total risk across open trades is ${Math.round(totalRiskPercent)}% of account. Overleveraging detected.`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (openTrades.length > 5) {
|
|
125
|
+
return {
|
|
126
|
+
detected: true,
|
|
127
|
+
reason: `${openTrades.length} open positions. Consider reducing number of concurrent trades.`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return { detected: false, reason: null };
|
|
131
|
+
}
|
|
132
|
+
export function detectRuleViolation(input) {
|
|
133
|
+
const result = validateRiskRules(input);
|
|
134
|
+
return {
|
|
135
|
+
detected: result.violations.length > 0,
|
|
136
|
+
violations: result.violations,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
export function checkRiskInternal(input) {
|
|
140
|
+
const validation = validateRiskRules(input);
|
|
141
|
+
const rr = input.stopLoss !== input.entry
|
|
142
|
+
? Math.abs(input.takeProfit - input.entry) / Math.abs(input.stopLoss - input.entry)
|
|
143
|
+
: 0;
|
|
144
|
+
const riskAmount = input.accountBalance * (input.riskPercent / 100);
|
|
145
|
+
const priceRisk = Math.abs(input.entry - input.stopLoss);
|
|
146
|
+
const positionSize = priceRisk > 0 ? riskAmount / priceRisk : 0;
|
|
147
|
+
return {
|
|
148
|
+
approved: validation.approved,
|
|
149
|
+
riskAmount: Math.round(riskAmount * 100) / 100,
|
|
150
|
+
riskRewardRatio: Math.round(rr * 100) / 100,
|
|
151
|
+
positionSizeEstimate: {
|
|
152
|
+
units: Math.round(positionSize * 10000) / 10000,
|
|
153
|
+
riskAmount: Math.round(riskAmount * 100) / 100,
|
|
154
|
+
riskPercent: input.riskPercent,
|
|
155
|
+
},
|
|
156
|
+
violations: validation.violations,
|
|
157
|
+
warnings: validation.warnings,
|
|
158
|
+
humanReviewRequired: true,
|
|
159
|
+
notFinancialAdvice: true,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=risk.js.map
|
package/dist/risk.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"risk.js","sourceRoot":"","sources":["../src/tradia/risk.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type IncomingMessage, type ServerResponse } from 'node:http';
|
|
2
|
+
export declare function handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
3
|
+
export declare function createTradiaServer(): import("http").Server<typeof IncomingMessage, typeof ServerResponse>;
|
|
4
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/tradia/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAA;AAgHnF,wBAAsB,aAAa,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA2D5F;AAED,wBAAgB,kBAAkB,yEAoCjC"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { authenticateRequest, redactApiKey } from './auth.js';
|
|
3
|
+
import { chargeCredits, TradiaBillingError } from './billing.js';
|
|
4
|
+
import { generateAgentPlan, analyzeMarketContext, evaluateSignal, proposeTrade, journalTrade, analyzePerformance, generatePortfolioReport, generatePublicUpdate, simulateBacktest, generateAccountabilityCard, exportMarkdown, exportJson, } from './engine.js';
|
|
5
|
+
import { checkRiskInternal } from './risk.js';
|
|
6
|
+
const VERSION = '0.1.0';
|
|
7
|
+
const PRODUCT = 'tradia';
|
|
8
|
+
const MAX_BODY_SIZE = 1024 * 100;
|
|
9
|
+
const TIMEOUT_MS = 30000;
|
|
10
|
+
function readBody(req) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
let body = '';
|
|
13
|
+
let size = 0;
|
|
14
|
+
req.on('data', (chunk) => {
|
|
15
|
+
size += chunk.length;
|
|
16
|
+
if (size > MAX_BODY_SIZE) {
|
|
17
|
+
reject(new Error('Request body too large'));
|
|
18
|
+
req.destroy();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
body += chunk.toString();
|
|
22
|
+
});
|
|
23
|
+
req.on('end', () => {
|
|
24
|
+
if (!body) {
|
|
25
|
+
resolve({});
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
resolve(JSON.parse(body));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
reject(new Error('Invalid JSON'));
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
req.on('error', reject);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function jsonResponse(res, status, data) {
|
|
39
|
+
res.writeHead(status, {
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
'Access-Control-Allow-Origin': '*',
|
|
42
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
43
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Api-Key',
|
|
44
|
+
});
|
|
45
|
+
res.end(JSON.stringify(data));
|
|
46
|
+
}
|
|
47
|
+
function errorResponse(res, status, type, message, code) {
|
|
48
|
+
jsonResponse(res, status, { error: { type, message, code } });
|
|
49
|
+
}
|
|
50
|
+
function redactSecrets(obj) {
|
|
51
|
+
if (!obj || typeof obj !== 'object')
|
|
52
|
+
return obj;
|
|
53
|
+
const result = { ...obj };
|
|
54
|
+
if (result.apiKey)
|
|
55
|
+
result.apiKey = redactApiKey(result.apiKey);
|
|
56
|
+
if (result.authorization)
|
|
57
|
+
result.authorization = '(redacted)';
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
function checkAuth(req) {
|
|
61
|
+
const authHeader = req.headers['authorization'];
|
|
62
|
+
const xApiKey = req.headers['x-api-key'];
|
|
63
|
+
const auth = authenticateRequest(authHeader, xApiKey);
|
|
64
|
+
return auth.authenticated;
|
|
65
|
+
}
|
|
66
|
+
function handleCors(req, res) {
|
|
67
|
+
if (req.method === 'OPTIONS') {
|
|
68
|
+
res.writeHead(204, {
|
|
69
|
+
'Access-Control-Allow-Origin': '*',
|
|
70
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
71
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Api-Key',
|
|
72
|
+
'Access-Control-Max-Age': '86400',
|
|
73
|
+
});
|
|
74
|
+
res.end();
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
const ROUTES = [
|
|
80
|
+
{ method: 'GET', path: '/health', handler: () => ({ status: 'ok', version: VERSION, product: PRODUCT }) },
|
|
81
|
+
{ method: 'GET', path: '/v1/tradia/health', handler: () => ({ status: 'ok', version: VERSION, product: PRODUCT }) },
|
|
82
|
+
{ method: 'POST', path: '/v1/tradia/agent/plan', action: 'tradia.agent.plan', handler: (b) => generateAgentPlan(b) },
|
|
83
|
+
{ method: 'POST', path: '/v1/tradia/market/analyze', action: 'tradia.market.analyze', handler: (b) => analyzeMarketContext(b) },
|
|
84
|
+
{ method: 'POST', path: '/v1/tradia/signal/evaluate', action: 'tradia.signal.evaluate', handler: (b) => evaluateSignal(b) },
|
|
85
|
+
{ method: 'POST', path: '/v1/tradia/risk/check', action: 'tradia.risk.check', handler: (b) => ({ result: checkRiskInternal(b) }) },
|
|
86
|
+
{ method: 'POST', path: '/v1/tradia/trade/propose', action: 'tradia.trade.propose', handler: (b) => proposeTrade(b) },
|
|
87
|
+
{ method: 'POST', path: '/v1/tradia/trade/journal', action: 'tradia.trade.journal', handler: (b) => journalTrade(b) },
|
|
88
|
+
{ method: 'POST', path: '/v1/tradia/portfolio/report', action: 'tradia.portfolio.report', handler: (b) => generatePortfolioReport(b) },
|
|
89
|
+
{ method: 'POST', path: '/v1/tradia/performance/analyze', action: 'tradia.performance.analyze', handler: (b) => analyzePerformance(b) },
|
|
90
|
+
{ method: 'POST', path: '/v1/tradia/public-update/generate', action: 'tradia.public_update.generate', handler: (b) => generatePublicUpdate(b) },
|
|
91
|
+
{ method: 'POST', path: '/v1/tradia/backtest/simulate', action: 'tradia.backtest.simulate', handler: (b) => simulateBacktest(b) },
|
|
92
|
+
{ method: 'POST', path: '/v1/tradia/accountability/card', action: 'tradia.accountability.card', handler: (b) => generateAccountabilityCard(b) },
|
|
93
|
+
{ method: 'POST', path: '/v1/tradia/export/markdown', action: 'tradia.export.markdown', handler: (b) => exportMarkdown(b) },
|
|
94
|
+
{ method: 'POST', path: '/v1/tradia/export/json', action: 'tradia.export.json', handler: (b) => exportJson(b) },
|
|
95
|
+
];
|
|
96
|
+
export async function handleRequest(req, res) {
|
|
97
|
+
if (handleCors(req, res))
|
|
98
|
+
return;
|
|
99
|
+
const route = ROUTES.find(r => r.method === req.method && r.path === req.url?.split('?')[0]);
|
|
100
|
+
if (!route) {
|
|
101
|
+
errorResponse(res, 404, 'not_found', `Route not found: ${req.method} ${req.url}`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (route.method === 'POST') {
|
|
105
|
+
const isAuth = checkAuth(req);
|
|
106
|
+
if (!isAuth) {
|
|
107
|
+
errorResponse(res, 401, 'auth_error', 'Missing or invalid API key. Set TALOCODE_API_KEY or TRADIA_ALLOW_LOCAL_UNAUTH=true.');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (route.method === 'POST' && route.action) {
|
|
112
|
+
try {
|
|
113
|
+
await chargeCredits(route.action);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (err instanceof TradiaBillingError) {
|
|
117
|
+
errorResponse(res, err.statusCode, err.code, err.message);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
errorResponse(res, 502, 'billing_unavailable', 'Billing service unavailable');
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (route.method === 'GET') {
|
|
126
|
+
try {
|
|
127
|
+
const result = route.handler({});
|
|
128
|
+
jsonResponse(res, 200, result);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
const message = err instanceof Error ? err.message : 'Internal error';
|
|
132
|
+
errorResponse(res, 500, 'internal_error', message);
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const body = await readBody(req);
|
|
138
|
+
const result = route.handler(body);
|
|
139
|
+
jsonResponse(res, 200, result);
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
if (err instanceof Error) {
|
|
143
|
+
if (err.message === 'Request body too large') {
|
|
144
|
+
errorResponse(res, 413, 'request_too_large', 'Request body exceeds maximum size');
|
|
145
|
+
}
|
|
146
|
+
else if (err.message === 'Invalid JSON') {
|
|
147
|
+
errorResponse(res, 400, 'invalid_json', 'Request body must be valid JSON');
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
errorResponse(res, 500, 'internal_error', err.message);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
errorResponse(res, 500, 'internal_error', 'Unknown error');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
export function createTradiaServer() {
|
|
159
|
+
const port = parseInt(process.env.PORT || '3070', 10);
|
|
160
|
+
const server = createServer((req, res) => {
|
|
161
|
+
req.setTimeout(TIMEOUT_MS, () => {
|
|
162
|
+
errorResponse(res, 408, 'timeout', 'Request timeout');
|
|
163
|
+
req.destroy();
|
|
164
|
+
});
|
|
165
|
+
handleRequest(req, res).catch((err) => {
|
|
166
|
+
console.error('Unhandled server error:', err instanceof Error ? err.message : err);
|
|
167
|
+
if (!res.headersSent) {
|
|
168
|
+
errorResponse(res, 500, 'internal_error', 'Internal server error');
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
server.listen(port, '0.0.0.0', () => {
|
|
173
|
+
console.log(`Tradia API server v${VERSION} listening on 0.0.0.0:${port}`);
|
|
174
|
+
});
|
|
175
|
+
const gracefulShutdown = () => {
|
|
176
|
+
console.log('Shutting down gracefully...');
|
|
177
|
+
server.close(() => {
|
|
178
|
+
console.log('Server closed');
|
|
179
|
+
process.exit(0);
|
|
180
|
+
});
|
|
181
|
+
setTimeout(() => {
|
|
182
|
+
console.error('Forced shutdown after timeout');
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}, 5000);
|
|
185
|
+
};
|
|
186
|
+
process.on('SIGTERM', gracefulShutdown);
|
|
187
|
+
process.on('SIGINT', gracefulShutdown);
|
|
188
|
+
return server;
|
|
189
|
+
}
|
|
190
|
+
const isMainModule = process.argv[1]?.endsWith('server.ts') || process.argv[1]?.endsWith('dist/server.js');
|
|
191
|
+
if (isMainModule) {
|
|
192
|
+
createTradiaServer();
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=server.js.map
|