feat: 修复报错
This commit is contained in:
113
python-app/app/kline_viewer.py
Normal file
113
python-app/app/kline_viewer.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import mplfinance as mpf
|
||||
import pandas as pd
|
||||
import redis
|
||||
|
||||
from data_protocol import KlineMessage
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Realtime K-line viewer from Redis")
|
||||
parser.add_argument("--redis-host", default="127.0.0.1", help="Redis host")
|
||||
parser.add_argument("--redis-port", type=int, default=6379, help="Redis port")
|
||||
parser.add_argument("--redis-db", type=int, default=0, help="Redis DB")
|
||||
parser.add_argument("--redis-password", default="", help="Redis password")
|
||||
parser.add_argument("--channel", default="kline.stream", help="Pub/Sub channel")
|
||||
parser.add_argument("--window", type=int, default=80, help="Window size to render")
|
||||
parser.add_argument("--only-final", action="store_true", help="Render only closed candles")
|
||||
parser.add_argument("--timeout", type=float, default=1.0, help="Subscriber timeout (seconds)")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def upsert_message(buffer: OrderedDict[int, KlineMessage], message: KlineMessage, max_size: int) -> None:
|
||||
buffer[message.open_time] = message
|
||||
buffer.move_to_end(message.open_time)
|
||||
while len(buffer) > max_size:
|
||||
buffer.popitem(last=False)
|
||||
|
||||
|
||||
def to_dataframe(messages: list[KlineMessage]) -> pd.DataFrame:
|
||||
rows = []
|
||||
for msg in messages:
|
||||
rows.append(
|
||||
{
|
||||
"Date": pd.to_datetime(msg.open_time, unit="ms"),
|
||||
"Open": float(msg.open),
|
||||
"High": float(msg.high),
|
||||
"Low": float(msg.low),
|
||||
"Close": float(msg.close),
|
||||
"Volume": float(msg.volume),
|
||||
}
|
||||
)
|
||||
frame = pd.DataFrame(rows).set_index("Date")
|
||||
return frame
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
plt.ion()
|
||||
client = redis.Redis(
|
||||
host=args.redis_host,
|
||||
port=args.redis_port,
|
||||
db=args.redis_db,
|
||||
password=args.redis_password or None,
|
||||
decode_responses=True,
|
||||
)
|
||||
pubsub = client.pubsub(ignore_subscribe_messages=True)
|
||||
pubsub.subscribe(args.channel)
|
||||
|
||||
print(
|
||||
f"subscribed redis channel={args.channel} "
|
||||
f"at {args.redis_host}:{args.redis_port}/{args.redis_db}"
|
||||
)
|
||||
|
||||
candles: OrderedDict[int, KlineMessage] = OrderedDict()
|
||||
|
||||
try:
|
||||
while True:
|
||||
packet = pubsub.get_message(timeout=args.timeout)
|
||||
if not packet:
|
||||
plt.pause(0.01)
|
||||
continue
|
||||
|
||||
raw = packet.get("data")
|
||||
if not isinstance(raw, str):
|
||||
continue
|
||||
|
||||
payload = json.loads(raw)
|
||||
message = KlineMessage.from_dict(payload)
|
||||
if args.only_final and not message.final:
|
||||
continue
|
||||
|
||||
upsert_message(candles, message, args.window)
|
||||
frame = to_dataframe(list(candles.values()))
|
||||
if frame.empty:
|
||||
continue
|
||||
|
||||
plt.clf()
|
||||
mpf.plot(
|
||||
frame,
|
||||
type="candle",
|
||||
style="yahoo",
|
||||
volume=True,
|
||||
datetime_format="%H:%M",
|
||||
tight_layout=True,
|
||||
)
|
||||
plt.pause(0.001)
|
||||
except KeyboardInterrupt:
|
||||
print("viewer stopped by user")
|
||||
finally:
|
||||
pubsub.close()
|
||||
client.close()
|
||||
plt.ioff()
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user