주식차트를 위한 ohlc 데이터 만들기

주식차트는 분,초,시,일 등의 가격 데이터를 O(open), H(high), L(low), C(close)를 가지고 봉 형태로 표시해 줍니다. 이를 candlesticks chart라고 합니다.

그러면, 주식의 거래데이터가 아래와 가티 주어질 때 ohlc를 만듭니다.

### 
실제 상황과 비슷하게 하기 위해서 아래 예제를 조금 바꾸어보았습니다. 조건이 바뀌기 전에 답을 주신 '상파'님의 양해를 먼저 구합니다.
###

예) 1분마다 open, high, low, close를 만듭니다.
분:초, 거래가격
1:02, 1100
1:20, 1170
1:59, 1090
2:30, 1030
2:31, 1110
2:42, 1150
2:55, 1210
2:56, 1190
3:02, 1120
3:09, 1100
4:15, 1090
4:20, 1080
4:55, 1050
4:56, 1020
4:57, 1000

[조건] 실제 주식 거래의 경우,, 분당 거래 회수가 10번, 100번, 1000번 등일 수 있으므로, high, low를 찾을 때 되도록 전체 데이터에서 찾지 않고,, 앞뒤 데이터를 비교하여 찾아주십시오.. 

답: 
open = [1100, 1030, 1120, 1090]
high = [1170, 1210, 1120, 1090]
low = [1090, 1030, 1100, 1000]
close = [1090, 1190, 1100, 1000]
ohlc candlestick finance chart
※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.

10개의 풀이가 있습니다.

요즘 들어 Queue를 자주 쓰네요 샘플 데이터를 만든 후에 차례로 처리했습니다. 처리 부분보다 초기화, 출력 코딩이 대부분이네요

그런데 비교 함수를 쓴다고 메모리 문제가 발생한다는 게 조금 의아합니다
메모리에 올라간 데이터들은 어차피 참조 방식으로 비교할 테고, 몇몇 Sort 알고리즘처럼 추가적인 메모리가 필요하지 않거든요

어쨋든, 500개의 데이타 샘플을 출력해봤습니다. 랜덤으로 초를 증가시키고, 가격도 1000을 기준으로 +- 250을 랜덤으로 주었습니다

struct Data
{
    public DateTime Time;
    public int Price;
}
static void Main()
{
    const int SAMPLE = 500;
    int time = 0, price = 0, minute = -1;
    Random rnd = new Random();

    Queue<Data> dataList = new Queue<Data>();
    List<int> open = new List<int>();
    List<int> high = new List<int>();
    List<int> low = new List<int>();
    List<int> close = new List<int>();
    List<int> count = new List<int>();

    for (int i = 0; i < SAMPLE; i++)
    {
        time += rnd.Next(0, 6);
        price = 1000 + rnd.Next(-250, 250);
        dataList.Enqueue(new Data() { Time = new DateTime().AddSeconds(time), Price = price });
    }

    while (dataList.Count > 0)
    {
        var data = dataList.Dequeue();
        if (minute < data.Time.Minute)
        {
            minute = data.Time.Minute;
            open.Add(data.Price);
            high.Add(data.Price);
            low.Add(data.Price);
            close.Add(data.Price);
            count.Add(1);
        }
        else
        {
            if (high[minute] < data.Price) high[minute] = data.Price;
            if (low[minute] > data.Price) low[minute] = data.Price;
            close[minute] = data.Price;
            count[minute]++;
        }
    }

    Console.Write("{0,15}{1,6}{2,6}{3,6}{4,6}{5,6}", "minute", "open", "high", "low", "close", "nData");
    Console.WriteLine();

    for (int i = 0; i < minute + 1; i++)
    {
        Console.Write("{0,15}", string.Format("{0:00}:00 ~ {1:00}:00", i, i + 1));
        Console.Write("{0,6}", open[i]);
        Console.Write("{0,6}", high[i]);
        Console.Write("{0,6}", low[i]);
        Console.Write("{0,6}", close[i]);
        Console.Write("{0,6}", count[i]);
        Console.WriteLine();
    }
}

결과

         minute  open  high   low close nData
  00:00 ~ 01:00   850  1183   750  1183    23
  01:00 ~ 02:00  1125  1222   798   911    24
  02:00 ~ 03:00   992  1237   768   884    25
  03:00 ~ 04:00   862  1166   777  1161    23
  04:00 ~ 05:00  1137  1216   755   755    28
  05:00 ~ 06:00   961  1238   766   970    31
  06:00 ~ 07:00  1010  1245   763  1147    24
  07:00 ~ 08:00   995  1169   769   819    21
  08:00 ~ 09:00   853  1246   754   774    22
  09:00 ~ 10:00  1228  1232   788   798    26
  10:00 ~ 11:00   791  1222   771   912    23
  11:00 ~ 12:00  1027  1232   774   815    25
  12:00 ~ 13:00   886  1239   758   910    23
  13:00 ~ 14:00   837  1182   756  1095    21
  14:00 ~ 15:00   811  1206   769  1048    26
  15:00 ~ 16:00  1201  1235   752  1112    21
  16:00 ~ 17:00  1188  1236   777   938    19
  17:00 ~ 18:00  1148  1234   767  1078    19
  18:00 ~ 19:00  1025  1247   854  1048    24
  19:00 ~ 20:00  1150  1225   784  1199    23
  20:00 ~ 21:00   854  1246   754   885    27
  21:00 ~ 22:00   932  1147   932  1147     2
계속하려면 아무 키나 누르십시오 . . .
※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.

파이썬 2.7.6입니다.

data='''\
1, 1100
2, 1170
3, 1090
4, 1030
5, 1110

6, 1150
7, 1210
8, 1190
9, 1120
10, 1100

11, 1090
12, 1080
13, 1050
14, 1020
15, 1000'''

d = [int(x.split()[1]) for x in data.split('\n') if x] #if x 는 빈줄때문에 삽입
open=[];high=[];low=[];close=[]
while d:
    i=0
    tmp=[]
    while d and i<5:tmp.append(d.pop(0));i+=1#5개씩 가져와서 tmp에 넣음
    open.append(tmp[0])
    high.append(max(tmp))
    low.append(min(tmp))
    close.append(tmp[-1])

print 'open=',open
print 'high=',high
print 'low=',low
print 'close=',close

※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.

제가 출제하신분의 의도를 알아채지못하고 엉뚱한 코드를 작성하는 바람에
문제를 수정하는 수고로움을 끼쳐드렸네요. 죄송합니다.^^;;
일단 자료를 화일로 저장해서 한줄씩 읽어서 처리했구요,
지정한 4개의 리스트외에는 리스트를 사용하지 않았습니다.
메모리는 최소한도로 사용했습니다.

이번 풀이는 hana11님의 의도에 맞는거죠? ㅎㅎ

open=[];high=[];low=[];close=[]
prev_t=''
for line in file('s498.txt'):
    t,p=line.split(', ');p=int(p)
    if not prev_t or prev_t[:-3]!=t[:-3]:
            open.append(p)
            high.append(p)
            low.append(p)
            close.append(p)
    if p>high[-1] : high[-1] = p
    if p<low[-1] : low[-1] = p
    close[-1]=p
    prev_t = t

print open
print high
print low
print close

문제 출제자가 잘 못낸 것이지요.. <감사> - hana11, 2016/01/27 19:37 M D
※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.

var open : Dictionary = Dictionary() var close : Dictionary = Dictionary() var high : Dictionary = Dictionary() var low : Dictionary = Dictionary()

    let componentSepa = input.componentsSeparatedByString("\n")

    for line in componentSepa {
        let componentHour = line.componentsSeparatedByString(":").first
        let componentValue = line.componentsSeparatedByString(", ").last

        if(componentHour == "") {
            continue
        }

        //open.
        if nil == open[componentHour!] {
            open[componentHour!] = componentValue

            low[componentHour!] = componentValue
            high[componentHour!] = componentValue
        }

        //close
        close[componentHour!] = componentValue

        //low
        if low[componentHour!] > componentValue {
            low[componentHour!] = componentValue
        }

        //high
        if high[componentHour!] < componentValue {
            high[componentHour!] = componentValue
        }
    }


    let sortOpen = open.sort { (obj1, obj2) -> Bool in
        return obj1.0 < obj2.0
    }

    let sortClose = close.sort { (obj1, obj2) -> Bool in
        return obj1.0 < obj2.0
    }

    let sortHigh = high.sort { (obj1, obj2) -> Bool in
        return obj1.0 < obj2.0
    }

    let sortLow = low.sort { (obj1, obj2) -> Bool in
        return obj1.0 < obj2.0
    }

    //output
    for seq in sortOpen.enumerate() {
        print("open hour(\(seq.element.0)) : \(seq.element.1)")
    }
    for seq in sortClose.enumerate() {
        print("close hour(\(seq.element.0)) : \(seq.element.1)")
    }
    for seq in sortHigh.enumerate() {
        print("high hour(\(seq.element.0)) : \(seq.element.1)")
    }
    for seq in sortLow.enumerate() {
        print("low hour(\(seq.element.0)) : \(seq.element.1)")
    }

2016/02/27 22:24

※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.

Ruby

거래값 1개를 받으면 {분=>[o,l,h,c]} 해쉬를 생성. 분에 해당하는 해쉬가 있으면 lhc값 업데이트. 가장 윗줄 enq[해쉬,분,가격]이 로직. 나머지는 입출력.

enq = ->q,m,v { q[m]? q[m][1,3]=[*[*q[m][1,2],v].minmax,v] : q[m]=[*[v]*4]; q }
olhc = ->s { s.reduce({}) {|q,e| min,_,price=e.scan(/\w+/); enq[q,min,price.to_i] } }
agg_olhc = ->stocks { %w(open low high close).zip(olhc[stocks].values.transpose) }
out_olhc = ->str { agg_olhc[str].each {|tag,prices| puts "#{tag} = #{prices}" } }

Test

stocks_str = ["1:02, 1100", "1:20, 1170", "1:59, 1090", "2:30, 1030", "2:31, 1110", "2:42, 1150", "2:55, 1210", "2:56, 1190", "3:02, 1120", "3:09, 1100", "4:15, 1090", "4:20, 1080", "4:55, 1050", "4:56, 1020", "4:57, 1000"]
tagged_olhc = [["open", [1100, 1030, 1120, 1090]], ["low", [1090, 1030, 1100, 1000]], ["high", [1170, 1210, 1120, 1090]], ["close", [1090, 1190, 1100, 1000]]]
expect(agg_olhc[stocks_str]).to eq tagged_olhc

Output

#=> out_olhc[stocks_str]
open = [1100, 1030, 1120, 1090]
low = [1090, 1030, 1100, 1000]
high = [1170, 1210, 1120, 1090]
close = [1090, 1190, 1100, 1000]
※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.

open=[];high=[];low=[];close=[] prev_t='' for line in file('s498.txt'): t,p=line.split(', ');p=int(p) if not prev_t or prev_t[:-3]!=t[:-3]: open.append(p) high.append(p) low.append(p) close.append(p) if p>high[-1] : high[-1] = p if p<low[-1] : low[-1] = p close[-1]=p prev_t = t

print open print high print low print close

※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.

입력을 파싱하는 부분,

파싱된 정보를 각 분 단위로 비교/업데이트하는 부분

분단위로 생성된 결과를 수집하는 부분

최종 결과를 출력하는 부분을 각각 코루틴으로 만들어서

쓸데없이 길게 구현해보았습니다.

파이썬 3.5 입니다.

import re


def coroutine(fn):
    def inner(*a, **b):
        r = fn(*a, **b)
        next(r)
        return r
    return inner


@coroutine
def printer():
    while True:
        data = (yield)
        if data:
            for k, v in data.items():
                print("{} = {}".format(k, v[1:]))


@coroutine
def acc(target=None):
    data = dict(open=[], close=[], high=[], low=[])
    while True:
        input_ = (yield)
        if not input_:
            break
        vopen, vclose, vhigh, vlow = input_
        data['open'].append(vopen)
        data['close'].append(vclose)
        data['high'].append(vhigh)
        data['low'].append(vlow)
    if target:
        target.send(data)


@coroutine
def updater(target=None):
    current_min = -1
    vhigh, vlow, vopen, vclose = 0, 0, 0, 0
    continue_ = True
    while continue_:
        input_ = (yield)
        if input_ is None:
            continue_ = False
        else:
            minute, _, value = input_
            print(minute, current_min, continue_)
        if continue_ is False or minute != current_min:
            if target:
                target.send((vopen, vclose, vhigh, vlow))
            vopen, vclose, vhigh, vlow = value, value, value, value
            current_min = minute
        else:
            vhigh = value if value > vhigh else vhigh
            vlow = value if value < vlow else vlow
            vclose = value
    if target:
        target.send(None)


@coroutine
def parser(target=None):
    pat = re.compile(r'(\d{1,2}):(\d{1,2}), (\d+)')
    while True:
        data = (yield)
        m = pat.match(data)
        result = [int(x) for x in m.groups()] if m is not None else None
        if target:
            target.send(result)
        if not result:
            break


def main():
    process = parser(updater(acc(printer())))
    try:
        while True:
            line = input()
            process.send(line)
            if not line:
                break
    except:
        pass

main()
※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.
data=[
'1:02, 1100',
'1:20, 1170',
'1:59, 1090',
'2:30, 1030',
'2:31, 1110',
'2:42, 1150',
'2:55, 1210',
'2:56, 1190',
'3:02, 1120',
'3:09, 1100',
'4:15, 1090',
'4:20, 1080',
'4:55, 1050',
'4:56, 1020',
'4:57, 1000',
]
o=[]
h=[]
l=[]
c=[]
wm=-1;
for d in data:
    dl = d.split(',')
    v = int(dl[1].strip())
    m = int(dl[0].split(":")[0].strip())
    if wm != m:
        o.append(v)
        h.append(v)
        l.append(v)
        c.append(v)
        wm = m
    if h[-1] < v:
        h[-1] = v
    if l[-1] > v:
        l[-1] = v
    c[-1] = v
print("open = {}".format(o))
print("higt = {}".format(h))
print("low = {}".format(l))
print("close = {}".format(c))

Python 3.5.2에서 작성하였습니다.

※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.
tmp = '''1:02, 1100
1:20, 1170
1:59, 1090
2:30, 1030
2:31, 1110
2:42, 1150
2:55, 1210
2:56, 1190
3:02, 1120
3:09, 1100
4:15, 1090
4:20, 1080
4:55, 1050
4:56, 1020
4:57, 1000'''.split('\n')
o,h,l,c = list(),list(),list(),list()
high_1, low_1, step = 0,0,0
value = [x.split(', ')[1] for x in tmp]
m = [x.split(':')[0] for x in tmp]
for x in range(len(m)):
    if step != int(m[x]):
        step = int(m[x])
        high_1 = value[x]
        low_1 = value[x]
        o.append(value[x])
    elif high_1 < value[x]:
        high_1 = value[x]
    elif low_1 >= value[x]:
        low_1 = value[x]
    if len(m)-1 == x or step != int(m[x+1]):
        h.append(high_1)
        l.append(low_1)
        c.append(value[x])
print('open =',o)
print('high =',h)
print('low =',l)
print('close =',c)

#### 2017.01.24 D-394 ####

앞뒤 비교해서 풀으라는게 무슨말인지 이해못해서 그냥 풀었습니다 허허헣...

※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.

분, 초, 가격의 데이터 array와 각 분마다 어디까지 위치하는지 알려주는 int[] index를 만들어 open,close는 맨 앞과 뒤, high와low는 첫번째부터 시작해서 다음것을 비교해가며 구하도록 만들었습니다.

package sss;

import java.util.ArrayList;

public class Candlestick {
    class Data{
        int minute, second, price;
        public Data(int minute, int second, int price){
            this.minute=minute;
            this.second=second;
            this.price=price;
        }
    }
    ArrayList<Data> data = new ArrayList<Data>();
    int index[];
    public Candlestick(){
        data.add(new Data(1,2,1100));data.add(new Data(1,20,1170));data.add(new Data(1,59,1090));
        data.add(new Data(2,30,1030));data.add(new Data(2,31,1110));data.add(new Data(2,42,1150));
        data.add(new Data(2,55,1210));data.add(new Data(2,56,1190));data.add(new Data(3,2,1120));
        data.add(new Data(3,9,1100));data.add(new Data(4,15,1090));data.add(new Data(4,20,1080));
        data.add(new Data(4,55,1050));data.add(new Data(4,56,1020));data.add(new Data(4,57,1000));
    }

    void set_index(){
        index = new int[data.get(data.size()-1).minute];
        for(int i=0;i<data.size();i++)
            index[data.get(i).minute-1]++;
        for(int i=0;i<index.length-1;i++)
            index[i+1]+=index[i];
    }
    void ohlc_open(){
        int open=0;

        System.out.print("[");
        for(int i=0,open_index=0;i<index.length;open_index=index[i++]){
            open = data.get(open_index).price;
            System.out.print(open + " ");
        }
        System.out.print("]");
    }
    void ohlc_high(){
        int high=0;
        System.out.print("[");
        for(int i = 0, first=0,last=0; i < index.length;i++){
            first=last; last=index[i];
            while(first < last){
                if(high < data.get(first).price)
                    high = data.get(first).price;
                first
                ++;
            }
            System.out.print(high + " ");
            high = 0;
        }
        System.out.print("]");
    }
    void ohlc_low(){
        int low=data.get(0).price;
        System.out.print("[");
        for(int i = 0, first=0,last=0; i < index.length;i++){
            first=last; last=index[i];
            while(first < last){
                if(low > data.get(first).price)
                    low = data.get(first).price;
                first++;
            }
            System.out.print(low + " ");
            low=999999999;
        }
        System.out.print("]");
    }
    void ohlc_close(){
        int close=0;
        System.out.print("[");
        for(int i=0,close_index=0; i<index.length; i++){
            close_index = index[i]-1;
            close = data.get(close_index).price;
            System.out.print(close+" ");
        }
        System.out.print("]");
    }
}

※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.

풀이 작성

※ 풀이작성 안내
  • 본문에 코드를 삽입할 경우 에디터 우측 상단의 "코드삽입" 버튼을 이용 해 주세요.
  • 마크다운 문법으로 본문을 작성 해 주세요.
  • 풀이를 읽는 사람들을 위하여 풀이에 대한 설명도 부탁드려요. (아이디어나 사용한 알고리즘 또는 참고한 자료등)
  • 작성한 풀이는 다른 사람(빨간띠 이상)에 의해서 내용이 개선될 수 있습니다.
목록으로
코딩도장

코딩도장은 프로그래밍 문제풀이를 통해서 코딩 실력을 수련(Practice)하는 곳입니다.

ohlc x 1
candlestick x 1
finance chart x 1

언어별 풀이 현황
전 체 x 10
cs x 1
python x 4
기 타 x 3
ruby x 1
java x 1