diff --git a/loader.py b/loader.py index 75194d9..d20ff3a 100644 --- a/loader.py +++ b/loader.py @@ -1,3 +1,5 @@ +# -*- coding: gbk -*- + import importlib.util import os import sys diff --git a/server.py b/server.py index e79db24..254392b 100644 --- a/server.py +++ b/server.py @@ -26,6 +26,7 @@ normalize_int as market_normalize_int, normalize_market_time as market_normalize_market_time, normalize_number as market_normalize_number, + build_full_tick_payload as market_build_full_tick_payload, ) from server_runtime_utils import ( create_runtime as runtime_create_runtime, @@ -211,11 +212,14 @@ def _normalize_quote_symbols(values): values = values.split(',') result = [] for value in values: - normalized = _normalize_quote_symbol(value) - if normalized is None: + if value is None: continue - if normalized not in result: - result.append(normalized) + for part in str(value).split(','): # 支持多个逗号分隔的输入symbol + normalized = _normalize_quote_symbol(part) + if normalized is None: + continue + if normalized not in result: + result.append(normalized) return result @@ -1249,6 +1253,23 @@ def _build_quote_payload(symbol): 'has_data': quote is not None, } +def _build_download_history_payload(symbol, period, start, end): + normalized_symbol = _normalize_quote_symbol(symbol) + if normalized_symbol is None: + return {'error': 'symbol_required'} + + function = globals().get('download_history_data') + if not callable(function): + return {'error': 'download_history_data_unavailable'} + + try: + function(symbol, period or '1d', start or '', end or '') + + except Exception as exc: + return {'error': 'download_history_failed', 'detail': str(exc), + 'symbol': symbol, 'period': period or '1d', 'start': start, 'end': end} + return {'status': 'downloaded', 'symbol': symbol, 'period': period or '1d', + 'start': start or '', 'end': end or ''} def _build_signals_payload(symbol): return market_build_signals_payload(RUNTIME, symbol, _normalize_quote_symbol) @@ -1257,6 +1278,9 @@ def _build_signals_payload(symbol): def _build_longhubang_payload(symbol, start_time, end_time): return market_build_longhubang_payload(RUNTIME, symbol, start_time, end_time, _record_error) +def _build_full_tick_payload(symbols): + return market_build_full_tick_payload(RUNTIME, symbols) + def _build_accounts_payload(): return { @@ -1367,11 +1391,14 @@ def _build_instrument_payload(symbol): normalized_symbol = _normalize_quote_symbol(symbol) if normalized_symbol is None: return {'error': 'symbol_required'} - function = globals().get('get_instrument_detail') - if not callable(function): - return {'error': 'instrument_detail_unavailable'} + context = RUNTIME.context_ref + if context is None: + return {'error': 'context_unavailable', } + get_instrument_detail = getattr(context, 'get_instrument_detail', None) + if not callable(get_instrument_detail): + return {'error': 'get_instrument_detail_unavailable', } try: - detail = function(normalized_symbol) + detail = get_instrument_detail(normalized_symbol) except Exception as exc: return { 'error': 'instrument_detail_failed', @@ -1460,7 +1487,9 @@ def _build_http_response(request_bytes): 'endpoints': [ '/health', '/positions', '/accounts', '/quotes', '/quote', '/orders', '/deals', '/subscribe', '/unsubscribe', '/candles', '/signals', '/instrument', - '/options', '/option-trade-options', '/longhubang', '/order', '/ws', '/debug/trade', + '/options', '/option-trade-options', '/longhubang', '/order', '/ws', + '/full_tick', '/download_history', + '/debug/trade', ], }) if path == '/health': @@ -1578,6 +1607,21 @@ def _build_http_response(request_bytes): return _build_json_response(200, payload) if path == '/accounts': return _build_json_response(200, _build_accounts_payload()) + + if path == '/full_tick': + raw_symbols = query.get('symbols') or query.get('symbol') or [] + symbols = _normalize_quote_symbols(raw_symbols) + return _build_json_response(200, _build_full_tick_payload(symbols)) + + if path == '/download_history': + symbol = _normalize_quote_symbol((query.get('symbol') or [None])[0]) + period = str((query.get('period') or ['1d'])[0] or '1d') + start = str((query.get('start') or [''])[0] or '') + end = str((query.get('end') or [''])[0] or '') + payload = _build_download_history_payload(symbol, period, start, end) + status_code = 200 if not payload.get('error') else 400 + return _build_json_response(status_code, payload) + if path == '/debug/trade': return _build_json_response(200, { 'health': _build_health_payload(), diff --git a/server_market_utils.py b/server_market_utils.py index 181d615..997bf14 100644 --- a/server_market_utils.py +++ b/server_market_utils.py @@ -1,3 +1,5 @@ +from server_runtime_utils import make_jsonable + def normalize_number(value): if value in (None, ''): return None @@ -243,3 +245,44 @@ def build_signals_payload(runtime, symbol, normalize_quote_symbol): 'highest_buy_price': highest_buy_price, 'points': points, } + + + +def build_full_tick_payload(runtime, symbols): + """调用 ContextInfo.get_full_tick(stock_code=[]) 获取全速 tick 行情。 + + symbols: list[str],空列表表示全市场(取决于 QMT 订阅范围)。 + 返回的 tick dict 逐标的经 normalize_number 归整数值,再经 make_jsonable + 归整为可序列化结构。 + """ + context = runtime.context_ref + if context is None: + return {'symbols': symbols, 'count': 0, 'data': {}, 'error': 'context_unavailable'} + get_full_tick = getattr(context, 'get_full_tick', None) + if not callable(get_full_tick): + return {'symbols': symbols, 'count': 0, 'data': {}, 'error': 'full_tick_unavailable', + 'hint': 'ContextInfo.get_full_tick 在当前运行环境不可用'} + try: + raw = get_full_tick(symbols if symbols else []) + tick_fields = ('askPrice', 'bidPrice', 'askVol', 'bidVol') + ticks = {} + if isinstance(raw, dict): + for code, tick in raw.items(): + if not isinstance(tick, dict): + ticks[code] = make_jsonable(tick) + continue + norm = dict(tick) + for field in tick_fields: + arr = norm.get(field) + if isinstance(arr, (list, tuple)): + norm[field] = [normalize_number(v) for v in arr] + for scalar in ('lastPrice', 'open', 'high', 'low', 'amount', 'volume', + 'openInterest', 'transactionNum', 'lastOpenInterest', 'lastVolume', + 'lastAmount', 'settlementPrice', 'lastSettlementPrice', 'pe', 'sp'): + if scalar in norm: + norm[scalar] = normalize_number(norm[scalar]) + ticks[code] = make_jsonable(norm) + return {'symbols': symbols, 'count': len(ticks), 'data': ticks} + except Exception as exc: # 单标的/参数异常或归整异常时给出可读错误 + return {'symbols': symbols, 'count': 0, 'data': {}, 'error': 'full_tick_failed', 'detail': str(exc)} +