하아찡

WPF - 호가창 본문

C#/코인프로그램 - 진행상황

WPF - 호가창

하아찡 2022. 8. 14. 02:39

업비트 실시간 호가를 받아오기위해 홈페이지 참조.

https://docs.upbit.com/docs/upbit-quotation-websocket

 

업비트 개발자 센터

업비트 Open API 사용을 위한 개발 문서를 제공 합니다.업비트 Open API 사용하여 다양한 앱과 프로그램을 제작해보세요.

docs.upbit.com

 

시세 정보란에 REST API 수신방식과 WebSocket 수신방식이 있는데 나는 WebSocket 방식을 채용함.

 

참조설정 참고사이트

https://icechou.tistory.com/258

 

현재 진행한 내용

호가창(현재 1~10호가 까지만 받아오게 설정해둠)

매수 / 매도 활성화버튼 -> 추후 해당 버튼으로 매수 / 매도 할 때 구분해주게함.

//해당버튼 마음에 들어서 나머지 버튼들도 저런 디자인으로 작업할예정

 

코드 작업은 

웹소켓 클래스 하나 작업중 추후 차트 실시간 데이터 추가 함수도 추가 할 예정

매수 매도 클래스 작업예정

 

호가창 실시간 작업

 

호가창 XAML(작업중)

<Grid Margin="5" >
        <Grid.RowDefinitions>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="240"/>
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>
        
        
        
        <Grid Grid.Row="2" x:Name="BidAsk" Grid.ColumnSpan="3">
            <Grid.RowDefinitions>
                <RowDefinition Height="32"/>
                <RowDefinition Height="30"/>
                <RowDefinition Height="30"/>
                <RowDefinition Height="30"/>
                <RowDefinition Height="30"/>
                <RowDefinition Height="1*"/>
            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="1*"/>
            </Grid.ColumnDefinitions>

            <StackPanel Grid.Column="0" Grid.Row="0" >
                <Button x:Name="BtnBid" 
                    Click="BtnBid_Click" MouseEnter="BtnBidAsk_MouseEnter" MouseLeave="BtnBidAsk_MouseLeave"
                    Content="매수" Margin="0" Height="30"
                    BorderThickness="0" >
                    <Button.Style>
                        <Style TargetType="{x:Type Button}">
                            <Setter Property="Background" Value="Transparent"/>
                        </Style>
                    </Button.Style>
                </Button>
                <WrapPanel x:Name="BidBottomBar" Height="2"/>
            </StackPanel>

            <StackPanel Grid.Column="1" Grid.Row="0" >
                <Button x:Name="BtnAsk" 
                    Click="BtnAsk_Click" MouseEnter="BtnBidAsk_MouseEnter" MouseLeave="BtnBidAsk_MouseLeave"
                    Content="매도" Margin="0"
                    Grid.Column="1" Grid.Row="0" Height="30"
                    BorderThickness="0" Background="Transparent" >
                </Button>
                <WrapPanel x:Name="AskBottomBar" Height="2"/>
            </StackPanel>
            


            <!-- 매수 매도 수량 입력-->
            <WrapPanel Grid.Row="3" Grid.ColumnSpan="2" HorizontalAlignment="Right">
                <Label Content="수량" FontSize="16" Margin="0,0,18,0"/>
                <TextBox x:Name="TxtQuantity" Text="TextBox" TextWrapping="Wrap" Margin="5,2.5,0,2.5" Height="25" FontSize="16"  Width="80" TextAlignment="Right"/>
                <ComboBox x:Name="CbRatio" Margin="5,2.5,0,2.5" Height="25" FontSize="16" Width="40">
                    <ComboBoxItem Content="최대"/>
                    <ComboBoxItem Content="75%"/>
                    <ComboBoxItem Content="50%"/>
                    <ComboBoxItem Content="25%"/>
                    <ComboBoxItem Content="10%"/>
                </ComboBox>
            </WrapPanel>

            <WrapPanel Grid.Row="4" Grid.ColumnSpan="2" HorizontalAlignment="Right">
                <Label Content="가격" FontSize="16" Margin="0,0,18,0"/>
                <TextBox x:Name="TxtPrice" Text="TextBox" TextWrapping="Wrap" Margin="5,2.5,0,2.5" Height="25" FontSize="16"  Width="80" TextAlignment="Right"/>
                <Button Margin="5,2.5,2.5,2.5" Width="18" FontSize="16" Content="+" BorderThickness="0"/>
                <Button Margin="2.5,2.5,0,2.5" Width="17" FontSize="16" Content="-" BorderThickness="0"/>
            </WrapPanel>
        </Grid>


        <ListView x:Name="LvAsk" Grid.Row="0" Grid.ColumnSpan="2" MinHeight="200" VerticalAlignment="Bottom"
                  ScrollViewer.VerticalScrollBarVisibility="Disabled"
                  ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                  BorderThickness="0"
                  Background="#ecf3fa">
            <ListView.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel VerticalAlignment="Bottom"/>
                </ItemsPanelTemplate>
            </ListView.ItemsPanel>
            <ListView.Resources>
                <!--상단 헤더 안보이게-->
                <Style TargetType="GridViewColumnHeader">
                    <Setter Property="Visibility" Value="Collapsed"/>
                </Style>

                <Style TargetType="ListViewItem">
                    <Setter Property="HorizontalContentAlignment" Value="Center"/>
                </Style>
            </ListView.Resources>
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Volume" DisplayMemberBinding="{Binding Volume}" Width="96" />
                    <GridViewColumn Header="Price" DisplayMemberBinding="{Binding Price}" Width="100"/>
                </GridView>
            </ListView.View>
        </ListView>

        <ListView x:Name="LvBid" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" MinHeight="200" VerticalAlignment="Top" 
                  ScrollViewer.VerticalScrollBarVisibility="Disabled"
                  ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                  BorderThickness="0"
                  Background="#fbf1ef">
            <ListView.Resources>
                <!--상단 헤더 안보이게-->
                <Style TargetType="GridViewColumnHeader">
                    <Setter Property="Visibility" Value="Collapsed"/>
                </Style>

                <Style TargetType="ListViewItem">
                    <Setter Property="HorizontalContentAlignment" Value="Center"/>
                </Style>
            </ListView.Resources>
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Price" DisplayMemberBinding="{Binding Price}" Width="100"  />
                    <GridViewColumn Header="Volume" DisplayMemberBinding="{Binding Volume}" Width="100" />
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>

 

호가창 코드(매수 매도부분 작업X)

string Coin = "";
        WebSoketPriceInfo wspi;

        //현재 매수상태인지 매도상태인지
        bool bBidMode = true;//매수
        bool bAskMode = false;//매도


        private static readonly DependencyProperty bidTxtClr = DependencyProperty.Register("BidTxtColor", typeof(Brush), typeof(BidAskView));
        private static readonly DependencyProperty askTxtClr = DependencyProperty.Register("AskTxtColor", typeof(Brush), typeof(BidAskView));
        private static readonly DependencyProperty defaultTxtClr = DependencyProperty.Register("DefaultTxtColor", typeof(Brush), typeof(BidAskView));
        private static readonly DependencyProperty defaultgBackClr = DependencyProperty.Register("DefaultBackColor", typeof(Brush), typeof(BidAskView));
        public Brush BidTxtClr
        {
            get { return (Brush)this.GetValue(bidTxtClr); }
            set { this.SetValue(bidTxtClr, value); }
        }
        public Brush AskTxtClr
        {
            get { return (Brush)this.GetValue(askTxtClr); }
            set { this.SetValue(askTxtClr, value); }
        }
        public Brush DefaultTxtClr
        {
            get { return (Brush)this.GetValue(defaultTxtClr); }
            set { this.SetValue(defaultTxtClr, value); }
        }
        public Brush DefaultBackClr
        {
            get { return (Brush)this.GetValue(defaultgBackClr); }
            set { this.SetValue(defaultgBackClr, value); }
        }


        public BidAskView()
        {
            InitializeComponent();
        }

        public void SetCoin(string coin)
        {
            //코인데이터를 읽어옴
            Coin = coin;
            if (wspi == null)
                wspi = new WebSoketPriceInfo(coin: Coin, lvbid: LvBid, lvask: LvAsk);
            else
                wspi.SetChangeCoin(Coin);

        }
        private void BtnAsk_Click(object sender, RoutedEventArgs e)
        {
            //매도로 활성화
            bAskMode = !bAskMode;
            bBidMode = !bBidMode;

            //매수버튼 글자색 기본으로 변경
            BtnBid.Foreground = DefaultTxtClr;
            BidBottomBar.Background = DefaultBackClr;
        }

        private void BtnBid_Click(object sender, RoutedEventArgs e)
        {
            //매수로 활성화
            bAskMode = !bAskMode;
            bBidMode = !bBidMode;

            //매도색 글자색 기본으로 변경
            BtnAsk.Foreground = DefaultTxtClr;
            AskBottomBar.Background = DefaultBackClr;
        }

        private void BtnBidAsk_MouseEnter(object sender, MouseEventArgs e)
        {
            if((string)(((Button)sender).Content) == "매수")
            {
                ((Button)sender).Foreground = BidTxtClr;
                BidBottomBar.Background = BidTxtClr;
            }
            else
            {
                ((Button)sender).Foreground = AskTxtClr;
                AskBottomBar.Background = AskTxtClr;
            }
        }

        private void BtnBidAsk_MouseLeave(object sender, MouseEventArgs e)
        {
            if ((string)(((Button)sender).Content) == "매수")
            {
                if (!bBidMode)
                {
                    ((Button)sender).Foreground = DefaultTxtClr;
                    BidBottomBar.Background = DefaultBackClr;
                }
            }
            else
            {
                if (!bAskMode)
                {
                    ((Button)sender).Foreground = DefaultTxtClr;
                    AskBottomBar.Background = DefaultBackClr;
                }
            }
            
        }

 

 웹소켓 클래스(추후 차트 실시간 함수도 추가해야함)

WebSocket ws;
        string Coin = "";
        string url = "wss://api.upbit.com/websocket/v1";
        object bid;
        object ask;
        bool Connect = false;
        public WebSoketPriceInfo(string coin="",object lvbid = null, object lvask = null)
        {
            //코인정보를 넘겨받음
            Coin = coin;
            this.bid = lvbid;
            this.ask = lvask;
            
            ws = new WebSocket(url);
            ws.OnOpen += ws_open;
            ws.OnClose += ws_close;
            ws.OnMessage += ws_message;
            ws.Connect();
        }
        public void SetChangeCoin( string coin)
        {
            //다른 코인을 불러올때 사용.
            //미리 생성했던 소켓을 종료하지않고 다시 생성해서
            //이전 데이터랑 같이 불러와지는 문제가 생겨서 추가함
            ws.Close();

            Coin = coin;
            ws = new WebSocket(url);
            ws.OnOpen += ws_open;
            ws.OnClose += ws_close;
            ws.OnMessage += ws_message;
            ws.Connect();
        }
        private void ws_connect()
        {
            
        }
        private void ws_open(object sender, EventArgs e)
        {
            JArray array = new JArray();
            //실시간 데이터 받아올 코인
            //형태 -> (코인종류).(받아올호가) 만약 .5면 1~5호가를 가져옴
            array.Add(Coin + ".10");

            JObject obj1 = new JObject();
            obj1["ticket"] = Guid.NewGuid().ToString();//UUID
            JObject obj2 = new JObject();
            obj2["type"] = "trade";
            obj2["codes"] = array;
            JObject obj3 = new JObject();
            obj3["type"] = "orderbook";
            obj3["codes"] = array;

            //format을 SIMPLE로 받을경우 축약형으로 들어옴.
            JObject obj4 = new JObject();
            obj4["format"] = "SIMPLE";
            obj4["codes"] = array;
            ws.Send(string.Format("[{0},{1},{2},{3}]", obj1.ToString(), obj2.ToString(), obj3.ToString(), obj4.ToString()));
            //tradeserver.Enabled = true;
        }

        private void ws_close(object sender, EventArgs e)
        { 
            Connect = false;
            //MessageBox.Show("WebSocket Close");
        }

        private void ws_message(object sender, MessageEventArgs e)
        {
            Connect = true;
            JObject jsonArray = JObject.Parse(Encoding.UTF8.GetString(e.RawData));
            List<Structs.PriceItem> Bid = new List<Structs.PriceItem>();
            List<Structs.PriceItem> Ask = new List<Structs.PriceItem>();
            foreach (var items in jsonArray.Children())
            {
                //호가 데이터 영역
                if (items.Path == @"obu")
                {
                    JArray jsonArray_child = JArray.Parse(items.First.ToString());
                    
                    foreach (var item in jsonArray_child.Children())
                    {
                        //가격 / 잔량
                        Ask.Add(new Structs.PriceItem { Price = Convert.ToDouble(item["ap"].ToString()), Volume = Math.Round(Convert.ToDouble(item["as"].ToString()),3) });
                        Bid.Add(new Structs.PriceItem { Price = Convert.ToDouble(item["bp"].ToString()), Volume = Math.Round(Convert.ToDouble(item["bs"].ToString()),3) });
                    }
                }


            }
            GetPriceVolumeData(Bid,Ask);
        }
        List<Structs.PriceItem> Biditems = new List<Structs.PriceItem>();
        List<Structs.PriceItem> Askitems = new List<Structs.PriceItem>();
        private void GetPriceVolumeData(List<Structs.PriceItem> Biditems, List<Structs.PriceItem> AskItems)
        {
            //소켓데이터에서 받아와서 데이터를 저장시킴
            //매도 잔량은 역순으로 출력해야 정방향
            AskItems.Reverse();
            this.Biditems = Biditems;
            this.Askitems = AskItems;

            if(ask != null && bid != null)
            {
                AddAsk(Askitems);
                AddBid(Biditems);
            }
        }

        //외부 컨트롤에 데이터를 넣기위해서 델리게이트 사용
        delegate void Dgt_AddItems(List<Structs.PriceItem> items);
        private void AddAsk(List<Structs.PriceItem> items)
        {
            if (((ListView)ask).Dispatcher.Thread == Thread.CurrentThread)
            {
                ((ListView)ask).ItemsSource = Askitems;
                
            }
            else
            {
                Dgt_AddItems t = AddAsk;

                object[] objs = new object[] { items };
                ((ListView)ask).Dispatcher.BeginInvoke(t, objs);
            }
        }

        private void AddBid(List<Structs.PriceItem> items)
        {
            if (((ListView)ask).Dispatcher.Thread == Thread.CurrentThread)
            {
                ((ListView)bid).ItemsSource = Biditems;
            }
            else
            {
                Dgt_AddItems t = AddBid;

                object[] objs = new object[] { items };
                ((ListView)bid).Dispatcher.BeginInvoke(t, objs);
                
            }
        }


        public object GetAskItems()
        {
            //매도 호가창 데이터 필요할까봐 만들어둠.
            if (Connect)
                return Askitems;
            else
                return null;
        }
        public object GetBidItems()
        {
            //매수 호가창 데이터 필요할까봐 만들어둠.
            if (Connect)
                return Biditems;
            else
                return null;
        }

 

남은작업

 - 매수 매도 클래스 생성.

 - 실시간 차트 함수 작업.

반응형

'C# > 코인프로그램 - 진행상황' 카테고리의 다른 글

WPF - 호가창 및 차트 수정사항 완료  (1) 2022.08.20
WPF - 호가창 및 차트  (0) 2022.08.16
WPF - 업비트 중간 진행사항  (0) 2022.08.05
WPF - 차트(4)  (0) 2022.08.05
WPF - 차트(3)  (0) 2022.08.04