•         

* * * * * 6

Туториалы по программированию под Betfair API. VB.Net


#1   Jericho

    Иерихон


  • RSB
  • 2226
859

20 January 2012 - 17:09

В этой теме будет вольный перевод туториалов Mumbles0'а по программированию под Betfair API со своими примерами и исходниками. В конце темы будет финальная версия бота.
Конечно повторять такие боты как GeeksToy и FairBot мы не будем, сможем конечно :) все вместе, но не будем. А туториалы будут полезны для многих пользователей, как некий FAQ по "ботостроительству".

Каждый отдельный пост - будет отдельный туториал...



Огромная просьба, не писать в этой теме. Комментирование, вопросы и ответы будут вестись в соседней теме - Обсуждение туториалов под Betfair API

Спасибо.

#2   Jericho

    Иерихон


  • RSB
  • 2226
859

20 January 2012 - 17:52

Шаг №1. Часть №1. Подключение к сервисам BFGlobalService и BFExchangeService.

Первое, что нам пригодится для написания программы это - Betfair Sports Exchange Reference Guide v6 - открыть (pdf). Это справочник функций для доступа к сервисам BetFair API.

Все досконально описывать не буду, будем подразумевать, что читатель знаком с программированием и имеет общие понятия принципа типов данных и построения логики программы.

Создаем новый проект в VB2010 и выбираем шаблон "Windows Forms Application". Чтобы работать с Betfair API (далее API) и использовать функции описанные в справочнике, необходимо создать ссылки на сервисы:

1. Добавляем в проект сервис BFGlobalService:
  • Открываем меню "Project"
  • Выбираем "Add Service Reference"
  • В текстовое поле "Address" вставляем: https://api.betfair....balService.wsdl
  • Нажимаем "Go". VB2010 начнет искать сервис.
  • В текстовое поле "Namespace" вместо "ServiceReference1" вводим "BFGlobal"
  • Нажимаем "OK" и студия добавит в проект ссылку на сервис.
2. Добавляем в проект сервис BFExchangeService:
  • Открываем меню "Project"
  • Выбираем "Add Service Reference"
  • В текстовое поле "Address" вставляем: https://api.betfair....ngeService.wsdl
  • Нажимаем "Go". VB2010 начнет искать сервис.
  • В текстовое поле "Namespace" вместо "ServiceReference1" вводим "BFUK"
  • Нажимаем "OK" и студия добавит в проект ссылку на сервис.
После чего Solution Explorer у нас должен выглядить следующим образом:
 006.png   8.04kb   61

Теперь мы с вами можем подключаться к бирже через API, получать рынки, делать ставки и т.д.

#3   Jericho

    Иерихон


  • RSB
  • 2226
859

20 January 2012 - 18:12

Шаг №1. Часть №2. Правка App.config

Забегая вперед, для тех, кто будет получать данные с помощью функции "getCompleteMarketPricesCompressed", необходимо изменить в настройках проекта размер буфера с 65536 на бОльший.

Двойным щелчком открыть App.config:
 007.png   9.63kb   29

и заменить все строки с числами 65536, скажем на 999999999

Прикрепил измененный файл:
 app.zip   898b   127


Все, в следующем шаге перейдем непосредственно к программированию.

#4   Jericho

    Иерихон


  • RSB
  • 2226
859

20 January 2012 - 18:55

Шаг №2. Часть №1. Авторизация и сохранение сессии.

Приведем наш проект к следующему виду для удобства:
 008.png   10.77kb   24

Добавим модуль и назовем его "modGlobal". Название форм, модулей, классов, переменных всегда выбирайте для себя сами по удобству и привычке...

В код модуля добавим следующие глобальные переменные:
	Public oHeaderGL As New BFGlobal.APIRequestHeader

	Public BetfairGL As New BFGlobal.BFGlobalServiceClient
	Public WithEvents BetFairUK As New BFUK.BFExchangeServiceClient

	Public SessTokFile As String

oHeaderGL будет содержать уникальное значение текущей сессии для Betfair. BetfairGL и BetFairUK - объекты на сервис, будут содержать все вызовы и методы API. В переменной SessTokFile будем хранить путь до файла, в котором будем сохранять уникальный ключ сессии. Это нужно для того, чтобы каждый раз не логинится при тестировании программы.

Далее добавим следующие функции и процедуры:
	Public Function oHeaderUK() As BFUK.APIRequestHeader
		Dim Header As New BFUK.APIRequestHeader
		Header.sessionToken = oHeaderGL.sessionToken
		Return Header
	End Function
	Public Sub CheckHeader(ByVal Header As BFGlobal.APIResponseHeader)
		With Header
			oHeaderGL.sessionToken = .sessionToken
		End With
	End Sub
	Public Sub CheckHeader(ByVal Header As BFUK.APIResponseHeader)
		With Header
			oHeaderGL.sessionToken = .sessionToken
		End With
	End Sub

В них собственно и проверяется и сохраняется значение сессии. Вторая процедура CheckHeader - перегрузка.
_____________

Далее добавим функцию авторизации:

Вариант 1. Пишем в коде свои логин и пароль, и просто вызываем процедуру для авторизации. Советую остановиться на этом варианте, чтобы по сто раз не их вводить.
	Public Sub Login()
		Dim oLoginReq As New BFGlobal.LoginReq
		Dim oLoginRes As New BFGlobal.LoginResp
		With oLoginReq
			.username = "логин"
			.password = "пароль"
			.productId = 82
		End With
		oLoginRes = BetfairGL.login(oLoginReq)
		With oLoginRes
			CheckHeader(.header)
		End With
	End Sub

Вариант 2. Это уже функция, через которую можно передать логин и пароль. Это для диалогового окна...
	Public Function LoginEx(ByVal username As String, ByVal password As String) As Integer
		Dim oLoginReq As New BFGlobal.LoginReq
		Dim oLoginRes As New BFGlobal.LoginResp
		With oLoginReq
			.username = username
			.password = password
			.productId = 82
		End With
		oLoginRes = BetfairGL.login(oLoginReq)
		With oLoginRes
			CheckHeader(.header)
		End With
		Return 1
	End Function

С модулем пока все...

Исходники модуля для данного шага:
 modGlobal.zip   606b   127

#5   Jericho

    Иерихон


  • RSB
  • 2226
859

20 January 2012 - 19:22

Шаг №2. Часть №2. Авторизация и сохранение сессии.

Теперь перейдем к коду формы. Добавим два метода, при загрузке и закрытии формы:
	Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
		SessTokFile = System.Windows.Forms.Application.StartupPath & "\SessToken.txt"
		oHeaderGL.sessionToken = My.Computer.FileSystem.ReadAllText(SessTokFile)
	End Sub
	Private Sub Form1_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
		My.Computer.FileSystem.WriteAllText(SessTokFile, oHeaderGL.sessionToken, False)
	End Sub

При загрузке программы мы получим в переменную SessTokFile путь до нашего исполняемого файла. "\SessToken.txt" - название файла, в котором будет хранится уник. знач. сессии. Если файл есть - прочитаем это значение..
При закрытии программы сохраним значение сессии.
Файл самому создавать не нужно. Если его нет в директории - он создатся сам при закрытии формы.
___________

Ну и кидаем на форму кнопку и пишем:
	Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
		Call Login()
	End Sub

При запуске программы - нажимаем на кнопку - произойдет авторизация. После чего закройте форму (нажмите на крестик) - ваша сессия сохранится, и в следующие разы "логинится" будет уже не надо.
Сколько длится сессия, в подробности не вдавался..(


Ошибки в модуле я не ловлю (такие как: неправильный пароль, блокировка аккаунта, сбой работы API и.т.д.) - это все будет в финальных исходниках...
___________

исходники формы к этому посту:
 forms.zip   3.04kb   95


В следующем шаге будем получать интересности :) логин, баланс и.т.д..

#6   Jericho

    Иерихон


  • RSB
  • 2226
859

20 January 2012 - 19:42

Шаг №2. Часть №3. Интерфейс

Набросал примерный интерфейс будущего бота:
 009.png   12.15kb   35
  • (1) - выбор рынков как в Гигсе
  • (2) - отображение рынков сортированных по времени
  • (3) - в этой вкладке будут все ставки (сматченные/несматченные)
  • (4) - баланс и др. информация.
Каждый рынок будет открываться в новом окне как в Гигсе.

Тема у меня в 7ке стоит 9х... Но это на время программирования. Имхо невозможно расставлять элементы в 7ке...

#7   Jericho

    Иерихон


  • RSB
  • 2226
859

20 January 2012 - 20:13

Шаг №2. Часть №4. Функции получения Имени пользователя и баланса аккаунта.

Откроем наш модуль и добавим следующие функции:
	Public Currency As String
	Public Function GetProfile() As String
		Dim oProfileReq As New BFGlobal.ViewProfileReq
		Dim oProfileResp As New BFGlobal.ViewProfileResp
		oProfileReq.header = oHeaderGL
		oProfileResp = BetfairGL.viewProfile(oProfileReq)
		With oProfileResp
			CheckHeader(.header)
			Currency = .currency
			GetProfile = .userName
		End With
	End Function
	Public Function GetAccountFunds() As String
		Dim oAccountFundsReq As New BFUK.GetAccountFundsReq
		Dim oAccountFundsRes As New BFUK.GetAccountFundsResp
		oAccountFundsReq.header = oHeaderUK()
		oAccountFundsRes = BetFairUK.getAccountFunds(oAccountFundsReq)
		With oAccountFundsRes
			CheckHeader(.header)
			GetAccountFunds = Currency & .availBalance.ToString
		End With
	End Function

В переменной Currency будет содержаться валюта аккаунта. Функция GetProfile() возвращает логин пользователя, и получает валюту в переменную Currency. Функция GetAccountFunds() - возвращает баланс аккаунта.

Вызывать функции следует поочереди, например так:
		Label2.Text = GetProfile()
		Label1.Text = GetAccountFunds()

Через oProfileResp можно получить множество интересных значений. Подробнее см. Betfair Sports Exchange Reference Guide v6 - viewProfile.

#8   Jericho

    Иерихон


  • RSB
  • 2226
859

20 January 2012 - 21:45

Шаг №3. Часть №1. Получение списка рынков

В этом шаге мы рассмотрим как можно получить все рынки, рынки определенного спорта, рынки за конкретный день и т.д. В последней части шага №3 у нас будет готов аналог навигатора как в GeeksToy.

1. Получаем список всех спортивных игр. Добавляем процедуру в модуль:
	Public Sub GetEvents()
		Dim oEventsReq As New BFGlobal.GetEventTypesReq
		Dim oEventsResp As BFGlobal.GetEventTypesResp
		oEventsReq.header = oHeaderGL
		oEventsResp = BetfairGL.getActiveEventTypes(oEventsReq)
		With oEventsResp
			CheckHeader(.header)
			If .errorCode = BFGlobal.GetEventsErrorEnum.OK Then
				For i = 0 To .eventTypeItems.Length - 1
					With .eventTypeItems(i)
						frmMain.ListBox1.Items.Add(.name & " (" & .id & ")")
					End With
				Next
			End If
		End With
	End Sub

Добавим на форму элемент ListBox для тестирования новой функции. При вызове GetEvents() получим следующие записи:
 010.png   10.82kb   23

,где .name - название вида спорта, id - соответственно id по которому мы сможем получить другие подкатегории спорта.

Как видно из списка у Скачек id -7, у Бокса - 6 и т.д. К вопросу vlad56'а как получить остальные рынки, кроме скачек. Ответ - подставить вместо 7ки другую цифру, соответсвующую id спорта... Подробнее в следующей части.

#9   Jericho

    Иерихон


  • RSB
  • 2226
859

21 January 2012 - 13:36

Шаг №3. Часть №2. Получение списка рынков отдельного вида спорта

В этой части мы рассмотрим как можно получить дерево рынка конкретного спорта. Добавим в наш модуль класс MarketDataType:

	Class MarketDataType
		Public marketId As Integer
		Public marketName As String
		Public marketType As String
		Public marketStatus As String
		Public eventDate As DateTime
		Public menuPath As String
		Public eventHeirachy As String
		Public betDelay As Integer
		Public exchangeId As Integer
		Public countryCode As String
		Public lastRefresh As DateTime
		Public noOfRunners As Integer
		Public noOfWinners As Integer
		Public totalAmountMatched As Double
		Public bspMarket As Boolean
		Public turningInPlay As Boolean
	End Class

Благодоря этому классу мы сможем получить всю необходимую информацию по рынку... Название, статус (закрыт, приостановлен, ин-плей..), время начала и т.д.

Добавим в модуль еще один класс UnpackAllMarkets, который в конструкторе класса будет заполнять структуру MarketDataType
   Class UnpackAllMarkets
		Public marketData As MarketDataType() = {}
		Private Const BaseDate As DateTime = #1/1/1970#
		Private Const ColonCode = "&%^@"
		Sub New(ByVal MarketString As String)
			Dim n As Integer, Mdata, Field As String()
			Mdata = MarketString.Replace("\:", ColonCode).Split(":")
			n = UBound(Mdata) - 1
			ReDim marketData(n)
			For i = 0 To n
				Field = Mdata(i + 1).Replace("\~", "-").Split("~")
				marketData(i) = New MarketDataType
				With marketData(i)
					.marketId = Field(0)
					.marketName = Field(1).Replace(ColonCode, ":")
					.marketType = Field(2)
					.marketStatus = Field(3)
					.eventDate = BaseDate.AddMilliseconds(Field(4))
					.menuPath = Field(5).Replace(ColonCode, ":")
					.eventHeirachy = Field(6)
					.betDelay = Field(7)
					.exchangeId = Field(8)
					.countryCode = Field(9)
					.lastRefresh = BaseDate.AddMilliseconds(Field(10))
					.noOfRunners = Field(11)
					.noOfWinners = Field(12)
					.totalAmountMatched = Val(Field(13))
					.bspMarket = (Field(14) = "Y")
					.turningInPlay = (Field(15) = "Y")
				End With
			Next
		End Sub
	End Class

Теперь мы сможем получить все рынки биржи. Добавляем следующую процедуру в содуль:
	Public Sub GetSoccerEvents()
		Dim oMarketsReq As New BFUK.GetAllMarketsReq
		With oMarketsReq
			.header = oHeaderUK()
			ReDim .eventTypeIds(0) : .eventTypeIds(0) = 1
			.fromDate = Today
			.toDate = Today.AddDays(1)
			.locale = "UK"
		End With
		With BetFairUK.getAllMarkets(oMarketsReq)
			CheckHeader(.header)
			If .errorCode = BFUK.GetAllMarketsErrorEnum.OK Then
				Dim AllMarkets As New UnpackAllMarkets(.marketData)
				Call PopulateListView(AllMarkets, frmMain.ListView1)
			End If
		End With
	End Sub

Здесь:
  • Функцию Call PopulateListView(AllMarkets, frmMain.ListView1), которая будет заполнять элемент ListView1 деревон рынков мы рассмотрем чуть позднее.
  • .eventTypeIds(0) = 1 - означает, что "выбранный спорт" - футбол
  • .fromDate = Today .toDate = Today.AddDays(1) - время рынков. У нас здесь рынки на сегодня. В GeeksToy можно выбирать "Вчера, Сегодня, Завтра" и т.д... Вот именно в эти параметры и подставляется время(дата) выбора. Т.е. от такого-то числа до такого-то...
  • .locale = "UK" для рынков Королевства...
_____________


Ну и напоследок добавим в модуль функцию, которая заполнит нам дерево рынками:
	Friend Sub PopulateTreeView(ByVal AllMarkets As UnpackAllMarkets, ByVal TreeView As TreeView)
		Dim Ids, Names As String(), Nodes As TreeNodeCollection
		TreeView.Nodes.Clear()
		With AllMarkets
			For i = 0 To .marketData.Length - 1
				With .marketData(i)
					Ids = .eventHeirachy.Split("/")
					Names = .menuPath.Split("\")
					Nodes = TreeView.Nodes
					For j = 1 To UBound(Ids)
						If Not Nodes.ContainsKey(Ids(j)) Then
							Dim Node As New TreeNode
							Node.Text = If(j <= UBound(Names), Names(j), .marketName)
							Node.Name = Ids(j)
							Nodes.Add(Node)
						End If
						Nodes = Nodes(Ids(j)).Nodes
					Next
				End With
			Next i
		End With
	End Sub

Все. Кидаем на форму элемент TreeView и вызываем процедуру GetSoccerEvents:
Call GetSoccerEvents()

Вот такое дерево получилось у меня:
 013.png   19.91kb   22

Иконки стран по вкусу :) Можно и без них. Таким образом в GeeksToy'е и получаются отдельные виды спорта - скачки, теннис, футбол...

В седующей части прикреплю исходники проделанных шагов.

#10   Jericho

    Иерихон


  • RSB
  • 2226
859

21 January 2012 - 14:01

Шаг №3. Часть №3. Исходники проекта проделанных шагов

Прикрепил к посту исходники проекта целиком с таким временным интерфейсом:
 014.png   18.44kb   21

 Test_BF.zip   134.52kb   168

#11   Jericho

    Иерихон


  • RSB
  • 2226
859

21 January 2012 - 16:29

Шаг №4. Часть №1. Строим навигатор как в GeeksToy

Открываем проект из Шага №3, и в код формы добавляем следующий код:
    Private Sub frmMain_Invalidated(ByVal sender As Object, ByVal e As System.Windows.Forms.InvalidateEventArgs) Handles Me.Invalidated
	    ComboBox1.Items.Add("Horses")
	    ComboBox1.Items.Add("Soccer") : ComboBox1.SelectedIndex = 1
	    ComboBox1.Items.Add("Tennis")
	    ComboBox2.Items.Add("Yesterday")
	    ComboBox2.Items.Add("Today") : ComboBox2.SelectedIndex = 1
	    ComboBox2.Items.Add("Tomorrow")
	    ComboBox3.Items.Add("All") : ComboBox3.SelectedIndex = 0
	    ComboBox3.Items.Add("In-play")
	    ComboBox3.Items.Add("On Now")
    End Sub

Тем самым мы получим следующий вид:
 016.png   2.58kb   9

Вместо приведенного кода можно добавить итемы и через Properties(свойства) элемента..
По умолчанию у нас будут показываться все футбольные рынки на сегодняшний день.

#12   Jericho

    Иерихон


  • RSB
  • 2226
859

21 January 2012 - 17:12

Шаг №4. Часть №1. Строим навигатор как в GeeksToy

Далее добавим в код формы реакцию на выбор ComboBox'ов:
	Private Sub ComboBox1_SelectedValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles ComboBox1.SelectedValueChanged
		selSport = ComboBox1.SelectedItem
	End Sub
	Private Sub ComboBox2_SelectedValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles ComboBox2.SelectedValueChanged
		selTime = ComboBox2.SelectedItem
	End Sub
	Private Sub ComboBox3_SelectedValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles ComboBox3.SelectedValueChanged
		selType = ComboBox3.SelectedItem
	End Sub

Переменные selSport, selTime, selType, объявим в модуле:
Public selSport As String, selTime As String, selType As String

И подправим функцию GetSoccerEvents на следующую процедуру:
   Public Sub GetEventsEx()
		Dim eventTypeIds As Integer, fromDate, toDate As Date
		Select Case selSport
			Case "Horses" : eventTypeIds = 7
			Case "Soccer" : eventTypeIds = 1
			Case "Tennis" : eventTypeIds = 2
		End Select
		Select Case selTime
			Case "Yesterday"
				fromDate = Today.AddDays(-1)
				toDate = Today
			Case "Today"
				fromDate = Today
				toDate = Today.AddDays(1)
			Case "Tomorrow"
				fromDate = Today.AddDays(1)
				toDate = Today.AddDays(2)
		End Select
		Dim oMarketsReq As New BFUK.GetAllMarketsReq
		With oMarketsReq
			.header = oHeaderUK()
			ReDim .eventTypeIds(0) : .eventTypeIds(0) = eventTypeIds
			.fromDate = fromDate
			.toDate = toDate
			.locale = "UK"
		End With
		With BetFairUK.getAllMarkets(oMarketsReq)
			CheckHeader(.header)
			If .errorCode = BFUK.GetAllMarketsErrorEnum.OK Then
				Dim AllMarkets As New UnpackAllMarkets(.marketData)
				Call PopulateTreeView(AllMarkets, frmMain.TreeView1)
			End If
		End With
	End Sub

Теперь при выборе футбола - получим футбольные рынки, при лошадках - лошадиные) и т.д...
 017.png   5.82kb   15

#13   Jericho

    Иерихон


  • RSB
  • 2226
859

24 January 2012 - 20:27

Дополнение. №1

В теме "Запланированное и не очень отключение серверов Betfair" говорилось о боте, который "поможет" узнать какие технические работы ведутся на бирже, и когда это кончится...

Пример сегодняшних работ для английской аудитории:
 031.png   8.18kb   23

______________
Немного теории как это все получить:
Чтобы увидеть ведутся ли технические работы на сервере - достаточно пройти по адресу: service.betfair.info/index.html

Открыв исходники html страницы, мы увидим следующее:
 032.png   4.4kb   21

В файле englishfrom1.js содержится строка даты и времени, когда планируется отключение (25/01/2012, 6:00AM), соответственно englishto1.js - время и дата окончания работ, englishproduct1.js - вид работ...

В имени файла, например englishfrom1.js - english означает страну, для которой отображать время (для России - russianfrom1.js), а цифра 1 - означает строку работ. Как видно строк с работами 3, значит и фалов должно быть три...
______________

Теперь давайте получим это все программно...
1. На форме разместим элемент ListView:
 034.png   9.4kb   12

и в коде формы у нас должно быть следующее:
Imports System.IO

Public Class Form1
	Private Sub Form1_Invalidated(ByVal sender As Object, ByVal e As System.Windows.Forms.InvalidateEventArgs) Handles Me.Invalidated
		Dim objItem As ListViewItem
		Try
			Dim i As Integer = 1
			Do
				objItem = ListView1.Items.Add(GetErr("englishfrom", i))
				objItem.SubItems.Add(GetErr("englishto", i))
				objItem.SubItems.Add(GetErr("englishproduct", i))
				i = i + 1
			Loop
		Catch ex As Exception
			'MsgBox("Err")
		End Try
	End Sub
	Function GetContents(ByRef strSource As String) As String
		Dim strStart, strEnd As Integer, strLen As Integer
		strStart = InStr(strSource, "(") + 2
		strEnd = InStr(strSource, ")") - 1
		strLen = strEnd - strStart
		Return Mid(strSource, strStart, strLen)
	End Function

	Function GetErr(ByVal str As String, ByVal i As Integer) As String
		Dim web_client As New Net.WebClient()
		Dim response As Stream
		response = web_client.OpenRead("http://service.betfair.info/files/frames/" & str & i & ".js")
		Dim stream_reader As New IO.StreamReader(response)
		Return GetContents(stream_reader.ReadToEnd()) : stream_reader.Close()
	End Function
End Class

Теперь разберем по функциям:
1. Private Sub Form1_Invalidated.... - начинает работу при запуске программы.В ней через цикл Do... Loop заполняются строки элемента ListView. Если технических работ на сервере нет - мы получим пустой список.

2. GetContents - функция возвращает строку между скобками и удаляет все лишнее. Если, например есть строка блаблабла(инфа)бла... то функция возвратит - инфа

3. GetErr - функция через web_client.OpenRead получает содержимое тех самых файлов, описаных выше (englishfrom1.js) и возвращает через GetContents чистую нужную строку с информацией...

В итоге мы видим следующую картину:
 033.png   11kb   11

Для России в Private Sub Form1_Invalidated заменить
objItem = ListView1.Items.Add(GetErr("englishfrom", i))
objItem.SubItems.Add(GetErr("englishto", i))
objItem.SubItems.Add(GetErr("englishproduct", i))
на
objItem = ListView1.Items.Add(GetErr("russianfrom", i))
objItem.SubItems.Add(GetErr("russianto", i))
objItem.SubItems.Add(GetErr("russianproduct", i))

Мне очень помогает... Написал этот код сразу же, после того как попал после незапланированных тех. работ...
Хотя если ставка уже принята, и внезапно пошли сбои в работе сервера, будет уже поздно... но проверить лишний раз никогда не мешает.

#14   Jericho

    Иерихон


  • RSB
  • 2226
859

28 January 2012 - 19:10

Шаг №5. Часть№1. Получаем всю информацию о рынке(коэф-ты, суммы, графики и.т.д.)

Прежде чем приступить к новому шагу - небольшое изменение к Дополнению. №1.

Чтобы исключить тех. работы, которые проводились раннее - необходимо поставить в цикл проверку сегодняшнего дня: If GetErr("englishfrom", i) = Today Then:
Do
	If GetErr("englishfrom", i) = Today Then
		objItem = ListView1.Items.Add(GetErr("englishfrom", i))
		objItem.SubItems.Add(GetErr("englishto", i))
		objItem.SubItems.Add(GetErr("englishproduct", i))
	End If
	i = i + 1
Loop

____________

Определяем ID рынка

После того, как мы прошли все четыре шага, у нас есть дерево, которое заполнено спортивными событиями. Пока только будем рассматривать футбол(особой разницы между спортом нет... Единственное в скачках больше информации о рынке).

У каждого элемента дерева есть ключ - в эти ключи в функции PopulateTreeView(AllMarkets, frmMain.TreeView1)
мы занесли id событий и рынков(всю иерархию).

Зная id каждого рынка мы сможем получить о нем всю возможную информацию...

Добавим для наглядности на форму два элемента TextBox.. И поместим в код формы следующие строки:
	Private Sub TreeView1_AfterSelect(ByVal sender As System.Object, ByVal e As System.Windows.Forms.TreeViewEventArgs) Handles TreeView1.AfterSelect
		TextBox2.Text = Val(e.Node.Name)
		If e.Node.Nodes.Count = 0 Then
			Beep()
			Dim marketId As Integer = Val(e.Node.Name)
			TextBox1.Text = marketId & "-" & e.Node.Text
			TextBox2.Text = e.Node.FullPath
		End If
	End Sub

Данная процедура будет выполняться после "выбора" элемента дерева(клик или перемещение клавишами).

Запускаем проект... И теперь перемещаясь по дереву, если элемент в иерархии не конечный:
 039.png   8.76kb   17
  • В TextBox2 - мы увидим Id элемента иерархии событий на бирже...
А если элемент последний. Какраз то, что нам нужно - увидим следующее:
 038.png   12.05kb   14
  • В TextBox1 - id рынка и через тире его название
  • В TextBox2 - "путь" элементов
В следующей части рассмотрим, что можно "выжать" из ID...

#15   Jericho

    Иерихон


  • RSB
  • 2226
859

28 January 2012 - 19:38

Шаг №5. Часть№2. Получаем всю информацию о рынке(коэф-ты, суммы, графики и.т.д.)

Сейчас приведу кусок кода, а потом мы его рассмотрим очень подробно

Думаю, читатель, у которого хватило терпения дочитать до этого шага уже знает, что такое процедуры и функции, принимаемые и передаваемые переменные... Поэтому далее будут только функции, которые "делают" хорошие вещи), и как их вызвать...

Следующая функция получит коэф-ты первой строчки Back и Lay того рынка, которой мы укажем через Id:
	Public Function GetOdds(ByVal id As String, ByRef Back As String, ByRef Lay As String) As Boolean
		Dim oMarketReq As New BFUK.GetMarketPricesReq
		Dim oMarketResp As BFUK.GetMarketPricesResp
		With oMarketReq
			.header = oHeaderUK()
			.marketId = id
		End With
		oMarketResp = BetFairUK.getMarketPrices(oMarketReq)
		With oMarketResp
			CheckHeader(.header)
			If .errorCode = BFUK.GetMarketPricesErrorEnum.OK Then
				With .marketPrices
					With .runnerPrices(0)
						With .bestPricesToBack(0)
							Back = .price
						End With
						With .bestPricesToLay(0)
							Lay = .price
						End With
					End With
				End With
			End If
		End With

		Return True
	End Function

Быстренько по функции
  • в oMarketReq передаем id рынка (помните, мы в предыдущем шаге научились его получать...)
  • получаем огромную структуру данных в oMarketResp

Вызов функции:
Dim Lay, Back As String
GetOdds(id, Back, Lay)
  • в id должен быть идентификатор рынка
  • а в переменных Lay и Back будут коэф-ты соответственно...

В следующей части рассмотрим что же за параметры такие runnerPrices(0), bestPricesToBack(0) и другие непонятные слова.

#16   Jericho

    Иерихон


  • RSB
  • 2226
859

28 January 2012 - 21:16

Шаг №5. Часть№3. Получаем всю информацию о рынке(коэф-ты, суммы, графики и.т.д.)

Проведем аналогию GeeksToy'ем и поймем что же за информацию мы можем получить в oMarketResp.

1. .runnerPrices() - если дословно, массив забегов (скорее всего пошло из скачек)... По-нашему событий.
 040.png   21.58kb   12
  • Видим на скрине, что Ювентус - это .runnerPrices(0), Удинезе - .runnerPrices(1) и т.д. Массив может быть разной размерности.. Здесь три, на рынках тоталов - два, на рынках "Результат(CorrectScore)" - куча...
  • Кол-во этих "забегов" можно взять из marketData(i) из noOfRunners (Шаг №3. Часть №2. Получение списка рынков отдельного вида спорта).
В .runnerPrices():

1. .bestPricesToBack() и .bestPricesToLay(0) - массив коэф-тов на Back и Lay. Отсчет идет, как показано на рисунке:
 040.png   25.68kb   15

2. .selectionId - id выбранного нами .runnerPrices(). Впоследствии пригодится нам для получения графика...

3. .totalAmountMatched - сумма сматченных денег выбранного нами .runnerPrices(). (показана зеленым прямоугольником)
 040.png   24.96kb   14
  • Общая сматченная сумма(показана красным прямоугольником) получается путем сложения всех .totalAmountMatched.
4. .lastPriceMatched - последний сматченный коэф. выбранного нами .runnerPrices():

 040.png   25.23kb   20
  • Красным прямоугольником помечен последний сматченный коэф
  • Зеленым - сумма. Сумма вычисляется путем запоминания .totalAmountMatched в переменную. А после того, как она изменилась - вычитается из сохраненной. Результат и будет последняя сматченная сумма.
В .bestPricesToBack() и .bestPricesToLay()

1..price и .amountAvailable - коэф. и сумма соответсвенно для выбранного stPricesToBack() или .bestPricesToLay()
 040.png   24.92kb   9

Чтобы более было понятно к середине дня скину функцию, которая получит по всю возможную информацию...

#17   Jericho

    Иерихон


  • RSB
  • 2226
859

28 January 2012 - 21:35

Шаг №5. Часть№3. Получаем всю информацию о рынке(коэф-ты, суммы, графики и.т.д.)

1. Получение графика.

Следующая процедура загружает график интересуещего нас события.

Необходимо расположить на форме элемент PictureBox1 и вызвать следующую процидуру:
    Private Sub GetGraph(ByVal MarketId As String, ByVal SelectionId As String)
	    Dim MyWebClient As New System.Net.WebClient
	    Dim ImageInBytes() As Byte = MyWebClient.DownloadData("http://uk.site.sports.betfair.com/betting/LoadRunnerInfoChartAction.do?marketId=" & MarketId & "&selectionId=" & SelectionId & "&asianLineId=0")
	    Dim ImageStream As New IO.MemoryStream(ImageInBytes)
	    PictureBox1.Image = New System.Drawing.Bitmap(ImageStream)
    End Sub
  • В параметр ByVal MarketId передать id рынка (Шаг №5. Часть№1.)
  • В параметр ByVal SelectionId передать id события (Шаг №5. Часть№2 - Часть№3.)

Функция загружает в массив байт необходимую картинку с графиком с сервера биржи, и рисует ее в элементе PictureBox1:
 041.png   76.59kb   7

В следующем шаге рассмотрим понятие Многопото́чность и научимся получать много данных одновремнно "без тормозов" главной формы программы.

#18   Jericho

    Иерихон


  • RSB
  • 2226
859

10 February 2012 - 19:12

Шаг №6. Часть№1. Многопоточность

Если мы с вами захотим получить много информации с разных рынков, то наша программа "подвиснет" на момент получения и обработки данных, пока функция не будет закончена. Чтобы этого избежать можно использовать фоновый поток для нашего приложения.

Многопоточность - свойство платформы (например, операционной системы, виртуальной машины и т. д.) или приложения, состоящее в том, что процесс, порождённый в операционной системе, может состоять из нескольких потоков, выполняющихся «параллельно», то есть без предписанного порядка во времени.

Чтобы облегчить нам жизнь существует такой компонент, как BackgroundWorker, он существует начиная с .NET Framework 2.0. Компонент позволяет производить некоторую операцию в отдельном потоке, например это бывает необходимо при долгих вычислениях, или множественных и продолжительных запросах к базе данных, сервисам и т.д..

У компонента всего три события:
  • DoWork - "работа", которую будет делать поток
  • ProgressChanged - отображает изменение потока
  • RunWorkerCompleted - действия по окончании потока
Одна проблема в том, что мы не можем в коде потока в событии DoWork обращаться к компонентам созданным в процессе основной программы. Иными словами если попытаемся в потоке написать TextBox1.Text = "Text" - то получим ошибку, компонент TextBox1 создан не в этом потоке... Поэтому действия следующие:

Сделаем получение коэф. из Шаг №5. Часть№2. в отдельном потоке:

1. Добавим структуру, которую и заполним в новом потоке информацией о рынке:
	Public Structure MarketOdds
		Dim MarketId As String
		Dim b0 As String
		Dim b0Amount As String
		Dim l0 As String
		Dim l0Amount As String
		Dim tAmount As String
		Dim SelId As String
	End Structure
	Public mOdds() As MarketOdds = {}

Структурку делал под себя - у вас она может быть сколь угодно разной(можете хоть всю информацию о рынке выжать в подобную структуру)..

Содержание структуры:
  • MarketId - id рынка
  • b0 - текущий коэф. back рынка
  • b0Amount - сумма под коэф. back
  • l0 - текущий коэф. lay рынка
  • l0Amount - сумма под коэф. lay
  • tAmount - сматченная сумма рынка
  • SelId - id выбранного рынка (например ТМ или ТБ)
Ну и создадим массив структур: Public mOdds() As MarketOdds = {}


2. Вставим следующий код, например в событие кнопки:
ReDim mOdds(ListView1.Items.Count - 1)
For i As Integer = 0 To ListView1.Items.Count - 1
   mOdds(i).MarketId = ListView1.Items(i).SubItems(6).Text
Next
BackgroundWorker1.RunWorkerAsync()

В этом коде:
  • ReDim mOdds(ListView1.Items.Count - 1) - для начала нам нужно указать размерность массива нашей структуры. В данной ситуации размерность равна кол-ву элементов в контроле ListView1. В строках ListView1 я храню id рынка. (как получить id рынков - Шаг №3. Часть №2.)
  • А далее присваиваем каждому элементу массива mOdds(i).MarketId свой id
  • BackgroundWorker1.RunWorkerAsync() - запускаем поток
3. Процедура работы потока:
	Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
		Dim oMarketReq As New BFUK.GetMarketPricesReq
		Dim oMarketResp As BFUK.GetMarketPricesResp
		For i As Integer = 0 To UBound(mOdds)
			With oMarketReq
				.header = oHeaderUK()
				.marketId = mOdds(i).MarketId
			End With
			oMarketResp = BetFairUK.getMarketPrices(oMarketReq)
			With oMarketResp
				CheckHeader(.header)
				If .errorCode = BFUK.GetMarketPricesErrorEnum.OK Then
					With .marketPrices
						With .runnerPrices(0)
							With .bestPricesToBack(0)
								mOdds(i).b0 = .price
								mOdds(i).b0Amount = .amountAvailable
							End With
							With .bestPricesToLay(0)
								mOdds(i).l0 = .price
								mOdds(i).l0Amount = .amountAvailable
							End With
							mOdds(i).tAmount = .totalAmountMatched.ToString
							mOdds(i).SelId = .selectionId
						End With
					End With
				End If
			End With
			System.Threading.Thread.Sleep(500)
			If ListView1.Items.Count = 1 Then
			Else
				BackgroundWorker1.ReportProgress(CInt((i / (ListView1.Items.Count - 1)) * 100))
			End If
		Next
	End Sub

По коду:
Код из Шаг №5. Часть№2, только данные о рынке загружаются в массив mOdds.
  • В каждом шаге цикла есть строка - System.Threading.Thread.Sleep(500) - заставляет поток "заснуть" на 500 милисекунд... Чтобы ограничить кол-во запросов к бирже.
  • BackgroundWorker1.ReportProgress(CInt((i / (ListView1.Items.Count - 1)) * 100)) - передает процент выполнения задачи...

4. Процедура отображения "прогресса". Добавим на форму элемент ProgressBar1
	Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
		Me.ProgressBar1.Value = e.ProgressPercentage
	End Sub


5. При завершении потока:
	Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
		For i As Integer = 0 To ListView1.Items.Count - 1
			ListView1.Items(i).SubItems(2).Text = mOdds(i).b0
			ListView1.Items(i).SubItems(3).Text = mOdds(i).l0
		Next
	End Sub

Здесь мы заолняем строки данными из массива mOdds


Таким образом можно разделить задачи в одной программе.

#19   Gaechka

    Новичок


  • Участник
  • 4
1

08 July 2012 - 23:20

Jericho, Вы позволите получить весь бот целиком. Я не уверена, что сделала ве правильно. VB только начинаю осваивать. Есть предположение. Хочу проверить. Буду признательна.

#20   Jericho

    Иерихон


  • RSB
  • 2226
859

09 July 2012 - 13:38

Jericho, Вы позволите получить весь бот целиком. Я не уверена, что сделала ве правильно. VB только начинаю осваивать. Есть предположение. Хочу проверить. Буду признательна.

Леночка, я этого бота уже не найду наверно у себя. Были проблемы с железом.
Готовый мой бот есть здесь. Его будет достаточно для вашей проверки. В нем есть все функции описанные в этих туториалах.