하아찡
[C#/WPF] Upbit 프로젝트 Chart - 2(전체코드) 본문
통으로 코드 한번 올리고 분할해서 정리하겠습니다.
코드 자체를 통체로 올리닌깐 너무 길어가지고 정리는 다음글에서 할게용.
완전히 완성된 코드는 아니여서 수정될수있습니다.
각종 이동평균선이나 그런것도 추가해야해서...
그리고 아직 차트 움직일때 연산량 많은것도 좀 수정해야해서...
ChartViewModel.cs
using Language;
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using PublicColor;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using Upbit.Event;
using Upbit.Functions;
using Upbit.Functions.Interface;
using Upbit.UpbitFunctions;
using static Upbit.Structs;
namespace Upbit.ViewModels
{
public struct line
{
public double FromX { get; set; }
public double FromY { get; set; }
public double ToX { get; set; }
public double ToY { get; set; }
public Brush LineColor { get; set; }
public double LineThickness { get; set; }
}
public struct CandlePosition
{
public int X { get; set; }
public int Y { get; set; }
}
public class ChartViewModel : BindableBase, IWebSocketTicker, ILanguage, IColors
{
#region Lang
private Language.Lang.PublicCoin lang;
public Language.Lang.PublicCoin Lang
{
get { if (lang is null) lang = new Language.Lang.PublicCoin(); return lang; }
set { SetProperty(ref lang, value); }
}
public virtual void SetLanguage()
{
Lang.UpdateLang();
}
#endregion Lang
#region Models
private List<line> horline;
public List<line> HorLine
{
get { return horline; }
set { SetProperty(ref horline, value); }
}
private List<line> verline;
public List<line> VerLine
{
get { return verline; }
set { SetProperty(ref verline, value); }
}
private List<Structs.Candle> tickdata;
public List<Structs.Candle> TickData
{
get { return tickdata; }
set { SetProperty(ref tickdata, value); }
}
private ObservableCollection<Structs.Tick> printtick;
public ObservableCollection<Structs.Tick> PrintTick
{
get { return printtick; }
set { SetProperty(ref printtick, value); }
}
private double tickwidth;
public double TickWidth
{
get { return tickwidth; }
set { SetProperty(ref tickwidth, value); }
}
//차트 캔들 비율값
private double charttickheight;
public double ChartTickHeight
{
get { return charttickheight; }
set { SetProperty(ref charttickheight, value); }
}
private int gridvolumeheight = 100;
public int GridVolumeHeight
{
get { return gridvolumeheight; }
set { SetProperty(ref gridvolumeheight, value); }
}
private double volumetickheight;
public double VolumeTickHeight
{
get { return volumetickheight; }
set { SetProperty(ref volumetickheight, value); }
}
private Thickness tickcanvasmargin;
public Thickness TickCanvasMargin
{
get { return tickcanvasmargin; }
set { SetProperty(ref tickcanvasmargin, value); }
}
private Thickness tickmargin;
public Thickness TickMargin
{
get { return tickmargin; }
set { SetProperty(ref tickmargin, value); }
}
//차트 최대 최소금액
private double minprice;
public double MinPrice
{
get { return minprice; }
set { SetProperty(ref minprice, value); }
}
private double maxprice;
public double MaxPrice
{
get { return maxprice; }
set { SetProperty(ref maxprice, value); }
}
private string Market = "";
private int lines = 30; //그리드에 그려지는 라인개수
private int KeepPrintCursor = 0;
private int PrintCandle = 200;
private const int PrintDefaultCandle = 200;
private const int PrintMaxCandle = 360; //출력 최대 캔들 수
private const int PrintMinCandle = 50; //출력 최소 캔들 수
private const double CandleMargin = 1.5;
private double GridHeight;
private int printcursor;
public int PrintCursor
{
get { return printcursor; }
set { SetProperty(ref printcursor, value); }
}
private double testnum = 0;
public double TestNum
{
get
{
return testnum;
}
set
{
SetProperty(ref testnum, value);
}
}
//고가
private string printhp;
public string PrintHp
{
get { return printhp; }
set { SetProperty(ref printhp, value); }
}
//저가
private string printlp;
public string PrintLp
{
get { return printlp; }
set { SetProperty(ref printlp, value); }
}
//시가
private string printop;
public string PrintOp
{
get { return printop; }
set { SetProperty(ref printop, value); }
}
//종가
private string printtp;
public string PrintTp
{
get { return printtp; }
set { SetProperty(ref printtp, value); }
}
private string printtime;
public string PrintTime
{
get { return printtime; }
set { SetProperty(ref printtime, value); }
}
private double maxvolume;
public double MaxVolume
{
get { return maxvolume * 1.1; }//10퍼 늘려서 출력
set { SetProperty(ref maxvolume, value); }
}
private const int GapYAxis = 22;//Y축 마진 22계산
const int LeaveCnt = 5; //캔들 최소 출력치
string FLen = "F8"; //소수점 출력
//사이드 커서 출력
private ObservableCollection<Structs.ChartSideCursor> printchartyaxis;
public ObservableCollection<Structs.ChartSideCursor> PrintChartYAxis
{
get { return printchartyaxis; }
set { SetProperty(ref printchartyaxis, value); }
}
private ObservableCollection<Structs.ChartSideCursor> printchartxaxis;
public ObservableCollection<Structs.ChartSideCursor> PrintChartXAxis
{
get { return printchartxaxis; }
set { SetProperty(ref printchartxaxis, value); }
}
//분 / 일 / 주 / 월 타입선택
private int PreMinNumber = 1;
private Visibility visiblecombboxmin;
public Visibility VisibleComboBoxMin
{
get { return visiblecombboxmin; }
set { SetProperty(ref visiblecombboxmin, value); }
}
private string ticktype;
public string TickType
{
get { return ticktype; }
set { SetProperty(ref ticktype, value); }
}
private List<string> listticktype;
public List<string> ListTickType
{
get { return listticktype; }
set { SetProperty(ref listticktype, value); }
}
private List<int> listtickmin;
public List<int> ListTickMin
{
get { return listtickmin; }
set { SetProperty(ref listtickmin, value); }
}
//분틱으로 검색시 틱시간
private int minticktime;
public int MinTickTime
{
get { return minticktime; }
set { SetProperty(ref minticktime, value); }
}
private PublicFunctions pfnc = new PublicFunctions();
#endregion Models
#region Prism
private IEventAggregator _ea;
#endregion Prism
#region Colors
/// <summary>
/// 색상 업데이트 이벤트
/// </summary>
public void EventUpdateColor()
{
MyColors.ColorText = PublicColor.Colors.Colorinstance.ColorText;
MyColors.ColorBack = PublicColor.Colors.Colorinstance.ColorBack;
MyColors.ColorDefaultBack = PublicColor.Colors.Colorinstance.ColorDefaultBack;
MyColors.ColorDefault = PublicColor.Colors.Colorinstance.ColorDefault;
MyColors.ColorBid = PublicColor.Colors.Colorinstance.ColorBid;
MyColors.ColorAsk = PublicColor.Colors.Colorinstance.ColorAsk;
//차트 색상변경
ChangeChartColor();
}
private PublicColor.Colors mycolors;
public PublicColor.Colors MyColors
{
get
{
if (mycolors is null) mycolors = new PublicColor.Colors();
return mycolors;
}
set { SetProperty(ref mycolors, value); }
}
/// <summary>
/// 색상변경 이벤트가 발생했을경우 캔들 색상을 변경시켜줌.
/// 23.11.14
/// </summary>
private void ChangeChartColor()
{
if (PrintTick is not null)
{
for (int i = 0; i < PrintTick.Count; i++)
{
Structs.Tick _t = PrintTick[i];
_t.Colors = SetCandleColor(_t.OP, _t.TP);
PrintTick[i] = _t;
}
}
}
#endregion Colors
#region Event
/// <summary>
/// 캔버스에 그리드를 그리는 이벤트 Load때와 SizeChange때 발생함.
/// 추후 시간에 따른 라인으로 변경할예정
/// 23.11.11
/// </summary>
private DelegateCommand<object> commandgridlines;
public DelegateCommand<object> CommandGridLines =>
commandgridlines ?? (commandgridlines = new DelegateCommand<object>(ExecuteCommandGridLines));
void ExecuteCommandGridLines(object sender)
{
if (sender is not Grid)
return;
Grid grid = (Grid)sender;
MyGrid = (Grid)sender;
GridHeight = grid.ActualHeight;
TickWidth = Math.Max(grid.ActualWidth / PrintCandle, 1.6) - CandleMargin;
ChartTickHeight = GridHeight / (MaxPrice - MinPrice);
VolumeTickHeight = GridVolumeHeight / MaxVolume;
double height = (grid.ActualHeight / lines);
double width = (grid.ActualWidth / lines);
HorLine = DrawLine(height, true);
VerLine = DrawLine(width, false);
if (PrintChartYAxis is not null)
{
PrintChartYAxis[1].Y = GridHeight + GapYAxis;
}
GetCandleTime();
GetCoinPrice();
}
private bool CandleMouseState = false; //현재는 클릭시 작동 추후 Left Right나눠야함 23.11.11
private double FirstXPos = 0; //눌러졌을때 X축값을 받아와서 Move쪽에서 사용
private Grid MyGrid = null; //캔버스로 받아오니 이벤트가 잘작동안돼서 그냥 그리드로 받아옴.
/// <summary>
/// 마우스 클릭 변수를 비활성화시킴
/// 추후 Left Right클릭 나눌예정
/// 23.11.11
/// </summary>
private DelegateCommand<object> commandmouseup;
public DelegateCommand<object> CommandMouseUp =>
commandmouseup ?? (commandmouseup = new DelegateCommand<object>(ExecuteCommandMouseUp));
void ExecuteCommandMouseUp(object obj)
{
if (MyGrid is null)
return;
CandleMouseState = false;
}
/// <summary>
/// 캔들 움직일때 사용하기위한 첫 클릭지점 저장
/// 추후 Left Right클릭 나눌예정
/// 23.11.11
/// </summary>
private DelegateCommand<MouseEventArgs> commandmousedown;
public DelegateCommand<MouseEventArgs> CommandMouseDown =>
commandmousedown ?? (commandmousedown = new DelegateCommand<MouseEventArgs>(ExecuteCommandMouseDown));
void ExecuteCommandMouseDown(MouseEventArgs e)
{
if (MyGrid is null)
return;
CandleMouseState = true;
FirstXPos = e.GetPosition(MyGrid).X;
}
/// <summary>
/// 그리드에서 마우스가 움직였을때 차트를 갱신시켜주기위한 이벤트
/// 23.11.14
/// </summary>
private DelegateCommand<MouseEventArgs> commandmousemove;
public DelegateCommand<MouseEventArgs> CommandMouseMove =>
commandmousemove ?? (commandmousemove = new DelegateCommand<MouseEventArgs>(ExecuteCommandMouseMove));
double move;
void ExecuteCommandMouseMove(MouseEventArgs e)
{
if (MyGrid != null)
{
move = e.GetPosition(MyGrid).X;
if (Market != "")
{
GetCandleInfo(move);
double price = MaxPrice - ((MaxPrice - MinPrice) * (e.GetPosition(MyGrid).Y / MyGrid.ActualHeight));
PrintChartYAxis[2].CursorValue = price.ToString(FLen);
PrintChartYAxis[2].Y = e.GetPosition(MyGrid).Y + GapYAxis;
if (CandleMouseState)
{
if (PrintTick.Count <= 0)
return;
ShowChart();
}
}
}
}
/// <summary>
/// 차트 확대 축소기능을 사용하기위한 이벤트
/// 23.11.14 -완
/// </summary>
private DelegateCommand<MouseWheelEventArgs> commandmousewheel;
public DelegateCommand<MouseWheelEventArgs> CommandMouseWheel =>
commandmousewheel ?? (commandmousewheel = new DelegateCommand<MouseWheelEventArgs>(ExecuteCommandMouseWheel));
void ExecuteCommandMouseWheel(MouseWheelEventArgs e)
{
//선택된 마켓이없을경우 실행하지않음.
if (Market == "")
return;
if (PrintTick.Count <= 0)
return;
int AddTicks = 10; //스크롤 한번에 10개씩 늘리고 줄임.
int Keep = PrintCandle; //현재 출력하는 캔들수를 저장해둠
int sign = 1;
int PrePrintCursor = PrintCursor;
bool Type = false;
int PreCnt = 0;//앞에 삭제 및 추가값
int NextCnt = 0;//뒤에 삭제 및 추가값
if (e.Delta > 0)
{
//확대
if (PrintCandle > PrintMinCandle)
{
PrintCandle = Math.Max(PrintCandle - AddTicks, PrintMinCandle);
sign *= -1;
Type = false;
}
else
return;//최대 확대상태여서 더이상 확대불가
}
else if (e.Delta < 0)
{
//축소
if (PrintCandle < PrintMaxCandle)
{
PrintCandle = Math.Min(PrintCandle + AddTicks, PrintMaxCandle);
Type = true;
}
else
return;//최소 축소상태여서 더이상 축소불가
}
if (Keep != PrintCandle)
{
TickWidth = Math.Max(MyGrid.ActualWidth / PrintCandle, 1.6) - CandleMargin;
double rate = move / MyGrid.ActualWidth;
PreCnt = Convert.ToInt32(AddTicks * rate * (sign * -1)); //N
NextCnt = Math.Abs(AddTicks - PreCnt); //10 - N
int ActivePreCnt = 0;
if (Type == true)
{
if (PreCnt != 0)
{
for (int i = 0; i < PreCnt; i++)
{
if (PrintCursor - (i - 1) < 0)
{
ActivePreCnt++;
}
}
if (ActivePreCnt != 0)
{
for (int i = 0; i < PrintTick.Count; i++)
{
PrintTick[i].NextNumber(ActivePreCnt);
}
for (int i = 0; i < ActivePreCnt; i++)
{
if (PrintTick.Count < PrintCandle)
{
Structs.Tick tick = GetTick(PrintCursor - (ActivePreCnt - (i + 1)), ActivePreCnt - (i + 1));
PrintTick.Insert(0, tick);
}
}
PrintCursor -= ActivePreCnt;
NextCnt = AddTicks - ActivePreCnt;
}
}
if (NextCnt != 0)
{
for (int i = 0; i < NextCnt; i++)
{
int index = (PrintCursor + PrintTick.Count);
if (index < TickData.Count && PrintTick.Count < PrintCandle)
{
Structs.Tick tick = GetTick(index, PrintTick.Count);
PrintTick.Add(tick);
}
}
}
}
else
{
if (PreCnt != 0)
{
//앞에 데이터 삭제시킴
for (int i = 0; i < PreCnt; i++)
{
if (PrintTick.Count > LeaveCnt)
{
PrintTick.RemoveAt(0);
ActivePreCnt++;
}
}
if (ActivePreCnt != 0)
{
//삭제시킨 데이터만큼 땡겨옴
for (int i = 0; i < PrintTick.Count; i++)
{
PrintTick[i].PreNumber(ActivePreCnt);
}
}
//TickData에서가져오는 커서값을 변경함.
//이후부턴 변경된 기준으로 가져올예정
PrintCursor += ActivePreCnt;
NextCnt = AddTicks - ActivePreCnt;
}
if (NextCnt != 0)
{
for (int i = 0; i < NextCnt; i++)
{
if (PrintTick.Count >= PrintCandle && PrintTick.Count > LeaveCnt)
{
PrintTick.RemoveAt(PrintTick.Count - 1);
}
}
}
}
CandleMaxMin();
GetCandleTime();
GetCoinPrice();
KeepPrintCursor = PrintCursor;
}
}
/// <summary>
/// 그리드에서 마우스가 떠났을때 이벤트
/// 23.11.11
/// </summary>
private DelegateCommand<object> commandmouseleave;
public DelegateCommand<object> CommandMouseLeave =>
commandmouseleave ?? (commandmouseleave = new DelegateCommand<object>(ExecuteCommandMouseLeave));
void ExecuteCommandMouseLeave(object parameter)
{
CandleMouseState = false;
}
/// <summary>
/// 캔들 불러오는 시간 타입을 변경시켜줌.
/// 23.11.15
/// </summary>
private DelegateCommand<string> commandselectedticktype;
public DelegateCommand<string> CommandSelectedTickType =>
commandselectedticktype ?? (commandselectedticktype = new DelegateCommand<string>(ExecuteCommandSelectedTickType));
void ExecuteCommandSelectedTickType(string type)
{
TickType = type;
if (type == "days")
{
VisibleComboBoxMin = Visibility.Collapsed;
MinTickTime = 1440;
}
else if (type == "weeks")
{
VisibleComboBoxMin = Visibility.Collapsed;
MinTickTime = 1440 * 7;
}
else if (type == "months")
{
VisibleComboBoxMin = Visibility.Collapsed;
MinTickTime = 1440 * 30;
}
else
{
//23.11.15 - 해당부분 수정 후 완료로 변경
//후에 TickTime값을 설정해주는작업을 완료했을때 1말고 설정된 값으로 변경시켜줘야함
VisibleComboBoxMin = Visibility.Visible;
MinTickTime = PreMinNumber;
}
CreateChart();
}
private DelegateCommand<object> commandselectedmin;
public DelegateCommand<object> CommandSelectedMin =>
commandselectedmin ?? (commandselectedmin = new DelegateCommand<object>(ExecuteCommandSelectedMin));
void ExecuteCommandSelectedMin(object parameter)
{
if (parameter is ComboBox)
{
ComboBox cb = (ComboBox)parameter;
PreMinNumber = (int)cb.SelectedItem;
MinTickTime = PreMinNumber;
CreateChart();
}
}
#endregion
#region Functions
/// <summary>
/// 해당 코인이 보유중일경우 차트에 출력.
/// </summary>
private void GetCoinPrice()
{
if (Market == "")
return;
if (PrintChartYAxis[4].Y <= 0)
{
UpbitFunctions.Accounts FAccount = new UpbitFunctions.Accounts();
double price = FAccount.GetAccountCoinPrice(Market);
PrintChartYAxis[4].CursorValue = price.ToString();
}
double value = Convert.ToDouble(PrintChartYAxis[4].CursorValue);
if (value >= 0)
{
double yvalue = (GridHeight * ((MaxPrice - value) / (MaxPrice - MinPrice))) + GapYAxis;
//차트 범위 초과했을경우 안보이게 수정
if (GridHeight <= yvalue || 0 >= yvalue)
yvalue = -100;
PrintChartYAxis[4].Y = yvalue;
}
else
{
PrintChartYAxis[4].Y = -100;
}
}
private void GetCandleTime()
{
if (PrintTick is null && Market == "")
return;
PrintChartXAxis = new ObservableCollection<ChartSideCursor>();
double TickWidth = (this.TickWidth + CandleMargin);
for (int i = PrintCursor + (PrintTick.Count - 1), j = PrintCandle + 1; i >= PrintCursor; i -= 50, j -= 50)
{
ChartSideCursor chart = new ChartSideCursor();
chart.X = (j - (PrintCandle - PrintTick.Count)) * TickWidth - (TickWidth / 2);
chart.Y = 0;
chart.Type = "Time";
chart.CursorValue = TickData[i].kst;
chart.BackColor = MyColors.ColorDefault;
chart.Color = MyColors.ColorDefaultBack;
PrintChartXAxis.Add(chart);
}
}
/// <summary>
/// 해당 Index에 캔들값을 가져옴. 현재 포커스 캔들 데이터출력을위해 사용
/// 23.11.15 - 완
/// </summary>
/// <param name="X"></param>
private void GetCandleInfo(double X)
{
double value = (MyGrid.ActualWidth - Math.Max((MyGrid.ActualWidth - X), 0.1)) / (TickWidth + CandleMargin);
int index = PrintCursor + Convert.ToInt32(Math.Floor(value));
Structs.Tick tick = GetTick(index);
if (tick is not null)
{
string len = tick.OP.ToString().Split(".")[0];
FLen = len.Length > 1 ? (len.Length > 2 ? "F0" : "F4") : "F8";
PrintOp = tick.OP.ToString(FLen);
PrintHp = tick.HP.ToString(FLen);
PrintLp = tick.LP.ToString(FLen);
PrintTp = tick.TP.ToString(FLen);
PrintTime = tick.KST;
TestNum = tick.Volume;
}
}
/// <summary>
/// TickData에서 PrintTick으로 데이터를 뽑아올때 사용. 구조체 Candle -> Tick으로 변경이여서 이렇게 사용.
/// 23.11.14 -완
/// </summary>
/// <param name="i">받아올 TickData Index Value</param>
/// <param name="x">PrintTick X axis</param>
/// <returns></returns>
private Structs.Tick GetTick(int i = 0, int x = 0)
{
if (i >= TickData.Count)
return null;
Structs.Tick tick = new Structs.Tick();
tick.X = x;
tick.Y = 0;
tick.HP = TickData[i].hp;
tick.LP = TickData[i].lp;
tick.TP = TickData[i].tp;
tick.OP = TickData[i].op;
tick.UTC = TickData[i].utc;
tick.KST = TickData[i].kst;
tick.Timestamp = TickData[i].timestamp;
tick.Volume = TickData[i].candle_acc_trade_volume;
tick.Colors = SetCandleColor(TickData[i].op, TickData[i].tp);
return tick;
}
/// <summary>
/// ShowChart함수 최소한 기능 첫 로드 차트만 생성해줌
/// 23.11.11 -완
/// </summary>
private void CreateChart()
{
PrintTick = new ObservableCollection<Structs.Tick>();
if (Market != "")
{
//초기 차트 생성
TickData = pfnc.Candles(TickType, MinTickTime, this.Market, "", 200);
TickData.Reverse();
int cnt = Math.Min(PrintCursor + PrintCandle, TickData.Count);
for (int i = PrintCursor, j = 0; i < cnt; i++, j++)
{
Structs.Tick tick = GetTick(i, j);
PrintTick.Add(tick);
}
CandleMaxMin();
GetCandleTime();
GetCoinPrice();
}
}
/// <summary>
/// 차트를 갱신시켜줌.
/// 23.11.14 -완
/// </summary>
private void ShowChart()
{
int newLeft = (int)((FirstXPos - move) / 10);
if (newLeft == 0)
return;
int AddCnt = 0;
bool MoveType = false;
FirstXPos = move;
//이전 데이터를 받아옴.
if ((PrintCursor + newLeft) < 0)
{
//갱신
DateTime FirstTime = Convert.ToDateTime(TickData[0].utc);
List<Candle> item = new List<Candle>();
item = pfnc.Candles(TickType, MinTickTime, this.Market, FirstTime.ToString("yyyy-MM-dd'T'HH:mm:ss"), 200);
for (int i = 0; i < item.Count; i++)
{
TickData.Insert(0, item[i]);
}
//더 갱신한 코인데이터 개수만큼 더해줌
KeepPrintCursor += item.Count;
PrintCursor += item.Count;
}
if (newLeft > 0)
{
PrintCursor = Math.Min(PrintCursor + newLeft, TickData.Count - LeaveCnt);
MoveType = true;
}
else if (newLeft < 0)
{
PrintCursor = Math.Max(PrintCursor + newLeft, 0);
MoveType = false;
}
AddCnt = Math.Abs(PrintCursor - KeepPrintCursor);
KeepPrintCursor = PrintCursor;
for (int i = 0; i < AddCnt; i++)
{
if (MoveType == true && PrintTick.Count > 1)
PrintTick.RemoveAt(0);
if (MoveType == false && PrintCandle - AddCnt < PrintTick.Count)
PrintTick.RemoveAt(PrintTick.Count - 1);
}
//기존데이터 X축 땡겨주고 밀어줌
if (AddCnt != 0)
{
foreach (Structs.Tick tick in PrintTick)
{
if (MoveType == true)
tick.PreNumber(Math.Abs(AddCnt));
else
tick.NextNumber(Math.Abs(AddCnt));
}
}
//데이터 등록
for (int i = 0; i < AddCnt; i++)
{
//정해진 개수를 넘어가면 추가하지않음.
int index = PrintCursor + PrintTick.Count + i;
if (MoveType && TickData.Count <= index)
{
break;
}
Candle c;
Structs.Tick tick = new Structs.Tick();
if (MoveType == true)
{
c = TickData[PrintCursor + PrintTick.Count];
tick.X = PrintTick.Count;
}
else
{
c = TickData[PrintCursor + (AddCnt - (i + 1))];
tick.X = AddCnt - (i + 1);
}
tick.Y = 0;
tick.HP = c.hp;
tick.LP = c.lp;
tick.TP = c.tp;
tick.OP = c.op;
tick.UTC = c.utc;
tick.KST = c.kst;
tick.Timestamp = c.timestamp;
tick.Volume = c.candle_acc_trade_volume;
tick.Colors = SetCandleColor(c.op, c.tp);
if (MoveType == true)
{
PrintTick.Add(tick);
}
else
{
PrintTick.Insert(0, tick);
}
}
CandleMaxMin();
GetCandleTime();
GetCoinPrice();
}
//데이터 병렬처리 테스트해봤음 음... 그냥 마우스 드래그할떄 제약을 좀 주는게 나을듯.
private async Task SizeUpdateDataAsync(int start)
{
await Task.Run(() =>
{
Debug.WriteLine(start.ToString());
for(int i = start; i < start + 50; i++)
{
if (PrintTick.Count <= i)
break;
if (MinPrice > PrintTick[i].LP)
MinPrice = PrintTick[i].LP;
if (MaxPrice < PrintTick[i].HP)
MaxPrice = PrintTick[i].HP;
if (MaxVolume < PrintTick[i].Volume)
MaxVolume = PrintTick[i].Volume;
}
});
}
/// <summary>
/// 캔들에 등록된 데이터를 가져와 Max갑과 Min값을 구해줌.
/// 23.11.14 -완
/// </summary>
private async void CandleMaxMin()
{
MaxPrice = 0;
MinPrice = 999999999;
MaxVolume = 0;
foreach (Structs.Tick tick in PrintTick)
{
if (MinPrice > tick.LP)
MinPrice = tick.LP;
if (MaxPrice < tick.HP)
MaxPrice = tick.HP;
if (MaxVolume < tick.Volume)
MaxVolume = tick.Volume;
}
/*
List<Task> tasks = new List<Task>();
for (int i = 0; i< PrintTick.Count;i += 50)
{
tasks.Add(SizeUpdateDataAsync(i));
}
await Task.WhenAll(tasks);
Debug.WriteLine("과연");
*/
string len = MaxPrice.ToString().Split(".")[0];
FLen = len.Length > 1 ? (len.Length > 2 ? "F0" : "F4") : "F8";
PrintChartYAxis[0].CursorValue = MaxPrice.ToString(FLen);
PrintChartYAxis[1].CursorValue = MinPrice.ToString(FLen);
ChartTickHeight = GridHeight / (MaxPrice - MinPrice);
VolumeTickHeight = GridVolumeHeight / MaxVolume;
PrintChartYAxis[3].CursorValue = PrintTick[PrintTick.Count - 1].TP.ToString();
PrintChartYAxis[3].Y = (GridHeight * ((MaxPrice - PrintTick[PrintTick.Count - 1].TP) / (MaxPrice - MinPrice))) + GapYAxis;
}
/// <summary>
/// 캔버스에 라인을 그려줌.
/// 23.11.11
/// </summary>
/// <param name="gap">줄 간격</param>
/// <param name="type">가로 / 세로</param>
/// <returns></returns>
private List<line> DrawLine(double gap, bool type)
{
List<line> lines = new List<line>();
for (int i = 0; i < this.lines; i++)
{
line add = new line();
if (type)
{
//가로
add.FromX = 0;
add.FromY = i * gap;
add.ToY = i * gap;
}
else
{
//세로
add.FromX = i * gap;
add.ToX = i * gap;
add.FromY = 0;
}
if (i % 10 == 0)
{
add.LineThickness = 0.6;
add.LineColor = Brushes.Transparent;//Brushes.Black;
}
else
{
add.LineThickness = 0.3;
add.LineColor = Brushes.Transparent;//Brushes.Gray;
}
lines.Add(add);
}
return lines;
}
/// <summary>
/// 외부에서 마켓을 선택했을경우 호출되는 함수
/// 23.11.14
/// </summary>
/// <param name="market"></param>
private void GetMarket(Structs.MarketCodes market)
{
Market = market.Market;
//기본값 셋팅
SetCtor();
//초기 차트 생성
CreateChart();
}
/// <summary>
/// 초기값 설정함수
/// 23.11.14
/// </summary>
private void SetCtor()
{
PrintChartYAxis = new ObservableCollection<ChartSideCursor>();
ChartSideCursor max = new ChartSideCursor();
max.X = 0;
max.Y = GapYAxis;
max.Type = "MaxPrice";
max.CursorValue = "";
max.BackColor = Brushes.Red;
max.Color = MyColors.ColorDefaultBack;
ChartSideCursor min = new ChartSideCursor();
min.X = 0;
min.Y = MyGrid.ActualHeight + GapYAxis;
min.Type = "MinPrice";
min.CursorValue = "";
min.BackColor = Brushes.Blue;
min.Color = MyColors.ColorDefaultBack;
ChartSideCursor cursor = new ChartSideCursor();
cursor.X = 0;
cursor.Y = -100;
cursor.Type = "Cursor";
cursor.CursorValue = "";
cursor.BackColor = Brushes.RoyalBlue;
cursor.Color = MyColors.ColorDefaultBack;
ChartSideCursor nowprice = new ChartSideCursor();
nowprice.X = 0;
nowprice.Y = -100;
nowprice.Type = "NowPrice";
nowprice.CursorValue = "";
nowprice.BackColor = Brushes.Green;
nowprice.Color = MyColors.ColorDefaultBack;
ChartSideCursor avgprice = new ChartSideCursor();
avgprice.X = 0;
avgprice.Y = -100;
avgprice.Type = "AvgPrice";
avgprice.CursorValue = "-100";
avgprice.BackColor = Brushes.Gold;
avgprice.Color = MyColors.ColorDefaultBack;
PrintChartYAxis.Add(max);
PrintChartYAxis.Add(min);
PrintChartYAxis.Add(cursor);
PrintChartYAxis.Add(nowprice);
PrintChartYAxis.Add(avgprice);
MaxVolume = 0;
KeepPrintCursor = 0;
PrintCandle = PrintDefaultCandle;
TickWidth = Math.Max(MyGrid.ActualWidth / PrintCandle, 1.6) - CandleMargin;
PrintCursor = 0;
MaxPrice = 0;
MinPrice = 9999999999;
}
private Brush SetCandleColor(double OP, double TP)
{
if (OP > TP)
return MyColors.ColorAsk;
else if (OP < TP)
return MyColors.ColorBid;
else
return MyColors.ColorBid;
}
public void TradeEvent(Trade tr)
{
throw new NotImplementedException();
}
public void OrderEvent(Orderbook ob)
{
}
/// <summary>
/// 실시간 데이터를 웹소켓에서 받아와서 차트에 갱신시켜줌.
/// 23.11.14
/// </summary>
/// <param name="tick"></param>
public void TickerEvent(Ticker tick)
{
if (Market == tick.cd)
{
if (TickData is not null && TickData.Count > 0)
{
int cnt = TickData.Count - 1;
DateTime StartTime = Convert.ToDateTime(TickData[cnt].kst);
StartTime = StartTime.AddSeconds(StartTime.Second * -1);
DateTime EndTime = StartTime.AddMinutes(MinTickTime).AddSeconds(-1);
DateTime dtTradeTime = FncDateTime.LongToDateTime(tick.ttms); //한국시간
UInt32 TradeTime = FncDateTime.DateTimeToInt(dtTradeTime);
try
{
//실시간 데이터 갱신
if (FncDateTime.DateTimeToInt(StartTime) <= TradeTime && TradeTime <= FncDateTime.DateTimeToInt(EndTime))
{
Candle candle = TickData[cnt];
candle.timestamp = tick.ttms;
candle.tp = tick.tp;
candle.candle_acc_trade_price = tick.atp;
candle.candle_acc_trade_volume += tick.tv;
//candle.kst = FncDateTime.LongToDateTime(tick.ttms).ToString();
//고가 갱신
if (candle.tp > candle.hp)
candle.hp = candle.tp;
//저가 갱신
if (candle.lp > candle.tp)
candle.lp = candle.tp;
//현재 갱신중인 틱을 보여줌.
if ((TickData.Count - PrintCursor) < PrintCandle)
{
Tick _tick = PrintTick[PrintTick.Count - 1];
_tick.TP = tick.tp;
//해당 기준 시간을 변경시 틱데이터를 불러올때 데이터가 불러와지지 않는 버그 발생 Timestamp를 변경해야할듯
//_tick.KST = candle.kst;
_tick.Timestamp = tick.ttms;
_tick.Colors = SetCandleColor(_tick.OP, _tick.TP);
if (_tick.TP < _tick.LP)
_tick.LP = _tick.TP;
if (_tick.TP > _tick.HP)
_tick.HP = _tick.TP;
_tick.Tail = 0;
_tick.Candle = 0;
_tick.CandleStartPoint = 0;
_tick.Volume += tick.tv;
if (MinPrice > _tick.LP)
MinPrice = _tick.LP;
if (MaxPrice < _tick.HP)
MaxPrice = _tick.HP;
if (MaxVolume < _tick.Volume)
MaxVolume = _tick.Volume;
VolumeTickHeight = GridVolumeHeight / MaxVolume;
ChartTickHeight = GridHeight / (MaxPrice - MinPrice);
PrintChartYAxis[0].CursorValue = MaxPrice.ToString(FLen);
PrintChartYAxis[1].CursorValue = MinPrice.ToString(FLen);
//현재가 표시해줌
PrintChartYAxis[3].CursorValue = _tick.TP.ToString();
PrintChartYAxis[3].Y = (GridHeight * ((MaxPrice - _tick.TP) / (MaxPrice - MinPrice))) + GapYAxis;
}
TickData[cnt] = candle;
}
else
{
EndTime = StartTime.AddMinutes(MinTickTime);
Candle precandel = TickData[cnt];
Candle candle = new Candle();
candle.timestamp = tick.ttms;
candle.tp = tick.tp;
candle.op = precandel.tp;
candle.hp = tick.tp;
candle.lp = tick.tp;
candle.kst = EndTime.ToString();
candle.candle_acc_trade_volume = tick.tv;
TickData.Add(candle);
//실시간 데이터 생성
if ((TickData.Count - PrintCursor) < PrintCandle)
{
Tick _tick = new Tick();
_tick.TP = tick.tp;
_tick.OP = precandel.tp;
_tick.HP = tick.tp;
_tick.LP = tick.tp;
_tick.KST = EndTime.ToString();
_tick.Volume = tick.tv;
if (MinPrice > _tick.LP)
MinPrice = _tick.LP;
if (MaxPrice < _tick.HP)
MaxPrice = _tick.HP;
if (MaxVolume < _tick.Volume)
MaxVolume = _tick.Volume;
VolumeTickHeight = GridVolumeHeight / MaxVolume;
ChartTickHeight = GridHeight / (MaxPrice - MinPrice);
_tick.Tail = 0;
_tick.Candle = 0;
_tick.CandleStartPoint = 0;
_tick.X = PrintTick[PrintTick.Count - 1].X + 1;
_tick.Colors = SetCandleColor(_tick.OP, _tick.TP);
Application.Current.Dispatcher.Invoke(() =>
{
PrintTick.Add(_tick);
GetCandleTime();
});
}
}
}
catch (Exception ex)
{
//MessageBox.Show("aaaaaa");
}
}
}
}
#endregion Functions
public ChartViewModel(IEventAggregator ea)
{
_ea = ea;
_ea.GetEvent<MessageCoinNameEvent>().Subscribe(GetMarket);
TickMargin = new Thickness { Left = 1, Right = 1 };
SetLanguage();
//색변경 이벤트 등록
PublicColor.Colors.ColorUpdate += (EventUpdateColor);
//틱 종류 저장
ListTickType = new List<string>();
ListTickType.Add("minutes"); ListTickType.Add("days");
ListTickType.Add("weeks"); ListTickType.Add("months");
TickType = ListTickType[0];
ListTickMin = new List<int>();
ListTickMin.Add(1); ListTickMin.Add(3);
ListTickMin.Add(5); ListTickMin.Add(15);
ListTickMin.Add(10); ListTickMin.Add(30);
ListTickMin.Add(60); ListTickMin.Add(240);
MinTickTime = ListTickMin[0];
//언어 변경시 이벤트 등록
Language.Language.LangChangeEvent += (SetLanguage);
//WebSocketTicker.OrderEvent += new WebSocketTicker.OrderEventHandler(OrderEvent);
WebSocketTicker.TickerEvent += new WebSocketTicker.TickEventHandler(TickerEvent);
}
}
}
반응형
'C# > 코인프로그램 - 코드' 카테고리의 다른 글
[C#/WPF] Upbit 프로젝트 Chart - 4(캔들) (2) | 2023.12.02 |
---|---|
[C#/WPF] Upbit 프로젝트 Chart - 3(그리드 라인) (2) | 2023.12.01 |
[C#/WPF] Upbit 프로젝트 Chart - 1 (0) | 2023.11.30 |
[C#/WPF] Upbit프로젝트 Order (0) | 2023.11.30 |
[C#/WPF] Upbit 프로젝트 CoinOrder - 1 (0) | 2023.11.30 |