2024-12-12 14:22:30
经常混币圈、股票和外汇的朋友们都知道有一个东西叫 K线,大概长这样
同时你也可以选择添加更多的图表,如布林线、MACD、RSI等,用这些技术指标来辅助决策。
这些技术参数很有用,如果能把一段时间的结果交给LLM去分析,应该比凭着感觉瞎买更靠谱吧!
那这些技术参数是怎么来的呢……?
去花钱买!这个世界上还有什么是钞能力无法解决的事情吗?
https://taapi.io/ 已经有人做好了!有股票 也有加密货币的数据。就是……免费版频率限制有些严重,想要获取多点数据就等几个小时吧。一下子升级个 Pro 要花14.99欧元,在PoC阶段好像也不太值得……
实际上,这些技术参数,全部都是使用K线数据,由客户端计算出来的。K线数据很容易获取,很多交易所都提供API,但是计算这些参数需要比较强的数学知识😅
还好已经有人写好了相关的库,虽然是 C/C++写的,但是不怕啦早就有人写好了 Python的wrapper
我们需要这个东西来计算数据,这个库支持200多个技术指标,包括RSI,MACD
安装方式可以参考官方文档
以币安的合约数据为例,非常简单
pip install binance-futures-connector from binance.um_futures import UMFutures um_client = UMFutures() candles=um_client.klines(symbol=‘BTCUSDT’, interval=‘1m’)
这样我们就获取到了 1分钟时间间隔的数据
转换成 pandas的DataFrame
方便后续处理
pd.DataFrame(candles)
布林线使用收盘价格进行计算,而且需要多组数据才可以计算出来
upperband, middleband, lowerband = talib.BBANDS(self.df["close"], timeperiod=20, nbdevup=2, nbdevdn=2, matype=0)
同样也是收盘价
rsi6 = talib.RSI(self.df["close"], timeperiod=6) rsi12 = talib.RSI(self.df["close"], timeperiod=12) rsi24 = talib.RSI(self.df["close"], timeperiod=24)
收盘价
dif, dea, macd = talib.MACD(self.df["close"], fastperiod=12, slowperiod=26, signalperiod=9)
包括SMA和EMA
sma7 = talib.SMA(self.df["close"], timeperiod=7) sma25 = talib.SMA(self.df["close"], timeperiod=25) sma99 = talib.SMA(self.df["close"], timeperiod=99) ema7 = talib.EMA(self.df["close"], timeperiod=7) ema25 = talib.EMA(self.df["close"], timeperiod=25) ema99 = talib.EMA(self.df["close"], timeperiod=99)
LLM更适合处理json数据,pandas就很方便啦
self.df.to_json(orient="records", date_format="epoch", date_unit="s")
有了数据之后就可以把他们导出成 json,然后调用LLM进行处理。
这里需要找一个可用的LLM,比如 Mistral AI、OpenAI 、Gemini或者 Claude。由于国内注册比较麻烦,而且风控很严格,建议大家使用「头顶冒火」的API接口
使用什么模型因人而异,一次数据量比较大,gpt-4o可能是比较好的选择,Claude Opus和o1-preview比较贵,但是也可以考虑,头顶冒火网站也都有的
然后要做的事情就是写提示词,让AI分析结果了!🧐
很麻烦是不是……不要怕,我已经写好并且开源了。使用方式详见下一页!⬇️⬇️⬇️ 部分RSS阅读器可能无法显示下一页的内容,请点击链接打开网站阅读
2024-11-03 20:53:15
Cloduflare WARP真的非常好用,并且支持代理模式。启用这个模式之后,它会在本机监听一个socks端口,应用程序可以配置到这个端口来使用代理。
对于服务器来说,一般使用 warp-cli
warp-cli proxy port 60606 warp-cli mode proxy
之后你就可以 curl
啦
https_proxy=socks5://127.0.0.1:60606 http_proxy=socks5://127.0.0.1:60606 curl ipv4.win IP: 104.28.157.116 CLOUDFLARE.COM CLOUDFLARE.COM
然而,Go的程序不支持 socks 代理,要手动加transport我可没那个功夫去加。
好消息是,Go默认是尊重环境变量http_proxy
的。那么就要想办法给socks代理转换为http代理
这事很简单嘛!用pproxy
就好了,别的不会,这个肯定很会!
pproxy -v -l http://127.0.0.1:8118 -r socks5://127.0.0.1:60606 https_proxy=http://127.0.0.1:8118 http_proxy=http://127.0.0.1:8118 curl ipv4.win curl: (52) Empty reply from server # pproxy logs Serving on ipv? 127.0.0.1:8118 by http http 127.0.0.1:45012 -> socks5 127.0.0.1:60606 -> ipv4.win:80 Unknown remote protocol from 127.0.0.1
怎么报错了呢🤨
可能是 pproxy
的问题,那么用gost
!
gost -L http://127.0.0.1:8118 -F socks5://127.0.0.1:60606 2024/11/03 12:32:43 route.go:700: http://127.0.0.1:8118 on 127.0.0.1:8118 2024/11/03 12:32:46 http.go:162: [http] 127.0.0.1:33284 -> http://127.0.0.1:8118 -> ipv4.win:80 2024/11/03 12:32:46 http.go:257: [route] 127.0.0.1:33284 -> http://127.0.0.1:8118 -> 1@socks5://127.0.0.1:60606 -> ipv4.win:80 2024/11/03 12:32:46 http.go:280: [http] 127.0.0.1:33284 -> 127.0.0.1:8118 : unexpected EOF
那……Privoxy
forward-socks5 / 127.0.0.1:60606 .
也不行!
甚至直接在 Firefox里设置socks5代理,也不行🤨
任何网站都打不开
偶然取消 DNS请求的勾选,就成功了……突然恍然大悟,WARP可能不支持远程解析DNS
那么要么用回 socks4
pproxy -v -l http://127.0.0.1:8118 -r socks4://127.0.0.1:60606 gost -L http://127.0.0.1:8118 -F socks4://127.0.0.1:60606
要么给加上DNS的支持
gost -L "http://127.0.0.1:8118?dns=1.1.1.1" -F socks5://127.0.0.1:60606
人生中宝贵的几个小时就这么没了。
2024-10-12 15:10:27
既然是容器,那么也可以跑数据库的吧?那只要再加上一个PHP,就可以跑 WordPress、Typecho之类的应用了!
在 Azure上,用 Container Apps也是可以做到serverless PHP应用的,具体来说就是:
一个pod里开两个容器(不太推荐这种方式),一个是php-apache,一个是MySQL,通过挂载 Azure Files作为volumes来做数据存储。
为什么想这么玩,是因为:
使用这种方案的好处是,在访问量不是很大的情况下,成本应该可以忽略不计,甚至可以在无访问时缩放到0副本,真正“无服务器” 😂
创建资源组,创建一个vnet,因为存储不想公开给所有人,也方便以后和其他虚拟机内网互通。
创建的时候,Container Apps Environment 选择已有的虚拟网络
配置镜像,这个镜像是我自己构建的,基于 php:8.3-apache,支持MySQL、PostgreSQL、SQLite,添加了 mod_rewrite,足够给WordPress和Typecho用
Primary Service选择 Azure Files
我创建了两个,一个是数据库的,一个是网站文件的
在创建完存储后,去复制一下access key
在容器环境中添加 SMB
依次输入信息,这里没有补全,不要输入错了哦
在容器环境中创建完之后,就可以到容器应用中添加啦
这里需要注意,数据库的卷可能需要如下额外挂载参数
uid=999,gid=999,nobrl,mfsymlinks,cache=none
编辑容器,添加volume就可以了
然后添加一个数据库的sidecar
如果需要配置环境变量,也可以一起配置了
最终结果是这样的两个容器,在一个pod里,我这种穷人自然只能选择最低配置啦
默认存储是可以公网访问的(需要用户名密码),为了更安全,我们可以配置为只有某几个虚拟网络可以访问
可能需要进入 console,执行一下创建数据库之类的操作,这点就自行发挥了。
在不使用的时候,容器可以缩为0(可以配置为最低1副本)
在有请求的时候,真的能访问耶,而且还可以自动扩容!
正常的应用不应该这样配置的,两个容器在同一个pod内耦合也是不理想的。
如果想要利用 Azure的优良线路,正常来说应该选择如下方法之一
最后……
PHP用什么跑不好要用这个,真是只有真正的赛博精神病才能够想出来这种操作🤡
2024-10-06 15:24:51
我的所有的代码最终都做成了 Docker image,因此可以非常方便的拿出来部署,自然也可以部署在各种 managed container或者managed k8s的环境。
更重要的是,Azure Container Apps 提供一定的免费额度,一般普通用户都够用的了!
这次的情况有点特殊:
通常来说,在 Azure 上的虚拟机,如果是同一个数据中心的,想要内网互通,直接都创建在一个虚拟网络下就可以了,默认情况下就是互通的,而且默认的安全组是允许同一个虚拟网络的通信的。
如果是不同的数据中心,或者不同的订阅,那么就要创建不同的虚拟网络,不同的网段,然后创建 peering
创建的过程非常简单,鼠标点点下一步就可以了
容器应用需要使用独享的虚拟网络,因此没法重复利用虚拟机已有的虚拟网络。
在创建容器应用时,选择数据中心,这里就建议就近选择啦。然后Container Apps Environment 点击create new
在新的窗口中选择 Networking,配置自己的虚拟网络,如果你的容器不需要被访问,那么甚至可以选择 internal
下一步需要配置我们使用什么镜像
需要注意的是 Command Override和Arguments Override这两个。Command Override并不是docker
和docker-compose
中的command
,比如你像 docker-compose.yml
里这样写的
services: generic: image: ghcr.io/webp-pt/webplb:latest command: webplb -mode=worker -queue=generic
,把这个webplb -mode=worker -queue=generic
复制到 Azure Portal上,那就错了……
那就简单了嘛!直接把 webplb -mode=worker -queue=generic
这一串粘贴到 Arguments Override的文本框,恭喜你又错了🎉。
因为,每一个参数都要用逗号和空格隔开,正确的形式是这样的
webplb, -mode=worker, -queue=generic
再接下来选择容器的配置,小到 0.25C 0.5G RAM,大到 4C 8G都在这里,如需环境变量也可以一起配置
再下一步选择端口映射,一个是容器的一个是发布的端口
创建的过程比较慢,大概要10-20分钟。如果你的应用有 ingress,那么还会看到一个 Application URL。
容器创建好之后,在 Monitoring – Console 可以连接到容器,或者说是pod内部,之后可以用ping……嗯?😐 其实是通的,nc一下就知道了,我检查了很多地方也检查了安全组的ICMP配置,但是还是没法ping通不过就这样了吧😐
通过http请求数量去扩容,实时的,默认就应该有这条规则,可以根据自身的情况进行配置。在满足条件时会自动扩容
需要使用 Custom,azure使用的是KEDA, 在metadata中填入两个字段
比如下图的写法,就是CPU使用率超过80%就触发;内存也同理,只需要把cpu改成memory就可以
在这里可以配置缩放的上下限,上限是1000,下限……可以选择0。
对于 Container Apps来说,最低副本数量是可以为0的。这意味着,如果你的网站没有收到任何流量或请求,ACA 将会自动缩减到零副本,从而节省资源。
当然这样有一定的冷启动时间……所以如果希望长时间运行,可以改成1或者任何合适的数值。
需要注意的是,缩放出来的新的容器的配置,都和最开始创建时配置的是一样的哦。
轮询间隔30秒(KEDA),冷却期300秒。这些参数无法改动。
如果镜像更新了,或者需要更改配置,在 Azure Portal上也可以轻松完成
Containers – Edit and deploy 在下面container image这里就可以重新配置了,包括环境变量在内。
当然了懂得都懂,一个pod可以包含多个container,这里也可以加另外一个container的。
这我是没想到的!
在急需扩容的时候,我的代码却出了问题,导致程序直接卡死并不退出,无法触发扩容规则🤡
在 Azure 容器应用中设置缩放规则 https://learn.microsoft.com/zh-cn/azure/container-apps/scale-app?pivots=azure-portal
Scaling options in Azure Container Apps https://techcommunity.microsoft.com/t5/apps-on-azure-blog/scaling-options-in-azure-container-apps/ba-p/3878282
2024-09-28 17:56:24
WebP Cloud 提供一个接口,可以用于获取图片的元数据,比如长宽、大小、色彩空间以及blurhash。这部分计算,尤其是blurhash其实还是略有压力的,我们就想能不能把这部分功能放到回源请求上。
对于 Azure Function来说,这必然不是问题,因为他几乎就是一个完整的NodeJS环境,可以用sharp的;但是对于Workers,由于他是V8,只支持NodeJS最基本的一些API,最多带上Wasm,这样用sharp就变成了几乎不可能的事情,因为要调用libvips。
最终经过了我的艰苦探索,发现一个名为 @cf-wasm/photon
的库,可以用来获取图片基础信息。首先需要 import
import { PhotonImage, SamplingFilter, resize } from '@cf-wasm/photon';
用起来也还行,首先我们需要通过 fetch
获取图片,得到一个response
,可以从这里拿到 ArrayBuffer
const response = await fetch(url) const buffer = await response.arrayBuffer();
然后PhotonImage
需要Uint8Array
,那先转换一下
const inputBytes = new Uint8Array(buffer);
然后加载图片
const inputImage = PhotonImage.new_from_byteslice(inputBytes);
宽高可以用 inputImage.get_width()
和inputImage.get_height()
色彩空间可以用 inputImage.get_image_data().colorSpace
文件大小直接 buffer.length
就行
计算 blurhash建议先调整图片大小,毕竟 Worker有执行时间限制
const resized = resize(inputImage, 32, 32, SamplingFilter.Nearest);
然后计算
const blur = encode(resized.get_raw_pixels(), resized.get_width(), resized.get_height(), 4, 4);
至于图片格式,那只能靠magic header了,比如使用如下ChatGPT给我的神奇代码
function getImageFormatFromArrayBuffer(arrayBuffer) { const uint8Array = new Uint8Array(arrayBuffer); // Check for PNG (first 8 bytes: 89 50 4E 47 0D 0A 1A 0A) if ( uint8Array[0] === 0x89 && uint8Array[1] === 0x50 && uint8Array[2] === 0x4e && uint8Array[3] === 0x47 && uint8Array[4] === 0x0d && uint8Array[5] === 0x0a && uint8Array[6] === 0x1a && uint8Array[7] === 0x0a ) { return 'png'; } // Check for JPEG (first 3 bytes: FF D8 FF) if (uint8Array[0] === 0xff && uint8Array[1] === 0xd8 && uint8Array[2] === 0xff) { return 'jpeg'; } // Check for GIF (first 6 bytes: GIF87a or GIF89a) if ( uint8Array[0] === 0x47 && uint8Array[1] === 0x49 && uint8Array[2] === 0x46 && uint8Array[3] === 0x38 && (uint8Array[4] === 0x37 || uint8Array[4] === 0x39) && uint8Array[5] === 0x61 ) { return 'gif'; } // Check for BMP (first 2 bytes: 42 4D) if (uint8Array[0] === 0x42 && uint8Array[1] === 0x4d) { return 'bmp'; } // Check for WebP (first 4 bytes: 52 49 46 46 and "WEBP" in bytes 8-11) if ( uint8Array[0] === 0x52 && uint8Array[1] === 0x49 && uint8Array[2] === 0x46 && uint8Array[3] === 0x46 && uint8Array[8] === 0x57 && uint8Array[9] === 0x45 && uint8Array[10] === 0x42 && uint8Array[11] === 0x50 ) { return 'webp'; } return 'Unknown format'; }
部署
npm install @cf-wasm/photon npm install blurhash wrangler deploy
就可以了。wrangler会自动打包,把依赖和wasm也一起上传上去
如果你使用的是Azure Function,那么事情就简单多了,直接安装并使用 sharp 就行。需要注意的一点是,Azure Function可以选择运行的环境是Linux还是Windows。所以本地也要安装好正确的sharp然后才可以部署。
npm install --cpu=x64 --os=linux sharp
详情可以参考Cross-platform
原来是打算把 Worker和Function 用同一套代码库的,但是由于 Worker的限制,即使不同情况下使用不同的import,Worker还是没法兼容 Function🫠
所以只能分开两个分支了。
Cloudflare Workers 太弱智了,害我失去了人生中宝贵的三个小时
2024-09-27 14:18:33
WebP Cloud Services使用 Cloudflare Workers进行回源来保证原站服务器IP不泄漏。本着降本增效的理念,我们这次准备将部分回源请求迁移到 Azure Function。
Azure Function 同样也是由 Azure 提供的serverless服务,运行时可以使用 Python、NodeJS、Java和亲儿子 .NET、PowerShell,和 Cloudflare Workers一样,每个月前100万请求免费,具体信息可以参考价格表
我们的回源代码是使用 JavaScript写的,虽然这个语言挺扭曲的,但是为了方便迁移,就不变啦!
你需要有一个活跃订阅的Azure账号……
搜索栏搜索 Function App,点击 Create 按钮,会发现 Azure 提供了五种选项。选择第一个 Consumption
进入配置页面,可以新建一个资源组,需要选择的是 Runtime,这里选择 nodejs,然后 Region选择离用户最接近的地方。这里和 Cloudflare Workers不一样,Cloudflare Workers是在全球部署的,会在离用户最近的节点执行;Azure 这里是在固定的节点执行,所以对于大陆用户来说,日韩港新是比较不错的位置。
默认就行,要是看默认的名字不喜欢,可以自己改掉
这里要选择Enable public access,要不然访问不了。
Monitoring 想开就开,为了省钱可以关掉。当然后续也可以重新开启;
Deployment是配置CD的地方,可以以后再配置
部署完成之后 Azure 会提供一个URL,打开之后应该是这样的
不像 Cloudflare Workers 那么直接,开发 Azure Functions 最好用 VS Code。因为用 Azure Functions 扩展会很方便。
然后按F1弹出窗口 Azure Functions – Create New Project,语言就选择 JavaScript,愿意做类型体操也可以选择 TypeScript,或者任何你熟悉的语言都可以。
Trigger为如何触发Functions,这个场景下选择HTTP Trigger就可以了
src/functions/fetchImage.js
这个就是你的代码host.json
:配置F1 – Deploy to Function App,选择订阅-刚刚创建的function
部署成功之后,浏览器访问:
https://blog-fun-post.azurewebsites.net/api/fetchimage
就可以看到 Hello World信息了。其中:
api
是路由前缀,可以自定义的fetchimage
是代码里写好的路由,当然也可以自定义,默认代码里定义的nameAzure 上可以看到已经部署好的代码
点进去终于有了和 Cloudflare Workers一样的在线编辑页面(不过可能是只读的,和选择的语言有关)
每次改代码都部署一次确实不是好办法。这个时候就要用本地调试啦,由于我们是用 HTTP trigger的,调试起来非常简单方便。哦对了,调试 JavaScript 应用得有nodejs
打开 fetchImage.js
按下F5,这里选择 Connect Storage Account(其实选择 emulator绕过去也行,没关系)
如果是第一次运行,可能需要安装一些依赖,安装完成之后就会看到控制台输出了类似如下信息
Azure Functions Core Tools Core Tools Version: 4.0.6280 Commit hash: N/A +421f0144b42047aa289ce691dc6db4fc8b6143e6 (64-bit) Function Runtime Version: 4.834.3.22875 [2024-09-26T12:27:04.937Z] Debugger listening on ws://127.0.0.1:9229/882fb0c8-1957-4070-a6be-9cfd3250d77b [2024-09-26T12:27:04.938Z] For help, see: https://nodejs.org/en/docs/inspector [2024-09-26T12:27:05.003Z] Worker process started and initialized. [2024-09-26T12:27:05.058Z] Debugger attached. Functions: fetchImage: [GET,POST] http://localhost:7071/api/fetchImage For detailed output, run func with --verbose flag.
浏览器访问http://localhost:7071/api/fetchImag
e 就可以了,改了代码也会自动热加载,当然也可以打断点。
一个最简单的 Functions 代码是这样的
const { app } = require('@azure/functions'); app.http('fetch', { methods: ['GET', 'POST'], authLevel: 'anonymous', handler: async (request, context) => { context.log(`Http function processed request for url "${request.url}"`); return { body: `Hello hello hello` }; }, });
app.http
的第一个参数是名字,要唯一,并且默认是路由methods
表示接受的请求方法handler
是处理请求的地方,第一个参数是request
,可以用来获取请求体之类的信息,第二个是context
,执行上下文,包括日志功能、绑定数据、环境信息等。return
用于返回响应,按照文档 context.res
也应该可以,但是在我这里不行,可能需要额外配置一下。返回一个object,编辑器会自动补全,比如body, status,headers 等默认,functions的代码都是 在 /api
这个路由下的,在 host.json
里可以进行配置
"extensions": { "http": { "routePrefix": "" } }
这样就没有 api
这个前缀了,当然你也可以随便改!
默认路由名称是app.http
的第一个参数,如
app.http('fetch', { methods: ['GET', 'POST'], authLevel: 'anonymous', handler: handler, });
那么路由就是 fetch
,如果要自定义可以额外传入一个 route
。需要注意不可以是空字符串,也不可以是/
开头的
app.http('fetch', { methods: ['GET', 'POST'], authLevel: 'anonymous', handler: handler, route: "something" });
如果你希望应用在 https://example.com 直接访问,不需要/api
也不需要路由名称,那么就要综合以上两种方法,配置前缀,然后 route: "/"
不能留空😂,如果留空就会出现azure默认的页面
我们的Cloudflare Workers代码是这么写的,非常简单
async function handleProxy(post_body) { const headers = { "Accept": post_body.accept, "User-Agent": post_body.user_agent }; const response = await fetch(post_body.origin_url, { method: post_body.request_name, headers: headers }); if (response.ok) { const res = new Response(response.body, { status: response.status, statusText: response.statusText, headers: response.headers }); return res; } else { return new Response(response.statusText || "Unknown Error", { status: response.status, statusText: response.statusText }); } } export default { async fetch(request, env, ctx) { try { const post_body = await request.json(); return handleProxy(post_body); } catch (error) { return new Response("Invalid JSON data", { status: 400 }); } }, };
基本上要改的地方如下:
也就是改一下那个default
async function handler(request, context) { try { const body = await request.json(); return handleProxy(body); } catch (error) { console.error('JSON parsing error:', error); return new Response('Invalid JSON data', { status: 400 }); } }
好了你的函数写好了,可以部署了。就是这么简单,除了配置环境那里麻烦点,别的都很容易,毕竟大家都是nodejs 20,自带了fetch
和Response
甚至可以通过运行时信息不同,使用不同的入口函数,进而做到用一套代码。