示例#1
0
class HistoricalPriceRecord(collections.namedtuple('HistoricalPriceRecord', ['time', 'open', 'high', 'low', 'close', 'volume'])):

    __slots__ = ()

    _krx_timezone = get_krx_timezone()

    @classmethod
    def from_tuple(cls, tup):
        if '일자' in tup._fields:
            dt = datetime.datetime.strptime(tup.일자, '%Y%m%d')
            dt = cls._krx_timezone.localize(dt)
            time = dt.timestamp() * (10 ** 6) # pylint: disable=redefined-outer-name
        elif '체결시간' in tup._fields:
            dt = datetime.datetime.strptime(tup.체결시간, '%Y%m%d%H%M%S')
            dt = cls._krx_timezone.localize(dt)
            time = dt.timestamp() * (10 ** 6)
        else:
            raise KiwoomOpenApiPlusError('Cannot specify time')
        open = abs(float(tup.시가)) # pylint: disable=redefined-builtin
        high = abs(float(tup.고가))
        low = abs(float(tup.저가))
        close = abs(float(tup.현재가))
        volume = abs(float(tup.거래량))
        return cls(time, open, high, low, close, volume)

    @classmethod
    def records_from_dataframe(cls, df):
        return [cls.from_tuple(tup) for tup in df[::-1].itertuples()]

    @classmethod
    def dict_records_from_dataframe(cls, df):
        return [msg._asdict() for msg in cls.records_from_dataframe(df)]
示例#2
0
class API:

    # 우선은 최대한 기존 Oanda 구현을 유지한채로 맞춰서 동작할 수 있도록 구현해놓고
    # 추후 동작이 되는게 확인되면 천천히 하단 API 에 맞게 최적화를 하는 방향으로 작업하는 것으로...

    _krx_timezone = get_krx_timezone()

    def __init__(self, context):
        self._context = context
        self._codes = self._context.GetCodeListByMarketAsList()

    def __getattr__(self, name):
        return getattr(self._context, name)

    def get_instruments_original(self, account, instruments): # TODO: 계좌에 따라 시장이 다를 수 있음
        instruments = self.GetStockInfoAsDataFrame(instruments)
        instruments = [tup._asdict() for tup in instruments.itertuples(index=False)]
        response = {'instruments': instruments}
        return response

    def get_instruments(self, account, instruments):
        if isinstance(instruments, str):
            instruments = [instruments]
        instruments = [inst for inst in instruments if inst in self._codes]
        response = {'instruments': instruments}
        return response

    def get_history(self, trcode, inputs, dtbegin=None, dtend=None):
        if trcode == 'opt10079':
            code = inputs['종목코드']
            interval = inputs['틱범위']
            adjusted_price = inputs.get('수정주가구분') == '1'
            df = self.GetTickStockDataAsDataFrame(code, interval, dtend, dtbegin, adjusted_price=adjusted_price)
        elif trcode == 'opt10080':
            code = inputs['종목코드']
            interval = inputs['틱범위']
            adjusted_price = inputs.get('수정주가구분') == '1'
            df = self.GetMinuteStockDataAsDataFrame(code, interval, dtend, dtbegin, adjusted_price=adjusted_price)
        elif trcode == 'opt10081':
            code = inputs['종목코드']
            adjusted_price = inputs.get('수정주가구분') == '1'
            df = self.GetDailyStockDataAsDataFrame(code, dtend, dtbegin, adjusted_price=adjusted_price)
        elif trcode == 'opt10082':
            code = inputs['종목코드']
            adjusted_price = inputs.get('수정주가구분') == '1'
            df = self.GetWeeklyStockDataAsDataFrame(code, dtend, dtbegin, adjusted_price=adjusted_price)
        elif trcode == 'opt10083':
            code = inputs['종목코드']
            adjusted_price = inputs.get('수정주가구분') == '1'
            df = self.GetMonthlyStockDataAsDataFrame(code, dtend, dtbegin, adjusted_price=adjusted_price)
        elif trcode == 'opt10094':
            code = inputs['종목코드']
            adjusted_price = inputs.get('수정주가구분') == '1'
            df = self.GetYearlyStockDataAsDataFrame(code, dtend, dtbegin, adjusted_price=adjusted_price)
        else:
            raise KiwoomOpenApiPlusError('Unexpected trcode %s' % trcode)

        candles = HistoricalPriceRecord.dict_records_from_dataframe(df)
        response = {'candles': candles}
        return response

    def get_positions(self, account):
        _summary, foreach = self.GetAccountEvaluationStatusAsSeriesAndDataFrame(account)
        positions = [{
            'instrument': tup.종목코드,
            'side': 'buy',
            'units': float(tup.보유수량),
            'avgPrice': float(tup.매입금액) / float(tup.보유수량),
        } for tup in foreach.itertuples()]
        response = {'positions': positions}
        return response

    def get_account(self, account):
        summary, _foreach = self.GetAccountEvaluationStatusAsSeriesAndDataFrame(account)
        response = {
            'marginAvail': float(summary['D+2추정예수금']),
            'balance': float(summary['추정예탁자산']),
        }
        return response

    def create_order(self, account, **kwargs):
        request_name = 'create_order(%s, %s)' % (account, kwargs)
        screen_no = ''
        account_no = account
        order_type = {
            'buy': 1,
            'sell': 2,
        }[kwargs['side']]
        code = kwargs['instrument']
        quantity = kwargs['units']
        price = kwargs.get('price', 0)
        quote_type = {
            'limit': '00',
            'market': '03',
        }[kwargs['type']]
        original_order_no = ''
        responses = self.OrderCall(
            request_name,
            screen_no,
            account_no,
            order_type,
            code,
            quantity,
            price,
            quote_type,
            original_order_no,
        )
        _msg = next(responses)
        _tr = next(responses)
        accept = next(responses)
        accept_data = dict(zip(accept.single_data.names, accept.single_data.values))
        result = {
            'orderOpened': {'id': accept_data['주문번호']}
        }
        return result

    def close_order(self, account, oid, size, dataname):
        request_name = 'close_order(%s, %s, %s, %s)' % (account, oid, size, dataname)
        screen_no = ''
        account_no = account
        order_type = 3 if size >= 0 else 4
        code = dataname
        quantity = 0
        price = 0
        quote_type = ''
        original_order_no = oid
        responses = self.OrderCall(
            request_name,
            screen_no,
            account_no,
            order_type,
            code,
            quantity,
            price,
            quote_type,
            original_order_no,
        )
        _msg = next(responses)
        _tr = next(responses)
        _accept = next(responses)
        confirm = next(responses)
        confirm_data = dict(zip(confirm.single_data.names, confirm.single_data.values))
        result = {
            'orderOpened': {'id': confirm_data['주문번호']}
        }
        return result

    def get_today_quotes_by_code(self, codes=None):
        if codes is None:
            codes = self._codes
        df = self.GetStockQuoteInfoAsDataFrame(codes)
        dt = pd.to_datetime(df['일자'].str.cat(df['체결시간']), format='%Y%m%d%H%M%S').dt.tz_localize(self._krx_timezone)
        dt = dt.astype(np.int64) // 10 ** 3
        df = pd.DataFrame({
            'dataname': df['종목코드'],
            'time': dt,
            'open': df['시가'].astype(float).abs(),
            'high': df['고가'].astype(float).abs(),
            'low': df['저가'].astype(float).abs(),
            'close': df['종가'].astype(float).abs(),
            'volume': df['거래량'].astype(float).abs(),
        })
        msgs = {tup.dataname: tup._asdict() for tup in df.itertuples(index=False)}
        return msgs
class KiwoomOpenApiPlusPriceEventChannel:

    _krx_timezone = get_krx_timezone()

    def __init__(self, stub):
        self._stub = stub
        self._fid_list = KiwoomOpenApiPlusRealType.get_fids_by_realtype('주식시세')

        self._request_observer = QueueBasedIterableObserver()
        self._request_iterator = iter(self._request_observer)
        self._response_iterator = self._stub.BidirectionalRealCall(self._request_iterator)
        self._response_subject = Subject()
        self._response_scheduler_max_workers = 8
        self._response_scheduler = ThreadPoolScheduler(self._response_scheduler_max_workers)
        self._buffered_response_iterator = QueueBasedBufferedIterator(self._response_iterator)
        self._response_observable = rx.from_iterable(self._buffered_response_iterator, self._response_scheduler)
        self._response_subscription = self._response_observable.subscribe(self._response_subject)

        self._subjects_by_code = {}

        self.initialize()

    def close(self):
        for _code, (_subject, subscription) in self._subjects_by_code.items():
            subscription.dispose()
        self._response_subscription.dispose()
        self._buffered_response_iterator.stop()
        self._response_iterator.cancel()

    def __del__(self):
        self.close()

    def initialize(self):
        request = KiwoomOpenApiPlusService_pb2.BidirectionalRealRequest()
        request.initialize_request.fid_list.extend(self._fid_list) # pylint: disable=no-member
        self._request_observer.on_next(request)

    def register_code(self, code):
        request = KiwoomOpenApiPlusService_pb2.BidirectionalRealRequest()
        code_list = [code]
        fid_list = KiwoomOpenApiPlusRealType.get_fids_by_realtype('주식시세')
        request.register_request.code_list.extend(code_list) # pylint: disable=no-member
        request.register_request.fid_list.extend(fid_list) # pylint: disable=no-member
        self._request_observer.on_next(request)
        logging.debug('Registering code %s for real events', code)

    def is_for_code(self, response, code):
        return response.arguments[0].string_value == code

    def filter_for_code(self, code):
        return ops.filter(lambda response: self.is_for_code(response, code))

    def is_valid_price_event(self, response):
        return all(name in response.single_data.names for name in ['20', '27', '28'])

    def filter_price_event(self):
        return ops.filter(self.is_valid_price_event)

    def time_to_timestamp(self, fid20):
        dt = datetime.datetime.now(self._krx_timezone).date()
        tm = datetime.datetime.strptime(fid20, '%H%M%S').time()
        dt = datetime.datetime.combine(dt, tm)
        dt = self._krx_timezone.localize(dt)
        return dt.timestamp() * (10 ** 6)

    def event_to_dict(self, response):
        single_data = dict(zip(response.single_data.names, response.single_data.values))
        result = {
            'time': self.time_to_timestamp(single_data['20']),
            'bid': abs(float(single_data['28'])),
            'ask': abs(float(single_data['27'])),
        }
        return result

    def convert_to_dict(self):
        return ops.map(self.event_to_dict)

    def get_observable_for_code(self, code):
        self.register_code(code)
        if code not in self._subjects_by_code:
            subject = Subject()
            subscription = self._response_subject.pipe(
                self.filter_for_code(code),
                self.filter_price_event(),
                self.convert_to_dict(),
            ).subscribe(subject)
            self._subjects_by_code[code] = (subject, subscription)
        subject, subscription = self._subjects_by_code[code]
        return subject