Initial commit
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
import baostock as bs
|
||||
|
||||
from Common.CEnum import AUTYPE, DATA_FIELD, KL_TYPE
|
||||
from Common.CTime import CTime
|
||||
from Common.func_util import kltype_lt_day, str2float
|
||||
from KLine.KLine_Unit import CKLine_Unit
|
||||
|
||||
from .CommonStockAPI import CCommonStockApi
|
||||
|
||||
|
||||
def create_item_dict(data, column_name):
|
||||
for i in range(len(data)):
|
||||
data[i] = parse_time_column(data[i]) if i == 0 else str2float(data[i])
|
||||
return dict(zip(column_name, data))
|
||||
|
||||
|
||||
def parse_time_column(inp):
|
||||
# 20210902113000000
|
||||
# 2021-09-13
|
||||
if len(inp) == 10:
|
||||
year = int(inp[:4])
|
||||
month = int(inp[5:7])
|
||||
day = int(inp[8:10])
|
||||
hour = minute = 0
|
||||
elif len(inp) == 17:
|
||||
year = int(inp[:4])
|
||||
month = int(inp[4:6])
|
||||
day = int(inp[6:8])
|
||||
hour = int(inp[8:10])
|
||||
minute = int(inp[10:12])
|
||||
elif len(inp) == 19:
|
||||
year = int(inp[:4])
|
||||
month = int(inp[5:7])
|
||||
day = int(inp[8:10])
|
||||
hour = int(inp[11:13])
|
||||
minute = int(inp[14:16])
|
||||
else:
|
||||
raise Exception(f"unknown time column from baostock:{inp}")
|
||||
return CTime(year, month, day, hour, minute)
|
||||
|
||||
|
||||
def GetColumnNameFromFieldList(fileds: str):
|
||||
_dict = {
|
||||
"time": DATA_FIELD.FIELD_TIME,
|
||||
"date": DATA_FIELD.FIELD_TIME,
|
||||
"open": DATA_FIELD.FIELD_OPEN,
|
||||
"high": DATA_FIELD.FIELD_HIGH,
|
||||
"low": DATA_FIELD.FIELD_LOW,
|
||||
"close": DATA_FIELD.FIELD_CLOSE,
|
||||
"volume": DATA_FIELD.FIELD_VOLUME,
|
||||
"amount": DATA_FIELD.FIELD_TURNOVER,
|
||||
"turn": DATA_FIELD.FIELD_TURNRATE,
|
||||
}
|
||||
return [_dict[x] for x in fileds.split(",")]
|
||||
|
||||
|
||||
class CBaoStock(CCommonStockApi):
|
||||
is_connect = None
|
||||
|
||||
def __init__(self, code, k_type=KL_TYPE.K_DAY, begin_date=None, end_date=None, autype=AUTYPE.QFQ):
|
||||
super(CBaoStock, self).__init__(code, k_type, begin_date, end_date, autype)
|
||||
|
||||
def get_kl_data(self):
|
||||
# 天级别以上才有详细交易信息
|
||||
if kltype_lt_day(self.k_type):
|
||||
if not self.is_stock:
|
||||
raise Exception("没有获取到数据,注意指数是没有分钟级别数据的!")
|
||||
fields = "time,open,high,low,close"
|
||||
else:
|
||||
fields = "date,open,high,low,close,volume,amount,turn"
|
||||
autype_dict = {AUTYPE.QFQ: "2", AUTYPE.HFQ: "1", AUTYPE.NONE: "3"}
|
||||
rs = bs.query_history_k_data_plus(
|
||||
code=self.code,
|
||||
fields=fields,
|
||||
start_date=self.begin_date,
|
||||
end_date=self.end_date,
|
||||
frequency=self.__convert_type(),
|
||||
adjustflag=autype_dict[self.autype],
|
||||
)
|
||||
if rs.error_code != '0':
|
||||
raise Exception(rs.error_msg)
|
||||
while rs.error_code == '0' and rs.next():
|
||||
yield CKLine_Unit(create_item_dict(rs.get_row_data(), GetColumnNameFromFieldList(fields)))
|
||||
|
||||
def SetBasciInfo(self):
|
||||
rs = bs.query_stock_basic(code=self.code)
|
||||
if rs.error_code != '0':
|
||||
raise Exception(rs.error_msg)
|
||||
code, code_name, ipoDate, outDate, stock_type, status = rs.get_row_data()
|
||||
self.name = code_name
|
||||
self.is_stock = (stock_type == '1')
|
||||
|
||||
@classmethod
|
||||
def do_init(cls):
|
||||
if not cls.is_connect:
|
||||
cls.is_connect = bs.login()
|
||||
|
||||
@classmethod
|
||||
def do_close(cls):
|
||||
if cls.is_connect:
|
||||
bs.logout()
|
||||
cls.is_connect = None
|
||||
|
||||
def __convert_type(self):
|
||||
_dict = {
|
||||
KL_TYPE.K_DAY: 'd',
|
||||
KL_TYPE.K_WEEK: 'w',
|
||||
KL_TYPE.K_MON: 'm',
|
||||
KL_TYPE.K_5M: '5',
|
||||
KL_TYPE.K_15M: '15',
|
||||
KL_TYPE.K_30M: '30',
|
||||
KL_TYPE.K_60M: '60',
|
||||
}
|
||||
return _dict[self.k_type]
|
||||
@@ -0,0 +1,34 @@
|
||||
import abc
|
||||
from typing import Iterable
|
||||
|
||||
from KLine.KLine_Unit import CKLine_Unit
|
||||
|
||||
|
||||
class CCommonStockApi:
|
||||
def __init__(self, code, k_type, begin_date, end_date, autype):
|
||||
self.code = code
|
||||
self.name = None
|
||||
self.is_stock = None
|
||||
self.k_type = k_type
|
||||
self.begin_date = begin_date
|
||||
self.end_date = end_date
|
||||
self.autype = autype
|
||||
self.SetBasciInfo()
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_kl_data(self) -> Iterable[CKLine_Unit]:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def SetBasciInfo(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def do_init(cls):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def do_close(cls):
|
||||
pass
|
||||
@@ -0,0 +1,100 @@
|
||||
from datetime import datetime
|
||||
import pandas as pd
|
||||
from Common.CEnum import AUTYPE, DATA_FIELD, KL_TYPE
|
||||
from Common.CTime import CTime
|
||||
from Common.func_util import kltype_lt_day, str2float
|
||||
from KLine.KLine_Unit import CKLine_Unit
|
||||
from .CommonStockAPI import CCommonStockApi
|
||||
|
||||
|
||||
class WebDataAPI(CCommonStockApi):
|
||||
"""Web应用的数据API适配器,用于将web应用的数据转换为chan.py格式"""
|
||||
|
||||
# 类变量用于存储数据
|
||||
_data_cache = {}
|
||||
|
||||
def __init__(self, code, k_type=KL_TYPE.K_DAY, begin_date=None, end_date=None, autype=AUTYPE.QFQ):
|
||||
super().__init__(code, k_type, begin_date, end_date, autype)
|
||||
|
||||
@classmethod
|
||||
def set_data(cls, code, df_data):
|
||||
"""设置指定代码的数据"""
|
||||
cls._data_cache[code] = df_data
|
||||
|
||||
def get_kl_data(self):
|
||||
"""将DataFrame数据转换为CKLine_Unit迭代器"""
|
||||
df_data = self._data_cache.get(self.code)
|
||||
if df_data is None or len(df_data) == 0:
|
||||
return
|
||||
|
||||
# 确保数据按时间排序并重置索引
|
||||
df_data = df_data.sort_values('date').reset_index(drop=True)
|
||||
print(f"WebDataAPI: 处理 {len(df_data)} 条K线数据,K线级别: {self.k_type}")
|
||||
|
||||
# 检查并处理重复时间
|
||||
if 'timestamp' in df_data.columns:
|
||||
df_data = df_data.drop_duplicates(subset=['timestamp'], keep='last')
|
||||
df_data = df_data.sort_values('timestamp').reset_index(drop=True)
|
||||
|
||||
prev_timestamp = None
|
||||
for idx, row in df_data.iterrows():
|
||||
# 转换时间格式
|
||||
if isinstance(row['date'], str):
|
||||
time_obj = datetime.fromisoformat(row['date'].replace('Z', '+00:00'))
|
||||
else:
|
||||
time_obj = row['date']
|
||||
|
||||
# 检查时间戳确保单调递增
|
||||
if 'timestamp' in row:
|
||||
current_timestamp = row['timestamp']
|
||||
if prev_timestamp is not None and current_timestamp <= prev_timestamp:
|
||||
print(f"跳过重复或倒序时间戳: {current_timestamp}, 上个时间戳: {prev_timestamp}")
|
||||
continue
|
||||
prev_timestamp = current_timestamp
|
||||
|
||||
# 创建CTime对象 - 根据K线级别智能决定auto参数
|
||||
# 对于日线及以上级别,且时分秒为0的情况,使用auto=True
|
||||
# 对于分钟级别或有具体时分的数据,使用auto=False确保精确时间
|
||||
use_auto = False # 默认不使用auto,确保时间精确
|
||||
|
||||
# 只有在日线级别且时分秒都为0时才考虑使用auto
|
||||
if self.k_type in [KL_TYPE.K_DAY, KL_TYPE.K_WEEK, KL_TYPE.K_MON]:
|
||||
if time_obj.hour == 0 and time_obj.minute == 0 and time_obj.second == 0:
|
||||
use_auto = True
|
||||
|
||||
ctime = CTime(
|
||||
time_obj.year,
|
||||
time_obj.month,
|
||||
time_obj.day,
|
||||
time_obj.hour,
|
||||
time_obj.minute,
|
||||
time_obj.second,
|
||||
auto=use_auto
|
||||
)
|
||||
|
||||
# 输出详细调试信息(只输出前几条)
|
||||
if idx < 3:
|
||||
print(f"第{idx+1}条数据: 原始时间={time_obj}, CTime={ctime}, auto={use_auto}, timestamp={ctime.ts}")
|
||||
|
||||
# 创建数据字典
|
||||
data_dict = {
|
||||
DATA_FIELD.FIELD_TIME: ctime,
|
||||
DATA_FIELD.FIELD_OPEN: float(row['open']),
|
||||
DATA_FIELD.FIELD_HIGH: float(row['high']),
|
||||
DATA_FIELD.FIELD_LOW: float(row['low']),
|
||||
DATA_FIELD.FIELD_CLOSE: float(row['close']),
|
||||
DATA_FIELD.FIELD_VOLUME: float(row['volume']) if 'volume' in row and pd.notna(row['volume']) else 0.0
|
||||
}
|
||||
|
||||
yield CKLine_Unit(data_dict, autofix=True)
|
||||
|
||||
def SetBasciInfo(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def do_init(cls):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def do_close(cls):
|
||||
pass
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
from datetime import datetime
|
||||
|
||||
import ccxt
|
||||
import json
|
||||
from Common.CEnum import AUTYPE, DATA_FIELD, KL_TYPE
|
||||
from Common.CTime import CTime
|
||||
from Common.func_util import kltype_lt_day, str2float
|
||||
from KLine.KLine_Unit import CKLine_Unit
|
||||
|
||||
from .CommonStockAPI import CCommonStockApi
|
||||
|
||||
|
||||
def GetColumnNameFromFieldList(fileds: str):
|
||||
_dict = {
|
||||
"time": DATA_FIELD.FIELD_TIME,
|
||||
"open": DATA_FIELD.FIELD_OPEN,
|
||||
"high": DATA_FIELD.FIELD_HIGH,
|
||||
"low": DATA_FIELD.FIELD_LOW,
|
||||
"close": DATA_FIELD.FIELD_CLOSE,
|
||||
}
|
||||
return [_dict[x] for x in fileds.split(",")]
|
||||
|
||||
|
||||
class CCXT(CCommonStockApi):
|
||||
is_connect = None
|
||||
|
||||
def __init__(self, code, k_type=KL_TYPE.K_DAY, begin_date=None, end_date=None, autype=AUTYPE.QFQ):
|
||||
super(CCXT, self).__init__(code, k_type, begin_date, end_date, autype)
|
||||
|
||||
def get_kl_data(self):
|
||||
fields = "time,open,high,low,close"
|
||||
exchange = ccxt.binance()
|
||||
timeframe = self.__convert_type()
|
||||
since_date = exchange.parse8601(f'{self.begin_date}T08:00:00')
|
||||
|
||||
#file_path = "BTC_USDT-5m-futures.json"
|
||||
#with open(file_path, 'r') as file:
|
||||
# data = json.load(file)
|
||||
data = exchange.fetch_ohlcv(self.code, timeframe, since=since_date)
|
||||
for item in data:
|
||||
time_obj = datetime.fromtimestamp(item[0] / 1000)
|
||||
time_str = time_obj.strftime('%Y-%m-%d %H:%M:%S')
|
||||
item_data = [
|
||||
time_str,
|
||||
item[1],
|
||||
item[2],
|
||||
item[3],
|
||||
item[4],
|
||||
item[5]
|
||||
]
|
||||
yield CKLine_Unit(self.create_item_dict(item_data, GetColumnNameFromFieldList(fields)), autofix=True)
|
||||
|
||||
def SetBasciInfo(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def do_init(cls):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def do_close(cls):
|
||||
pass
|
||||
|
||||
def __convert_type(self):
|
||||
_dict = {
|
||||
KL_TYPE.K_DAY: '1d',
|
||||
KL_TYPE.K_WEEK: '1w',
|
||||
KL_TYPE.K_MON: '1M',
|
||||
KL_TYPE.K_5M: '5m',
|
||||
KL_TYPE.K_15M: '15m',
|
||||
KL_TYPE.K_30M: '30m',
|
||||
KL_TYPE.K_60M: '1h',
|
||||
KL_TYPE.K_1M: '1m', # need to add this 1m
|
||||
}
|
||||
return _dict[self.k_type]
|
||||
|
||||
def parse_time_column(self, inp):
|
||||
if len(inp) == 10:
|
||||
year = int(inp[:4])
|
||||
month = int(inp[5:7])
|
||||
day = int(inp[8:10])
|
||||
hour = minute = 0
|
||||
elif len(inp) == 17:
|
||||
year = int(inp[:4])
|
||||
month = int(inp[4:6])
|
||||
day = int(inp[6:8])
|
||||
hour = int(inp[8:10])
|
||||
minute = int(inp[10:12])
|
||||
elif len(inp) == 19:
|
||||
year = int(inp[:4])
|
||||
month = int(inp[5:7])
|
||||
day = int(inp[8:10])
|
||||
hour = int(inp[11:13])
|
||||
minute = int(inp[14:16])
|
||||
else:
|
||||
raise Exception(f"unknown time column from TradingView:{inp}")
|
||||
return CTime(year, month, day, hour, minute, auto=not kltype_lt_day(self.k_type))
|
||||
|
||||
def create_item_dict(self, data, column_name):
|
||||
for i in range(len(data)):
|
||||
data[i] = self.parse_time_column(data[i]) if i == 0 else str2float(data[i])
|
||||
return dict(zip(column_name, data))
|
||||
@@ -0,0 +1,86 @@
|
||||
import os
|
||||
|
||||
from Common.CEnum import DATA_FIELD, KL_TYPE
|
||||
from Common.ChanException import CChanException, ErrCode
|
||||
from Common.CTime import CTime
|
||||
from Common.func_util import str2float
|
||||
from KLine.KLine_Unit import CKLine_Unit
|
||||
|
||||
from .CommonStockAPI import CCommonStockApi
|
||||
|
||||
|
||||
def create_item_dict(data, column_name):
|
||||
for i in range(len(data)):
|
||||
data[i] = parse_time_column(data[i]) if column_name[i] == DATA_FIELD.FIELD_TIME else str2float(data[i])
|
||||
return dict(zip(column_name, data))
|
||||
|
||||
|
||||
def parse_time_column(inp):
|
||||
# 20210902113000000
|
||||
# 2021-09-13
|
||||
if len(inp) == 10:
|
||||
year = int(inp[:4])
|
||||
month = int(inp[5:7])
|
||||
day = int(inp[8:10])
|
||||
hour = minute = 0
|
||||
elif len(inp) == 17:
|
||||
year = int(inp[:4])
|
||||
month = int(inp[4:6])
|
||||
day = int(inp[6:8])
|
||||
hour = int(inp[8:10])
|
||||
minute = int(inp[10:12])
|
||||
elif len(inp) == 19:
|
||||
year = int(inp[:4])
|
||||
month = int(inp[5:7])
|
||||
day = int(inp[8:10])
|
||||
hour = int(inp[11:13])
|
||||
minute = int(inp[14:16])
|
||||
else:
|
||||
raise Exception(f"unknown time column from csv:{inp}")
|
||||
return CTime(year, month, day, hour, minute)
|
||||
|
||||
|
||||
class CSV_API(CCommonStockApi):
|
||||
def __init__(self, code, k_type=KL_TYPE.K_DAY, begin_date=None, end_date=None, autype=None):
|
||||
self.headers_exist = True # 第一行是否是标题,如果是数据,设置为False
|
||||
self.columns = [
|
||||
DATA_FIELD.FIELD_TIME,
|
||||
DATA_FIELD.FIELD_OPEN,
|
||||
DATA_FIELD.FIELD_HIGH,
|
||||
DATA_FIELD.FIELD_LOW,
|
||||
DATA_FIELD.FIELD_CLOSE,
|
||||
# DATA_FIELD.FIELD_VOLUME,
|
||||
# DATA_FIELD.FIELD_TURNOVER,
|
||||
# DATA_FIELD.FIELD_TURNRATE,
|
||||
] # 每一列字段
|
||||
self.time_column_idx = self.columns.index(DATA_FIELD.FIELD_TIME)
|
||||
super(CSV_API, self).__init__(code, k_type, begin_date, end_date, autype)
|
||||
|
||||
def get_kl_data(self):
|
||||
cur_path = os.path.dirname(os.path.realpath(__file__))
|
||||
file_path = f"{cur_path}/../{self.code}.csv"
|
||||
if not os.path.exists(file_path):
|
||||
raise CChanException(f"file not exist: {file_path}", ErrCode.SRC_DATA_NOT_FOUND)
|
||||
|
||||
for line_number, line in enumerate(open(file_path, 'r')):
|
||||
if self.headers_exist and line_number == 0:
|
||||
continue
|
||||
data = line.strip("\n").split(",")
|
||||
if len(data) != len(self.columns):
|
||||
raise CChanException(f"file format error: {file_path}", ErrCode.SRC_DATA_FORMAT_ERROR)
|
||||
if self.begin_date is not None and data[self.time_column_idx] < self.begin_date:
|
||||
continue
|
||||
if self.end_date is not None and data[self.time_column_idx] > self.end_date:
|
||||
continue
|
||||
yield CKLine_Unit(create_item_dict(data, self.columns))
|
||||
|
||||
def SetBasciInfo(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def do_init(cls):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def do_close(cls):
|
||||
pass
|
||||
Reference in New Issue
Block a user