Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions loader.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# -*- coding: gbk -*-

import importlib.util
import os
import sys
Expand Down
62 changes: 53 additions & 9 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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(),
Expand Down
43 changes: 43 additions & 0 deletions server_market_utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from server_runtime_utils import make_jsonable

def normalize_number(value):
if value in (None, ''):
return None
Expand Down Expand Up @@ -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)}