prab-cli 1.2.6 → 1.2.7
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/dist/index.js +41 -0
- package/dist/lib/crypto/analyzer.js +397 -93
- package/dist/lib/crypto/data-fetcher.js +342 -50
- package/dist/lib/crypto/index.js +12 -1
- package/dist/lib/crypto/signal-generator.js +278 -3
- package/dist/lib/crypto/strategy.js +673 -0
- package/dist/lib/slash-commands.js +6 -0
- package/dist/server/index.js +70 -0
- package/package.json +3 -3
|
@@ -13,6 +13,8 @@ exports.quickSignal = quickSignal;
|
|
|
13
13
|
exports.fullSignal = fullSignal;
|
|
14
14
|
exports.displayComprehensiveAnalysis = displayComprehensiveAnalysis;
|
|
15
15
|
exports.comprehensiveAnalysis = comprehensiveAnalysis;
|
|
16
|
+
exports.displayStrategyResult = displayStrategyResult;
|
|
17
|
+
exports.runSmartStrategy = runSmartStrategy;
|
|
16
18
|
const chalk_1 = __importDefault(require("chalk"));
|
|
17
19
|
const ora_1 = __importDefault(require("ora"));
|
|
18
20
|
const data_fetcher_1 = require("./data-fetcher");
|
|
@@ -221,14 +223,31 @@ async function displaySignal(result) {
|
|
|
221
223
|
console.log(chalk_1.default.cyan("\u{2502}") +
|
|
222
224
|
` Trend: ${trendIcon} ${chalk_1.default.white(signal.indicators.trend)}`.padEnd(53) +
|
|
223
225
|
chalk_1.default.cyan("\u{2502}"));
|
|
224
|
-
// EMA Values
|
|
226
|
+
// EMA Values with Tilt
|
|
225
227
|
console.log(chalk_1.default.cyan("\u{251C}" + "\u{2500}".repeat(45) + "\u{2524}"));
|
|
226
228
|
console.log(chalk_1.default.cyan("\u{2502}") + chalk_1.default.gray(" EMA Indicators:").padEnd(53) + chalk_1.default.cyan("\u{2502}"));
|
|
229
|
+
// EMA10 with tilt indicator
|
|
230
|
+
const ema10TiltIcon = signal.indicators.ema10Tilt.direction === "bullish"
|
|
231
|
+
? chalk_1.default.green("\u{2191}")
|
|
232
|
+
: signal.indicators.ema10Tilt.direction === "bearish"
|
|
233
|
+
? chalk_1.default.red("\u{2193}")
|
|
234
|
+
: chalk_1.default.gray("\u{2194}");
|
|
235
|
+
const ema10TiltColor = signal.indicators.ema10Tilt.strength === "HIGH"
|
|
236
|
+
? chalk_1.default.green
|
|
237
|
+
: signal.indicators.ema10Tilt.strength === "MEDIUM"
|
|
238
|
+
? chalk_1.default.yellow
|
|
239
|
+
: chalk_1.default.gray;
|
|
227
240
|
console.log(chalk_1.default.cyan("\u{2502}") +
|
|
228
|
-
|
|
241
|
+
` EMA10: $${signal.indicators.currentEMA10.toFixed(2)} ${ema10TiltIcon} ${ema10TiltColor(signal.indicators.ema10Tilt.strength)}`.padEnd(52) +
|
|
229
242
|
chalk_1.default.cyan("\u{2502}"));
|
|
243
|
+
// EMA20 with tilt
|
|
244
|
+
const ema20TiltIcon = signal.indicators.ema20Tilt.direction === "bullish"
|
|
245
|
+
? chalk_1.default.green("\u{2191}")
|
|
246
|
+
: signal.indicators.ema20Tilt.direction === "bearish"
|
|
247
|
+
? chalk_1.default.red("\u{2193}")
|
|
248
|
+
: chalk_1.default.gray("\u{2194}");
|
|
230
249
|
console.log(chalk_1.default.cyan("\u{2502}") +
|
|
231
|
-
|
|
250
|
+
` EMA20: $${signal.indicators.currentEMA20.toFixed(2)} ${ema20TiltIcon}`.padEnd(52) +
|
|
232
251
|
chalk_1.default.cyan("\u{2502}"));
|
|
233
252
|
console.log(chalk_1.default.cyan("\u{2502}") +
|
|
234
253
|
chalk_1.default.gray(` EMA50: $${signal.indicators.currentEMA50.toFixed(2)}`).padEnd(44) +
|
|
@@ -238,6 +257,50 @@ async function displaySignal(result) {
|
|
|
238
257
|
chalk_1.default.gray(` EMA200: $${signal.indicators.currentEMA200.toFixed(2)}`).padEnd(44) +
|
|
239
258
|
chalk_1.default.cyan("\u{2502}"));
|
|
240
259
|
}
|
|
260
|
+
// Probability indicator
|
|
261
|
+
const probColor = signal.indicators.probability === "HIGH"
|
|
262
|
+
? chalk_1.default.green
|
|
263
|
+
: signal.indicators.probability === "MEDIUM"
|
|
264
|
+
? chalk_1.default.yellow
|
|
265
|
+
: chalk_1.default.gray;
|
|
266
|
+
console.log(chalk_1.default.cyan("\u{2502}") +
|
|
267
|
+
` Probability: ${probColor(signal.indicators.probability)}`.padEnd(52) +
|
|
268
|
+
chalk_1.default.cyan("\u{2502}"));
|
|
269
|
+
// Market Condition indicator
|
|
270
|
+
const marketCondition = signal.indicators.marketCondition;
|
|
271
|
+
if (marketCondition) {
|
|
272
|
+
const conditionColor = marketCondition.shouldTrade ? chalk_1.default.green : chalk_1.default.red;
|
|
273
|
+
const conditionIcon = marketCondition.shouldTrade ? "\u{2705}" : "\u{26A0}";
|
|
274
|
+
console.log(chalk_1.default.cyan("\u{2502}") +
|
|
275
|
+
` Market: ${conditionIcon} ${conditionColor(marketCondition.shouldTrade ? "TRADEABLE" : "AVOID")}`.padEnd(52) +
|
|
276
|
+
chalk_1.default.cyan("\u{2502}"));
|
|
277
|
+
// Show EMA separation
|
|
278
|
+
const separationColor = marketCondition.emaSeparationPercent > 0.3
|
|
279
|
+
? chalk_1.default.green
|
|
280
|
+
: marketCondition.emaSeparationPercent > 0.15
|
|
281
|
+
? chalk_1.default.yellow
|
|
282
|
+
: chalk_1.default.red;
|
|
283
|
+
console.log(chalk_1.default.cyan("\u{2502}") +
|
|
284
|
+
chalk_1.default
|
|
285
|
+
.gray(` EMA Sep: ${separationColor(marketCondition.emaSeparationPercent.toFixed(2) + "%")}`)
|
|
286
|
+
.padEnd(44) +
|
|
287
|
+
chalk_1.default.cyan("\u{2502}"));
|
|
288
|
+
// Show warning if not tradeable
|
|
289
|
+
if (!marketCondition.shouldTrade) {
|
|
290
|
+
const reasonTrunc = marketCondition.reason.length > 35
|
|
291
|
+
? marketCondition.reason.substring(0, 32) + "..."
|
|
292
|
+
: marketCondition.reason;
|
|
293
|
+
console.log(chalk_1.default.cyan("\u{2502}") +
|
|
294
|
+
chalk_1.default.yellow(` ${reasonTrunc}`).padEnd(44) +
|
|
295
|
+
chalk_1.default.cyan("\u{2502}"));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// Alert trigger
|
|
299
|
+
if (signal.indicators.alertTrigger) {
|
|
300
|
+
console.log(chalk_1.default.cyan("\u{2502}") +
|
|
301
|
+
chalk_1.default.yellow.bold(" \u{26A1} HIGH PROBABILITY SETUP!").padEnd(52) +
|
|
302
|
+
chalk_1.default.cyan("\u{2502}"));
|
|
303
|
+
}
|
|
241
304
|
// Technical observations
|
|
242
305
|
console.log(chalk_1.default.cyan("\u{251C}" + "\u{2500}".repeat(45) + "\u{2524}"));
|
|
243
306
|
console.log(chalk_1.default.cyan("\u{2502}") + chalk_1.default.gray(" Technical Analysis:").padEnd(53) + chalk_1.default.cyan("\u{2502}"));
|
|
@@ -684,3 +747,215 @@ async function comprehensiveAnalysis(symbol) {
|
|
|
684
747
|
console.log("");
|
|
685
748
|
}
|
|
686
749
|
}
|
|
750
|
+
// ============================================
|
|
751
|
+
// SMART TREND CONFLUENCE STRATEGY
|
|
752
|
+
// ============================================
|
|
753
|
+
const strategy_1 = require("./strategy");
|
|
754
|
+
/**
|
|
755
|
+
* Display strategy result in terminal
|
|
756
|
+
*/
|
|
757
|
+
function displayStrategyResult(result, symbol) {
|
|
758
|
+
const boxWidth = 60;
|
|
759
|
+
const contentWidth = boxWidth - 4;
|
|
760
|
+
const border = {
|
|
761
|
+
top: chalk_1.default.cyan("\u{250C}" + "\u{2500}".repeat(boxWidth) + "\u{2510}"),
|
|
762
|
+
mid: chalk_1.default.cyan("\u{251C}" + "\u{2500}".repeat(boxWidth) + "\u{2524}"),
|
|
763
|
+
bot: chalk_1.default.cyan("\u{2514}" + "\u{2500}".repeat(boxWidth) + "\u{2518}"),
|
|
764
|
+
left: chalk_1.default.cyan("\u{2502}"),
|
|
765
|
+
right: chalk_1.default.cyan("\u{2502}"),
|
|
766
|
+
};
|
|
767
|
+
const line = (content, padEnd = contentWidth) => {
|
|
768
|
+
console.log(border.left + " " + content.padEnd(padEnd) + " " + border.right);
|
|
769
|
+
};
|
|
770
|
+
// Signal colors
|
|
771
|
+
const signalColors = {
|
|
772
|
+
STRONG_BUY: chalk_1.default.green.bold,
|
|
773
|
+
BUY: chalk_1.default.green,
|
|
774
|
+
WAIT: chalk_1.default.yellow,
|
|
775
|
+
SELL: chalk_1.default.red,
|
|
776
|
+
STRONG_SELL: chalk_1.default.red.bold,
|
|
777
|
+
NO_TRADE: chalk_1.default.gray,
|
|
778
|
+
};
|
|
779
|
+
const signalIcons = {
|
|
780
|
+
STRONG_BUY: "\u{1F7E2}\u{1F7E2}",
|
|
781
|
+
BUY: "\u{1F7E2}",
|
|
782
|
+
WAIT: "\u{1F7E1}",
|
|
783
|
+
SELL: "\u{1F534}",
|
|
784
|
+
STRONG_SELL: "\u{1F534}\u{1F534}",
|
|
785
|
+
NO_TRADE: "\u{26AA}",
|
|
786
|
+
};
|
|
787
|
+
const signalColor = signalColors[result.signal];
|
|
788
|
+
const signalIcon = signalIcons[result.signal];
|
|
789
|
+
console.log("");
|
|
790
|
+
console.log(border.top);
|
|
791
|
+
// Header
|
|
792
|
+
line(chalk_1.default.bold.white(`\u{1F3AF} SMART TREND CONFLUENCE - ${symbol.toUpperCase()}`));
|
|
793
|
+
line(chalk_1.default.gray(`Higher TF: ${result.higherTimeframe.interval} | Lower TF: ${result.lowerTimeframe.interval}`));
|
|
794
|
+
// Main Signal
|
|
795
|
+
console.log(border.mid);
|
|
796
|
+
line(chalk_1.default.bold(`SIGNAL: ${signalIcon} ${signalColor(result.signal)}`));
|
|
797
|
+
line(`Direction: ${result.direction === "long"
|
|
798
|
+
? chalk_1.default.green("LONG \u{2191}")
|
|
799
|
+
: result.direction === "short"
|
|
800
|
+
? chalk_1.default.red("SHORT \u{2193}")
|
|
801
|
+
: chalk_1.default.gray("NONE")}`);
|
|
802
|
+
// Confluence Score
|
|
803
|
+
console.log(border.mid);
|
|
804
|
+
const scoreColor = result.score.total >= 85
|
|
805
|
+
? chalk_1.default.green
|
|
806
|
+
: result.score.total >= 70
|
|
807
|
+
? chalk_1.default.cyan
|
|
808
|
+
: result.score.total >= 50
|
|
809
|
+
? chalk_1.default.yellow
|
|
810
|
+
: chalk_1.default.red;
|
|
811
|
+
line(chalk_1.default.bold(`\u{1F4CA} CONFLUENCE SCORE: ${scoreColor(result.score.total + "/100")}`));
|
|
812
|
+
line(result.score.meetsMinimum
|
|
813
|
+
? chalk_1.default.green(" \u{2705} Meets minimum threshold (70)")
|
|
814
|
+
: chalk_1.default.red(" \u{274C} Below minimum threshold (70)"));
|
|
815
|
+
// Score Breakdown
|
|
816
|
+
console.log(border.mid);
|
|
817
|
+
line(chalk_1.default.gray("Score Breakdown:"));
|
|
818
|
+
const breakdown = result.score.breakdown;
|
|
819
|
+
const barWidth = 20;
|
|
820
|
+
const drawBar = (label, score, max) => {
|
|
821
|
+
const filled = Math.round((score / max) * barWidth);
|
|
822
|
+
const empty = barWidth - filled;
|
|
823
|
+
const bar = chalk_1.default.green("\u{2588}".repeat(filled)) + chalk_1.default.gray("\u{2591}".repeat(empty));
|
|
824
|
+
const scoreStr = `${score}/${max}`;
|
|
825
|
+
line(` ${label.padEnd(12)} ${bar} ${scoreStr}`);
|
|
826
|
+
};
|
|
827
|
+
drawBar("Trend", breakdown.trendAlignment, 30);
|
|
828
|
+
drawBar("Pullback", breakdown.pullbackQuality, 25);
|
|
829
|
+
drawBar("Key Level", breakdown.keyLevel, 20);
|
|
830
|
+
drawBar("Volume", breakdown.volume, 15);
|
|
831
|
+
drawBar("RSI", breakdown.rsiRange, 10);
|
|
832
|
+
// Trade Setup
|
|
833
|
+
if (result.signal !== "NO_TRADE" && result.signal !== "WAIT") {
|
|
834
|
+
console.log(border.mid);
|
|
835
|
+
line(chalk_1.default.bold("\u{1F4B0} TRADE SETUP"));
|
|
836
|
+
line(` Entry: ${chalk_1.default.white("$" + result.entry.toFixed(4))}`);
|
|
837
|
+
line(` Stop Loss: ${chalk_1.default.red("$" + result.stopLoss.toFixed(4))}`);
|
|
838
|
+
line(` Target 1: ${chalk_1.default.green("$" + result.takeProfit1.toFixed(4))} (1.5R)`);
|
|
839
|
+
line(` Target 2: ${chalk_1.default.green("$" + result.takeProfit2.toFixed(4))} (2.5R)`);
|
|
840
|
+
line(` Risk/Reward: ${chalk_1.default.cyan(result.riskRewardRatio + ":1")}`);
|
|
841
|
+
}
|
|
842
|
+
// Timeframe Analysis
|
|
843
|
+
console.log(border.mid);
|
|
844
|
+
line(chalk_1.default.bold("\u{1F4C8} TIMEFRAME ANALYSIS"));
|
|
845
|
+
const trendIcon = (trend) => trend === "bullish"
|
|
846
|
+
? chalk_1.default.green("\u{2191}")
|
|
847
|
+
: trend === "bearish"
|
|
848
|
+
? chalk_1.default.red("\u{2193}")
|
|
849
|
+
: chalk_1.default.gray("\u{2192}");
|
|
850
|
+
line(` ${result.higherTimeframe.interval.toUpperCase()}: ${trendIcon(result.higherTimeframe.trend)} ${result.higherTimeframe.trend.padEnd(8)} | RSI: ${result.higherTimeframe.rsi.toFixed(0)}`);
|
|
851
|
+
line(` ${result.lowerTimeframe.interval.toUpperCase()}: ${trendIcon(result.lowerTimeframe.trend)} ${result.lowerTimeframe.trend.padEnd(8)} | RSI: ${result.lowerTimeframe.rsi.toFixed(0)}`);
|
|
852
|
+
// EMA Values
|
|
853
|
+
console.log(border.mid);
|
|
854
|
+
line(chalk_1.default.bold("\u{1F4C9} EMA VALUES (Lower TF)"));
|
|
855
|
+
line(chalk_1.default.gray(` EMA10: $${result.lowerTimeframe.ema10.toFixed(4)}`));
|
|
856
|
+
line(chalk_1.default.gray(` EMA20: $${result.lowerTimeframe.ema20.toFixed(4)}`));
|
|
857
|
+
line(chalk_1.default.gray(` EMA50: $${result.lowerTimeframe.ema50.toFixed(4)}`));
|
|
858
|
+
if (result.lowerTimeframe.ema200 > 0) {
|
|
859
|
+
line(chalk_1.default.gray(` EMA200: $${result.lowerTimeframe.ema200.toFixed(4)}`));
|
|
860
|
+
}
|
|
861
|
+
// Pullback Status
|
|
862
|
+
console.log(border.mid);
|
|
863
|
+
line(chalk_1.default.bold("\u{1F504} PULLBACK STATUS"));
|
|
864
|
+
const pullbackColor = result.pullback.quality === "optimal"
|
|
865
|
+
? chalk_1.default.green
|
|
866
|
+
: result.pullback.quality === "acceptable"
|
|
867
|
+
? chalk_1.default.yellow
|
|
868
|
+
: chalk_1.default.red;
|
|
869
|
+
line(` Quality: ${pullbackColor(result.pullback.quality.toUpperCase())}`);
|
|
870
|
+
line(` In EMA Zone: ${result.pullback.inEMAZone ? chalk_1.default.green("Yes") : chalk_1.default.red("No")}`);
|
|
871
|
+
line(chalk_1.default.gray(` Distance to EMA10: ${result.pullback.distanceToEMA10.toFixed(2)}%`));
|
|
872
|
+
line(chalk_1.default.gray(` Distance to EMA20: ${result.pullback.distanceToEMA20.toFixed(2)}%`));
|
|
873
|
+
// Candle Pattern
|
|
874
|
+
if (result.candlePattern.type !== "none") {
|
|
875
|
+
console.log(border.mid);
|
|
876
|
+
line(chalk_1.default.bold("\u{1F56F} CANDLE PATTERN"));
|
|
877
|
+
line(` ${chalk_1.default.cyan(result.candlePattern.type.replace(/_/g, " ").toUpperCase())}`);
|
|
878
|
+
line(chalk_1.default.gray(` ${result.candlePattern.description}`));
|
|
879
|
+
line(chalk_1.default.gray(` Strength: ${result.candlePattern.strength}/100`));
|
|
880
|
+
}
|
|
881
|
+
// Key Levels
|
|
882
|
+
if (result.keyLevels.length > 0) {
|
|
883
|
+
console.log(border.mid);
|
|
884
|
+
line(chalk_1.default.bold("\u{1F511} KEY LEVELS"));
|
|
885
|
+
result.keyLevels.slice(0, 4).forEach((level) => {
|
|
886
|
+
const levelColor = level.type.includes("support") || level.type.includes("bullish") ? chalk_1.default.green : chalk_1.default.red;
|
|
887
|
+
const typeStr = level.type.replace(/_/g, " ");
|
|
888
|
+
line(` ${levelColor(typeStr.padEnd(18))} $${level.price.toFixed(4)} (${level.distance.toFixed(1)}%)`);
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
// Volume
|
|
892
|
+
console.log(border.mid);
|
|
893
|
+
line(chalk_1.default.bold("\u{1F4CA} VOLUME"));
|
|
894
|
+
const volColor = result.volume.volumeRatio > 1.2
|
|
895
|
+
? chalk_1.default.green
|
|
896
|
+
: result.volume.volumeRatio > 0.8
|
|
897
|
+
? chalk_1.default.yellow
|
|
898
|
+
: chalk_1.default.red;
|
|
899
|
+
line(` Ratio: ${volColor(result.volume.volumeRatio.toFixed(2) + "x")} average`);
|
|
900
|
+
line(` Trend: ${result.volume.trend}`);
|
|
901
|
+
// Confluence Factors
|
|
902
|
+
console.log(border.mid);
|
|
903
|
+
line(chalk_1.default.bold("\u{2705} CONFLUENCE FACTORS"));
|
|
904
|
+
result.score.factors.slice(0, 8).forEach((factor) => {
|
|
905
|
+
const color = factor.startsWith("\u{2713}") || factor.startsWith("✓")
|
|
906
|
+
? chalk_1.default.green
|
|
907
|
+
: factor.startsWith("\u{2717}") || factor.startsWith("✗")
|
|
908
|
+
? chalk_1.default.red
|
|
909
|
+
: chalk_1.default.yellow;
|
|
910
|
+
const truncated = factor.length > contentWidth - 3 ? factor.substring(0, contentWidth - 6) + "..." : factor;
|
|
911
|
+
line(color(` ${truncated}`));
|
|
912
|
+
});
|
|
913
|
+
// Reasoning
|
|
914
|
+
if (result.reasoning.length > 0) {
|
|
915
|
+
console.log(border.mid);
|
|
916
|
+
line(chalk_1.default.bold("\u{1F4DD} REASONING"));
|
|
917
|
+
result.reasoning.forEach((r) => {
|
|
918
|
+
const truncated = r.length > contentWidth - 5 ? r.substring(0, contentWidth - 8) + "..." : r;
|
|
919
|
+
line(chalk_1.default.gray(` • ${truncated}`));
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
// Warnings
|
|
923
|
+
if (result.warnings.length > 0) {
|
|
924
|
+
console.log(border.mid);
|
|
925
|
+
line(chalk_1.default.yellow.bold("\u{26A0}\u{FE0F} WARNINGS"));
|
|
926
|
+
result.warnings.forEach((w) => {
|
|
927
|
+
const truncated = w.length > contentWidth - 5 ? w.substring(0, contentWidth - 8) + "..." : w;
|
|
928
|
+
line(chalk_1.default.yellow(` • ${truncated}`));
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
// Footer
|
|
932
|
+
console.log(border.bot);
|
|
933
|
+
console.log("");
|
|
934
|
+
console.log(chalk_1.default.gray.italic(" \u{26A0}\u{FE0F} This is not financial advice. Always do your own research."));
|
|
935
|
+
console.log(chalk_1.default.gray.italic(" \u{1F4A1} Minimum score of 70 required. Only trade with proper risk management."));
|
|
936
|
+
console.log("");
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Run the Smart Trend Confluence Strategy
|
|
940
|
+
*/
|
|
941
|
+
async function runSmartStrategy(symbol, htfInterval = "4h", ltfInterval = "1h") {
|
|
942
|
+
const spinner = (0, ora_1.default)(`Running Smart Trend Confluence on ${symbol.toUpperCase()}...`).start();
|
|
943
|
+
try {
|
|
944
|
+
spinner.text = `Fetching ${htfInterval} and ${ltfInterval} data...`;
|
|
945
|
+
const result = await (0, strategy_1.generateStrategySignal)(symbol, htfInterval, ltfInterval);
|
|
946
|
+
spinner.succeed(`Strategy analysis complete for ${symbol.toUpperCase()}`);
|
|
947
|
+
displayStrategyResult(result, symbol);
|
|
948
|
+
}
|
|
949
|
+
catch (error) {
|
|
950
|
+
spinner.fail(`Failed to run strategy on ${symbol}`);
|
|
951
|
+
console.log("");
|
|
952
|
+
console.log(chalk_1.default.red(` Error: ${error.message || "Unknown error"}`));
|
|
953
|
+
if (error.message?.includes("Invalid symbol") || error.message?.includes("Failed to fetch")) {
|
|
954
|
+
const suggestions = await (0, data_fetcher_1.findSimilarSymbols)(symbol, 5);
|
|
955
|
+
if (suggestions.length > 0) {
|
|
956
|
+
console.log(chalk_1.default.gray("\n Did you mean: ") + chalk_1.default.cyan(suggestions.join(", ")));
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
console.log("");
|
|
960
|
+
}
|
|
961
|
+
}
|