commit 50ab2cf69c64db66a27c3c01e3abaa36e5b10e9c Author: 李东云 Date: Mon Feb 10 18:30:16 2025 +0800 Initialize project structure with Bun, Elysia, and AI-powered article summarization diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b1ee42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/2025-2-10.md b/2025-2-10.md new file mode 100644 index 0000000..d3face5 --- /dev/null +++ b/2025-2-10.md @@ -0,0 +1,13 @@ +# 总结 + +**核心观点:** + +* **指数层面:** 虽然今天收小阳线,但存在隐患,主要问题是成交量大幅萎缩,即便突破3322点(60日线)问题可能更大。下午的上攻伴随量能萎缩,呈现量价背离,需要警惕。 +* **操作建议:** 如果明天高开个股出现进攻衰竭信号,可适当减仓,以防量能持续萎缩导致市场降温。但由于今天只是第一天量价不匹配,若未来量能能再次放大,则个股仍有操作空间。 +* **仓位:** 个人维持5-5.5成仓位,消费和医药持仓为主,不考虑加仓。目前位置不适合中线布局,除非是长线布局。 +* **DS题材(可能指数字经济或算力概念):** 目前已进入深度博傻阶段,等待接盘侠。三大运营商(电信、联通)必须持续冲新高,否则可能面临负反馈。资金不会立刻撤出DS,仍会在科技股中流动,直到更大的利好出现。 +* **风险提示:** 不建议追高DS题材,现在入场已经太晚,如果要参与只能小仓位短线投机,并设置止损。不要盲目相信科技信仰,避免高位接盘。 + +**总结:** + +作者认为当前市场存在量价背离的隐患,建议谨慎操作,注意控制仓位。对于近期火热的DS题材,作者持谨慎态度,认为已进入博傻阶段,不建议追高。 diff --git a/README.md b/README.md new file mode 100644 index 0000000..b4a02e7 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# info_flow + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.js +``` + +This project was created using `bun init` in bun v1.1.37. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/bak/ai.js b/bak/ai.js new file mode 100644 index 0000000..edccd1a --- /dev/null +++ b/bak/ai.js @@ -0,0 +1,15 @@ +// ai.js +export async function analyzeContent(content) { + // 假设DeepSeek API的调用方式 + const response = await fetch("https://api.deepseek.com/v1/analyze", { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer sk-75972dd6431e4440a0428fa8922ed6b1", + }, + body: JSON.stringify({ content }), + }); + const result = await response.json(); + console.log('Result:', result); + return result.summary; // 假设返回的是Markdown格式的总结 +} diff --git a/bak/rss.js b/bak/rss.js new file mode 100644 index 0000000..003d34f --- /dev/null +++ b/bak/rss.js @@ -0,0 +1,21 @@ +// ... existing code ... +export async function fetchRSS(url, maxRetries = 3) { + let retries = 0; + while (retries < maxRetries) { + try { + const response = await fetch(url, { verbose: true, keepalive: true, timeout: 10000, compress: false }); + // console.log('Response:', response); + const text = await response.text(); + // console.log('Response Text:', text); + return text; + } catch (error) { + console.error(`Fetch Error (Attempt ${retries + 1}):`, error); + retries++; + // 等待一段时间再重试,避免过于频繁的请求 + await new Promise(resolve => setTimeout(resolve, 1000)); + } + } + console.error('Fetch failed after multiple retries.'); + return null; +} +// ... existing code ... \ No newline at end of file diff --git a/bak/server.js b/bak/server.js new file mode 100644 index 0000000..bfbd872 --- /dev/null +++ b/bak/server.js @@ -0,0 +1,29 @@ +// server.js +import { serve } from "bun"; +import { fetchRSS } from "./rss.js"; +import { analyzeContent } from "./ai.js"; +import { getWeChatArticleLinks } from "./wechat.js"; +const PORT = 3000; + +async function handleRequest(request) { + // 从环境变量中获取 RSS URL,如果没有则使用默认值 + const rssUrl = "https://mp.weixin.qq.com/s/vQgsMuxXffpFZkNFj89wUQ"; + try { + const rssContent = await fetchRSS(rssUrl); + const analyzedContent = await analyzeContent(rssContent); + + return new Response(analyzedContent, { + headers: { "Content-Type": "text/markdown" }, + }); + } catch (error) { + console.error("Error processing request:", error); + return new Response("Error processing request", { status: 500 }); + } +} + +serve({ + port: PORT, + fetch: handleRequest, +}); + +console.log(`Server running at http://localhost:${PORT}`); diff --git a/bak/wechat.js b/bak/wechat.js new file mode 100644 index 0000000..c7f4851 --- /dev/null +++ b/bak/wechat.js @@ -0,0 +1,24 @@ +import { JSDOM } from 'jsdom'; + +async function getWeChatArticleLinks(publicAccountName) { + // const searchUrl = `https://weixin.sogou.com/weixin?p=01030402&query=${encodeURIComponent(publicAccountName)}&type=2&ie=utf8`; + // const response = await fetch(searchUrl); + console.log(`https://mp.weixin.qq.com/mp/profile_ext?action=getmsg&__biz=${publicAccountName}`); + const response = await fetch(`https://mp.weixin.qq.com/mp/profile_ext?action=getmsg&__biz=${publicAccountName}`, { + headers: { + 'Referer': `https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=${publicAccountName}` + } +}); + const html = await response.text(); + + const { document } = new JSDOM(html).window; + const articleLinks = []; + const articleElements = document.querySelectorAll('.news-box .news-list li a'); + articleElements.forEach((element) => { + const link = element.href; + articleLinks.push(link); + }); + return articleLinks; +} + +export { getWeChatArticleLinks }; \ No newline at end of file diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..b546e67 Binary files /dev/null and b/bun.lockb differ diff --git a/config.json b/config.json new file mode 100644 index 0000000..e69de29 diff --git a/global.d.ts b/global.d.ts new file mode 100644 index 0000000..9e77821 --- /dev/null +++ b/global.d.ts @@ -0,0 +1 @@ +declare module '@elysiajs/html'; \ No newline at end of file diff --git a/history/2025/2/10.md b/history/2025/2/10.md new file mode 100644 index 0000000..1efbbfb --- /dev/null +++ b/history/2025/2/10.md @@ -0,0 +1,78 @@ +好的,我已经收到了你提供的三篇文章。现在我将对它们进行分析,并为你设计短、中、长线的投资策略。 + +**一、文章核心内容总结:** + +* **文章1(2.10日收盘总结):** + * **市场隐患:** 量能大幅缩量,下午出现量价背离,进攻衰竭信号。 + * **操作建议:** 尾盘/明早高开出现进攻衰竭信号的个股减仓。如果后续量能放大或维持常态,则个股仍可操作,不轻易大减仓。 + * **仓位建议:** 维持5-5.5成仓位。消费和医药持仓为主,不加仓。 + * **投资策略:** 中线布局意义不大,除非是半年以上不动的大长线布局。 + * **Deepseek (DS) 风险:** 深度博傻阶段,等接盘侠。三大运营商必须继续冲新高,否则负反馈可能开始。不建议追高,手痒玩玩投机仓短线带好止损。资金仍在科技股里流动,等待更大层面的重磅利好。 +* **文章2 (阳气乍露,日之初升):** + * **市场定性:** 情绪周期为上升。 + * **操作建议:** 关注拓维信息、华胜天成,给机会可考虑上车。 + * **Deepseek (DS) 策略:** DS是市场最强主线,等待DS大分歧时上车补票。前排给机会大胆上车。 +* **文章3 (重要提示):** + * **应对策略:** 在节奏感不好的时候,耐心等待一个看得懂的极致最票的模式内机会。 + * **龙头战法:** 行情超预期走强后,重新改变行情定性,出现龙头强分机会。 + * **案例:** 每日互动 (DS概念),行情级别在确定性扩大加强,作为市场最强票,绕不过去。 + * **强趋势股梳理:** 列举了云服务、DS、哪吒、AI医疗、端侧、智驾、机器人等方向的个股,作为一段时间内的观察标的。 + * **主要题材梳理:** DS概念、中兵系重组。 + +**二、投资策略设计:** + +综合以上三篇文章的信息,我可以为你设计一个短中长线的投资策略,请注意,这只是根据提供的资料做出的建议,实际投资需要结合你的风险承受能力、资金情况和市场变化灵活调整。 + +**1. 短线策略(1-3周):** + +* **核心:** 把握Deepseek (DS) 的短线机会,但控制风险。 +* **选股:** + * **激进型:** 关注DS概念中的龙头个股,如文章3提到的每日互动,或者其他文中提及的DS概念股。 + * **稳健型:** 等待DS概念出现大分歧时,选择调整充分、基本面较好的个股逢低介入。关注文章2提到的拓维信息、华胜天成,若调整到位可考虑。 +* **操作:** + * 严格止损:因为DS概念处于博傻阶段,一旦出现亏损,立即止损。文章1建议手痒玩玩投机仓短线带好止损。 + * 快进快出:不恋战,赚取快钱,避免成为接盘侠。 + * 关注量价关系:文章1提到量价背离是风险信号,要警惕。 +* **仓位:** 短线仓位控制在总仓位的10-20%。 + +**2. 中线策略(3个月-1年):** + +* **核心:** 围绕科技主线,选择有业绩支撑、成长性较好的个股,适当关注消费和医药。 +* **选股:** + * **科技:** 从云服务、AI医疗、端侧、智驾、机器人等方向中,选择细分行业的龙头企业。 + * **消费/医药:** 持有原有仓位,不加仓。 +* **操作:** + * 逢低布局:选择在回调时逐步建仓。 + * 价值投资:关注公司的基本面,长期持有。 +* **仓位:** 中线仓位控制在总仓位的30-40%。 + +**3. 长线策略(1年以上):** + +* **核心:** 低位布局,长期持有,耐心等待。 +* **选股:** + * **低估值潜力股:** 寻找被市场低估的、有长期发展潜力的个股。 + * **行业龙头:** 选择具有长期竞争优势的行业龙头企业。 +* **操作:** + * 分批建仓:在低位分批买入,降低成本。 + * 长期持有:不轻易交易,享受企业成长带来的收益。 +* **仓位:** 长线仓位控制在总仓位的30-40%。 + +**4. 整体仓位控制:** + +* 根据你的风险承受能力,将总仓位控制在50-70%。 +* 短、中、长线仓位的比例可以根据市场情况进行调整。 +* 保留一定的现金,以备不时之需。 + +**5. 风险提示:** + +* 股市有风险,投资需谨慎。 +* 密切关注市场动态,及时调整投资策略。 +* 不要盲目跟风,要有自己的判断。 +* 控制好仓位,避免过度投资。 + +**6. 需要进一步考虑的问题:** + +* **文章1中提到“其他更大更高层面的重磅利好”是什么?** 这个利好出现时,可能会打破DS的局面,需要密切关注。 +* **中兵系重组的机会如何?** 文章3提到了中兵系重组,可以进一步研究相关标的。 + +希望这个策略能给你一些参考。记住,投资需要不断学习和实践,才能找到最适合自己的方法。祝你投资顺利! diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..2a5582d --- /dev/null +++ b/index.ts @@ -0,0 +1,143 @@ +import { Elysia } from "elysia"; +import { html } from '@elysiajs/html' +import { JSDOM } from 'jsdom'; +import { GoogleGenerativeAI } from "@google/generative-ai"; + +const genAI = new GoogleGenerativeAI(process.env.API_KEY || ""); + +const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash-lite-preview-02-05" }); + +async function callGeminiAPI(articleContent: string): Promise { + // articleContent = `You are a helpful assistant that summarizes WeChat articles.use Chinese: ${articleContent}`; + // const response = (await model.generateContent(articleContent)).response; + // return response.text(); + + + const { + GoogleGenerativeAI, + HarmCategory, + HarmBlockThreshold, + } = require("@google/generative-ai"); + + const apiKey = process.env.API_KEY; + const genAI = new GoogleGenerativeAI(apiKey); + + const model = genAI.getGenerativeModel({ + model: "gemini-2.0-flash", + }); + + const generationConfig = { + temperature: 1, + topP: 0.95, + topK: 40, + maxOutputTokens: 8192, + responseMimeType: "text/plain", + }; + + async function run(articleContent: string) { + const chatSession = model.startChat({ + generationConfig, + history: [ + { + role: "user", + parts: [ + { text: "接下来我会给你提供几篇文章,用---和换行分割。如果是投资主题,我希望你能帮我总结和设计出一个短中长线的投资策略:" }, + ], + }, + { + role: "model", + parts: [ + { text: "请提供你说的文章,我会根据内容总结并设计投资策略。你需要将几篇文章用`---`和换行分割,像你例子里那样。" }, + ], + }, + { + role: "user", + parts: [ + { text: articleContent }, + ], + } + ], + }); + + const result = await chatSession.sendMessage("INSERT_INPUT_HERE"); + console.log(result.response.text()); + return result.response.text(); + } + + return run(articleContent); +} + +const app = new Elysia() + .use(html()) + .get("/", () => ` + + + + 文章总结 + + +

输入微信文章链接

+
+
+

+ +
+ + + `) + .post("/summarize", async ({ body }) => { + try { + const { articleUrl } = body as { articleUrl: string }; + const urls = articleUrl.split('\n').filter(url => url.trim() !== ''); + + async function fetchArticle(url: string): Promise { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to fetch article: Status Code ${response.status}`); + } + return await response.text(); + } + + let articleText = ''; + for (const url of urls) { + try { + const articleHTML = await fetchArticle(url); + const dom = new JSDOM(articleHTML); + const jsContent = dom.window.document.querySelector("#js_content"); + const content = jsContent ? jsContent.textContent : ""; + articleText += content + '\n---\n'; + } catch (error) { + console.error(`Failed to process article from ${url}:`, error); + // Optionally, you might want to handle the error more gracefully, + // such as skipping the article or returning a default value. + } + } + + // return articleText; + const geminiResponse = await callGeminiAPI(articleText); + const today = new Date(); + const fs = require('fs'); + const path = require('path'); + const fileName = `history/${today.getFullYear()}/${today.getMonth() + 1}/${today.getDate()}.md`; + const dir = path.dirname(fileName); + + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + fs.writeFileSync(fileName, geminiResponse); + return new Response(geminiResponse, { + headers: { + "Content-Type": "text/markdown; charset=utf-8", + }, + }); + } catch (error: any) { + console.error("Error:", error); + return { error: error.message }; + } + }) + .listen({ port: 3000 }); + +console.log( + `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` +); diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..238655f --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..871be67 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "info_flow", + "module": "index.js", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "type": "module", + "dependencies": { + "@elysiajs/html": "^1.2.0", + "@google/generative-ai": "^0.21.0", + "cheerio": "^1.0.0", + "dotenv": "^16.4.7", + "elysia": "^1.2.12", + "jsdom": "^26.0.0", + "openai": "^4.83.0" + } +} \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..749ab24 --- /dev/null +++ b/style.css @@ -0,0 +1,11 @@ +/* style.css */ +body { + max-width: 768px; + margin: 0 auto; + font-family: system-ui; + line-height: 1.6; +} +.timestamp { + color: #666; + font-size: 0.8em +} diff --git a/wechat.js b/wechat.js new file mode 100644 index 0000000..e1bd9ca --- /dev/null +++ b/wechat.js @@ -0,0 +1,16 @@ +import { JSDOM } from 'jsdom'; + +async function getWeChatArticleLinks(publicAccountName) { + const html = await response.text(); + + const { document } = new JSDOM(html).window; + const articleLinks = []; + const articleElements = document.querySelectorAll('.news-box .news-list li a'); + articleElements.forEach((element) => { + const link = element.href; + articleLinks.push(link); + }); + return articleLinks; +} + +export { getWeChatArticleLinks }; \ No newline at end of file