솔루션 아키텍처 다이어그램: 소셜 미디어 옵션 시장
우리는 거래소를 소매 거래 장소로 상상했는데, 이곳에서는 시장 참가자가 모바일 핸드셋을 사용해 다양한 소셜 미디어 1인 명의(대부분의 사람이 해시태그로 생각하는 이름)로 유럽 바이너리 범위의 콜 옵션 계약을 구매합니다. 계약은 10분마다 발행되며 10분 후에 만료됩니다. 만료 시, 앞선 기간 동안 누적된 #해시태그 멘션 개수를 사용하여 어떤 참가자가 내가격 계약을 보유하고 있었는지 결정하고 그에 따라 참가자들의 잔고가 업데이트됩니다. 프리미엄은 계약의 미결제 약정에 대해 수금되고 계약이 내가격 조건에 부합하면 환급됩니다. 모든 계약은 1:1로 지급이 이루어집니다.
우리는 데모 구현을 위해 다음과 같은 Google Cloud 제품을 선택했습니다.
Compute Engine이 작업 서버 역할을 했습니다.
이 구현은 계약 발생, 만료 및 결제를 위한 주기적 작업을 실행합니다. 이 디자인에서는 트윗을 BigQuery로 지속적으로 내부 데이터화하기 위한 데몬으로 실행할 싱글톤 프로세스도 필요합니다. 우리는 이러한 계산 작업을 Compute Engine의 임시 가상 머신으로 통합하기로 했습니다. 작업 서버의 작업은 예약을 위해 크론 작업을 사용하여 node.js 및 셸 스크립트로 작성되고 배포 유연성을 위해 삽입된 VM 시작 스크립트를 사용하여 인스턴스 템플릿으로 구성되었습니다. 작업 서버는 시스템 상의 어떤 트레이더와도 상호 작용하지 않지만, '시장 운영 데이터베이스'에 참가자 및 계약 상태를 둘 다 게재합니다.
다음과 같이 Cloud Firestore가 우리의 시장 운영 데이터베이스 역할을 했습니다.
Cloud Firestore는 우리가 시장 세션에 관한 정보를 저장할 때 사용하는 문서 지향 데이터베이스입니다. 트윗 개수와 UI로 표시되는 미결제 약정 데이터에 대한 자연스러운 목적지 역할을 하고 프런트 엔드와 빈틈없이 통합할 수 있게 합니다.
Firebase와 App Engine은 다음과 같이 우리의 모바일 및 웹 애플리케이션을 제공했습니다.
우리는 모바일 및 웹 애플리케이션의 인터페이스 둘 다에 Firebase SDK를 사용하여 프런트 엔드를 위해 간소화된 코드베이스를 유지할 수 있었습니다. (리더보드 및 시황과 같은) 일부 UI 컴포넌트는 (계약에서 참가자의 약정이 내가격으로 만료될 때와 같이) 원본 데이터의 변화를 반영하기 위해 지속적인 업데이트가 필요합니다. Firebase SDK는 개발자를 위해 간결한 추상화를 제공하고 프런트 엔드 컴포넌트가 Cloud Firestore 문서에 바인딩되도록 하므로, 원본 데이터가 변경될 때마다 자동으로 업데이트할 수 있습니다.
우리는 프런트 엔드 애플리케이션을 호스팅하기 위해 App Engine을 선택함으로써 서버 관리나 구성 배포에 크게 신경쓰지 않고 UI 개발에 집중할 수 있었습니다. 이는 팀이 매력적인 프런트 엔드를 빠르게 생산하는 데 도움이 되었습니다.
Cloud Functions는 다음과 같이 백엔드 API 서비스를 실행했습니다.
UI는 Cloud Firestore에 거래를 저장해야 하는데, Cloud Functions가 서버리스 방식으로 이 작업을 용이하게 해줍니다. 이 서버리스 백엔드가 의미하는 바는, 서버 구성이나 스키마 정의보다는 개발 로직에 집중할 수 있으므로 개발 반복 길이를 대폭 줄일 수 있다는 점입니다.
BigQuery와 BigQuery ML은 트윗을 저장하고 분석했습니다.
BigQuery는 매우 많은 갖가지 문제를 해결해 주므로 BigQuery가 이 프로젝트 중 얼마나 많은 부분을 지원하는지 잊기 쉬울 수 있습니다. 먼저 BigQuery는 최소한의 통합 노력으로 Twitter 데이터를 대규모로 경제적으로 스트리밍하는 볼륨을 안정적으로 내부 데이터화하고 저장합니다. 트윗을 내부 데이터화하기 위한 데몬 프로세스 코드는 83행의 자바스크립트로 구성되는데, 그중 BigQuery에 관련된 코드는 19행에 불과합니다.
다음으로, BigQuery에서는 표준 SQL 구문을 사용하여 내부 데이터화한 데이터에서 특징과 라벨을 추출할 수 있습니다. 가장 중요한 점은, BigQuery ML로 데이터 자체에 ML 기능을 적용하여 데이터에서 추출한 특징을 바탕으로 모델을 훈련하여 궁극적으로는 표준 SQL로 모델을 쿼리하여 런타임에서 예측을 노출할 수 있다는 점입니다.
BigQuery ML은 금융 서비스 커뮤니티가 일상적으로 직면하는 두 가지 중요한 문제를 해결하는 데 도움이 될 수 있습니다. 첫째, BigQuery ML은 데이터에 예측 모델링 기능을 적용하여 외부 예측 모델로 민감한 데이터를 이전하는 것과 관련된 비용, 시간 및 규제 리스크를 줄여줍니다. 둘째, BigQuery ML은 일반적인 SQL 구문을 사용하여 이런 모델을 개발할 수 있게 해주므로 데이터 분석가가 예측을 수행하고 통계적 통찰력을 개발할 수 있게 해줍니다. Next ‘18 London에서 거래소의 한 참석자는 특정 분야의 데이터에는 깊은 통찰력과 정통한 지식을 가지고 있지만 통계에는 약할 수도 있는 데이터 분석가와, 머신러닝에는 전문성이 있지만 특정 문제 영역에는 익숙하지 못할 수도 있는 데이터 사이언티스트 사이의 중요한 격차를 이 도구가 메워준다는 점을 관찰했습니다. 우리는 BigQuery ML이 이러한 두 가지 상이한 역할을 하나로 혼합함으로써 금융 서비스에서 중요한 인재 부족 문제의 해결에 도움이 된다고 믿습니다.
데이터 구조화 및 모델링
우리의 모델 훈련 접근 방식은 다음과 같습니다.
- 첫째, 원시 데이터를 가능한 가장 간단한 양식으로 유지합니다. 즉, 특정 해시태그를 포함한 트윗에 대해 Twitter Enterprise API 피드를 필터링하고(사전 정의된 하위 집합에서 끌어옴), 특정 해시태그뿐 아니라 Twitter 피드에서 해당 해시태그가 관찰된 트윗의 타임스탬프로도 구성된 2열의 시계열을 유지합니다.
- 둘째, 기본 시계열 테이블 위에 자리하면서 원시 Twitter 데이터로부터 특징을 추출하는 뷰를 SQL에서 정의합니다. 우리는 모델이 그 다음 10분의 기간 내에 특정 해시태그에 대해 트윗 발생 수를 예측할 수 있도록 하는 특징을 선택했습니다. 구체적인 사항은 다음과 같습니다.
#핀테크는 #블록체인이나 #브렉시트와는 전혀 다른 행태로 나타날 수 있으므로 모델이 이 점을 특징으로 인식하고 있어야 합니다.
일요일의 트윗 행태는 목요일의 트윗 행태와는 다를 것입니다.
우리는 24시간으로 이루어진 하루를 144개의 10분 단위 세그먼트로 나누어 모델이 24시간 주기의 다양한 부분 간에 발생하는 추세의 차이점에 대해 알려줄 수 있도록 했습니다.
이 값은 기본 시계열 데이터를 기반으로 하는 뷰로 계산됩니다.
미래의 트윗 수를 정확히 예측하려면 해당 해시태그가 이전 1시간 동안 얼마나 활발하게 쓰였는지, 그리고 그러한 활동이 완만한 추이로 이루어졌는지(예: 지난 6차례의 10분 기간에서 각각 일정하게 100회의 트윗 발생) 아니면 갑자기 이루어졌는지(예: 5차례의 10분 기간에서는 트윗이 하나도 없다가 한 차례의 10분 기간에서 600개의 트윗이 갑자기 발생) 여부를 모델이 알아야 합니다.
이것은 우리의 라벨로, 모델이 예측할 마지막 출력값입니다. 작업 서버에서 실행되는 계약 발행 프로세스는 각각의 해시태그와 10분의 기간에 대한 행사 범위(범위 1: 0~100, 범위 2: 101~250 등)가 있는 옵션 계약을 발행하기 위한 로직을 포함합니다. 우리는 대규모의 과거 Twitter 데이터세트를 선택하고 똑같은 로직을 사용하여 각각의 예에 내가격이었을 범위를 표시하는 라벨로 스탬핑했습니다. 주식에 대해 발행되는 개별주식 옵션 체인은 특정 주식의 주가 기록으로 알려지는 것과 마찬가지로, 우리 거래소의 옵션 체인은 기본 해시태그의 볼륨 기록으로 알려집니다.
이 SQL 뷰에서 모델을 훈련합니다. BigQuery ML은 모델 훈련을 놀랍도록 접근하기 쉬운 연습으로 만들어 줍니다. 데이터 웨어하우스 내에 머무르는 동안, 우리는 SQL 문을 사용하여 원본 데이터가 들어 있고 특정 열을 라벨로 사용하는 특정 뷰에서 훈련한 모델을 만들고 싶다고 선언합니다.
마지막으로, 프로덕션 환경에 훈련한 모델을 배포합니다. 다시 SQL을 사용하여 임의의 테이블을 쿼리하는 것과 마찬가지로 특정 입력 매개변수를 기반으로 간단히 모델을 쿼리합니다.
옵션 계약 거래
매력적인 환경을 만들기 위해, 우리는 참석자(거래 집단)가 계약과 참가자 성과를 추적할 수 있도록 여러 가지 대형 '시장 데이터' 화면을 배치함으로써 어느 정도는 공개 호가 시장 분위기를 내고 싶었습니다. 데모 참가자들은 거래소에서 Pixel 2 핸드셋을 들고 간단한 UI를 사용하여 주문을 냈는데, 여기서 세 가지 해시태그 중 일부나 전부에 크레딧을 할당할 수 있었습니다. 참가자는 주문을 낼 때 자체적인 예측에 의존하거나 현재 시장에서 거래 중인 계약의 목록 중에서 특정한 옵션 포트폴리오에 대한 BigQuery ML 모델의 예측을 사용하는 방법 중에서 선택했습니다. 참가자는 특정 계약에 대한 거래가 체결된 후에는 다른 '트레이더'와 실시간으로 거래의 성과를 모니터링한 다음 (10분마다) 만기에 거래 시간이 종료될 때 각각의 예측이 얼마나 정확했는지 확인했습니다.
ML 훈련 프로세스
트윗 볼륨에 대한 유용한 예측 결과를 손쉽게 생성하기 위해, 우리는 세 부분으로 이루어진 프로세스를 사용합니다. 첫째, 트윗 시계열 데이터를 BigQuery 테이블에 저장합니다. 둘째, 모델 훈련에 필요한 특징과 라벨을 추출하기 위해 이 테이블 위에 뷰가 층을 이루며 놓이도록 계층을 만듭니다. 마지막으로, BigQuery ML을 사용하여 모델을 훈련하고 모델에서 예측 결과를 얻습니다.
카운트할 해시태그의 정식 목록은 'hashtags'라는 이름의 BigQuery 테이블 내에 저장됩니다. 이 목록은 'tweets' 테이블과 조인되어 각 기간에 대한 집계 결과를 결정합니다.
예시 1: 'hashtags' 테이블의 스키마 정의
"schema": {
"fields": [
{
"type": "STRING",
"name": "hashtag",
"mode": "REQUIRED"
}
]
}
1. 트윗 시계열 데이터 저장
트윗 리스너는 아래의 예시 2에 나열된 스키마를 보유하는 'tweets'라는 이름의 BigQuery 테이블에 태그, 타임스탬프 및 기타 메타데이터를 작성합니다.
예시 2: 'tweets' 테이블의 스키마 정의
"schema": {
"fields": [
{
"type": "STRING",
"name": "tweet_id"
},
{
"type": "STRING",
"name": "hashtag",
"mode": "REQUIRED"
},
{
"type": "TIMESTAMP",
"name": "twitter_timestamp",
"mode": "REQUIRED"
},
{
"type": "TIMESTAMP",
"name": "system_timestamp"
},
{
"type": "STRING",
"name": "tweet_text"
},
{
"type": "FLOAT",
"name": "user_latitude"
},
{
"type": "FLOAT",
"name": "user_longitude"
},
{
"type": "STRING",
"name": "user_language"
}
]
}
2. 계층화된 뷰를 통해 특징 추출
최하위 수준 뷰는 하루 중의 기간별로 각 해시태그의 일치하는 항목 수를 계산합니다. 중간 수준 뷰는 위 섹션('데이터 구조화 및 모델링')에서 모니터링되는 특징을 추출합니다. 그런 다음 최상위 수준 뷰가 시계열 데이터에서 라벨(즉, '내가격이었을' 행사 범위)을 추출합니다.
a. 최하위 수준 뷰
최하위 수준 뷰는 예시 3에서 SQL에 의해 정의됩니다. 뷰 정의는 해시태그를 기준으로 트윗 기록을 10분 버킷으로(24시간의 일일 기준으로 이러한 버킷이 144개 있음) 집계하는 로직을 포함합니다.
예시 3: 최하위 수준 뷰 정의
(
WITH
ht_wndw AS (
SELECT
tw.hashtag,
DATE( twitter_timestamp ) date,
FORMAT_TIMESTAMP( "%E4Y", twitter_timestamp ) year,
FORMAT_TIMESTAMP("%U",twitter_timestamp ) week,
CAST(CAST(FORMAT_TIMESTAMP("%w",twitter_timestamp ) AS INT64)+1 AS STRING) day,
--Use MOD() to find out how many seconds have passed since midnight. Then, convert “number
--of seconds” to “number of 10-minute windows”
FORMAT("%03d",CAST (TRUNC (MOD( UNIX_SECONDS(twitter_timestamp), (6*60*10*24) ) / (10*60) )+1 AS INT64) ) wndw,
twitter_timestamp ts
FROM
derivatives.hashtag.hashtags
JOIN
derivatives.hashtag.tweets tw
USING
(hashtag) )
SELECT
CONCAT(hashtag,'-',year,week,day,wndw) AS row_id,
CONCAT(year,week,day,wndw) AS wndw_id,
COUNT(*) num_tweets,
hashtag,
year,
week,
day,
wndw,
date
FROM
ht_wndw
GROUP BY
hashtag,
year,
week,
day,
wndw,
date
ORDER BY
wndw_id DESC )
b. 중간 수준 뷰
일부 특징(예: 해시태그, 요일 또는 하루 중 특정 기간)은 간단히 선택할 수 있는 반면, 다른 특징(예: 지난 1시간 동안의 평균 트윗 수 및 속도)은 좀 더 복잡합니다. 예시 4의 SQL은 이처럼 더 복잡한 특징의 선택을 설명한 것입니다.
예시 4: 특징 추가를 위한 중간 뷰 정의
(
WITH
wndw_plus_prior_deltas AS (
WITH
wndw_plus_prior AS (
SELECT
wh.row_id,
wh.wndw_id,
wh.hashtag,
wh.year,
wh.week,
wh.day,
wh.date,
wh.wndw,
wh.num_tweets,
IFNULL(SAFE.LN(wh.num_tweets), 0) as log_num_tweets,
LAG(num_tweets) OVER(hashtag_ten_minute_window) prior_num_tweets,
IFNULL(SAFE.LN(LAG(num_tweets) OVER(hashtag_ten_minute_window)),
0) AS log_prior_num_tweets
FROM
derivatives.hashtag.zerofill_windowly_hashtag wh
WINDOW
hashtag_ten_minute_window AS (
PARTITION BY
hashtag
ORDER BY
YEAR,
week,
DAY,
wndw ASC) )
SELECT
row_id,
wndw_id,
hashtag,
YEAR,
week,
DAY,
date,
wndw,
num_tweets,
log_num_tweets,
prior_num_tweets,
log_prior_num_tweets,
(num_tweets-prior_num_tweets) num_tweets_delta,
(log_num_tweets-log_prior_num_tweets) log_num_tweets_delta
FROM
wndw_plus_prior
WHERE
1=1
ORDER BY
row_id,
wndw_id)
SELECT
row_id,
wndw_id,
hashtag,
YEAR,
week,
DAY,
date,
wndw,
num_tweets,
log_num_tweets,
prior_num_tweets,
log_prior_num_tweets,
num_tweets_delta,
log_num_tweets_delta,
AVG(log_num_tweets_delta) OVER (hashtag_prior_hour_window) avg_log_num_tweets_delta,
AVG(log_prior_num_tweets) OVER (hashtag_prior_hour_window) avg_log_prior_num_tweets
FROM
wndw_plus_prior_deltas
WHERE
1=1
WINDOW
hashtag_prior_hour_window AS (
PARTITION BY
hashtag
ORDER BY
YEAR,
week,
DAY,
wndw ROWS BETWEEN 6 PRECEDING
AND 1 PRECEDING)
ORDER BY
row_id DESC,
wndw_id DESC)
c. 최상위 수준 뷰
이전 뷰에서 필요한 특징을 모두 선택했으므로, 이제는 라벨을 선택할 차례입니다. 라벨은 주어진 과거 해시태그와 10분의 기간에 대해 내가격이었을 행사 범위여야 합니다. 애플리케이션의 'Contract Issuance' 일괄 작업은 10분의 기간마다 행사 범위를 생성하고 'Expiration and Settlement' 작업은 어떤 계약(계약 범위)이 내가격 조건에 부합했는지 판별합니다. 모델 훈련을 위해 과거의 예시에 라벨을 지정할 때는 이와 정확히 똑같은 애플리케이션 로직을 적용하는 것이 매우 중요합니다.
예시 5: 최상위 수준 뷰
WITH
add_log_fs AS (
SELECT
ft.row_id,
ft.wndw_id,
ft.hashtag,
ft.year,
ft.week,
ft.day,
ft.date,
ft.wndw,
ft.num_tweets,
ft.log_num_tweets,
ft.prior_num_tweets,
ft.log_prior_num_tweets,
ft.avg_log_prior_num_tweets,
ft.num_tweets_delta,
ft.log_num_tweets_delta,
Ft.avg_log_num_tweets_delta,
--Case statements determine the ITM contract range (1-7) of a historical hashtag/window’s count, had the
--game been operating at the time of the historical example
CASE
WHEN (ft.num_tweets = 0 OR ft.num_tweets < (av.avg - (av.avg * 0.3))) THEN 'One'
...
WHEN ft.num_tweets > (av.avg + (av.avg * 0.3)) THEN 'Seven'
ELSE 'No Cat'
END AS series,
av.avg avg_num_tweets_for_wndw_day,
IFNULL(SAFE.LN(av.avg),
0) AS log_avg_num_tweets_for_wndw_day
FROM
derivatives.hashtag.zerofill_windowly_hashtag_feats ft
JOIN
derivatives.hashtag.avg_counts av
ON
ft.date = av.as_of_date
AND ft.day = av.day
AND ft.wndw = av.wndw
AND ft.hashtag = av.hashtag )
SELECT
*
FROM
add_log_fs
WHERE
1=1
ORDER BY
wndw_id DESC,
row_id
3. 모델 훈련 및 모델에서 예측 결과 얻기
선택한 특징과 라벨을 포함한 뷰를 만들었으면 BigQuery ML 모델 생성문에서 이 뷰를 참조합니다.
예시 6: 모델 생성
CREATE OR REPLACE MODEL derivatives.hashtag.count_predictor_logreg
OPTIONS
( model_type='logistic_reg',
input_label_cols=['series']) AS
SELECT
wh.series,
wh.hashtag,
wh.day,
wh.wndw,
wh.avg_log_num_tweets_delta,
wh.avg_log_prior_num_tweets
FROM
derivatives.hashtag.zerofill_windowly_hashtag_labels wh
그런 다음, 계약 발행 시점에 모델에 대해 어떤 계약이 내가격이 될지에 관한 예측을 불러오기 위한 쿼리를 실행합니다.
예시 7: 모드에서 예측 선택(SELECT... FROM)
SELECT
*
FROM
ML.PREDICT (MODEL derivatives.hashtag.count_predictor_logreg,
(
SELECT
wh.hashtag,
wh.day,
wh.wndw,
wh.avg_log_num_tweets_delta,
wh.avg_log_prior_num_tweets
FROM
derivatives.hashtag.zerofill_windowly_hashtag_labels wh
WHERE
1=1
AND wh.wndw='044'
AND wh.hashtag='brexit'
AND wh.date='2019-01-01') )
개선 사항
이 거래소는 비교적 짧은 리드 시간으로 빌드되었으므로, 일정에 맞춰 선보여야 하는 현실을 고려하여 아키텍처와 전술적 측면에서 여러 가지로 단순화를 추구했습니다. 앞으로 이 거래소의 반복 과정을 통해 다음과 같은 여러 가지 향상된 기능을 구현할 생각입니다.
Cloud Pub/Sub는 구체화된 데이터 파이프라인 아키텍처를 가능하게 해주는 요소로, 거래소의 솔루션 아키텍처 내에 있는 여러 가지 영역을 개선하기 위한 것입니다. 예를 들어 Cloud Pub/Sub는 필수 컴포넌트가 일괄 작업 지향적이 아니라 이벤트 중심이 되도록 허용함으로써 보고된 트윗 카운트의 지연 시간을 줄여줄 것입니다.
현재 아키텍처는 옵션 계약의 발행과 만료를 위해 Compute Engine 인스턴스에서 실행되는 Linux '크론'에 의존하는데, 이는 솔루션의 순수 관리 부담을 늘립니다. 팀에서는 (버전 1 아키텍처 배포 후) 작년 11월에 출시된 Cloud Scheduler를 사용하여 인프라 오버헤드는 줄이면서도 기능은 그대로 유지한 채로 제공할 수 있을 것입니다.
Pub/Sub 메시지를 BigQuery에 유지하는 것처럼, 데이터를 한 곳에서 다른 곳으로 단순히 이동하는 역할을 하는 코드가 솔루션에 불필요하게 많이 포함되는 일이 종종 있습니다. 개발팀에서는 Cloud Dataflow 템플릿을 사용하여 애플리케이션에서 이처럼 별다른 차이를 만들어내지 못하는 코드 행을 줄이고 수많은 공통 사용 사례를 위한 특정 파이프라인을 간단하게 구성하고 관리할 수 있습니다.
트윗의 지리적 출처와 내부 데이터화한 트윗의 실제 텍스트를 저장하면 향후 계약을 정의하기 위한 더욱 풍부한 기초를 제공할 수도 있습니다. 예를 들어 특정 해시태그에 대한 트윗 콘텐츠에 관해 감정 분석을 수행하여 어떤 주제에 관한 전반적인 감정과 관련된 바이너리 계약을 발행할 수 있습니다.
- 일괄 작업과 모델 실행 중에서 중복 코드를 없애기 위한 BigQuery 사용자 정의 함수(UDF) 고려
10분이라는 간격으로 시간을 재치 있게 다루는 기능과 같은 특정 기능은 아키텍처의 여러 주요 요소에서 요구되는 기능이며, 팀이 SQL과 자바스크립트로 모두 중복 알고리즘을 배포하는 결과를 낳았습니다. 팀에서는 BigQuery UDF를 사용하여 일단 자바스크립트로 알고리즘을 작성하고, 자바스크립트 일괄 프로세스뿐 아니라 BigQuery ML 모델에서도 같은 코드 자산을 활용할 수 있습니다.
트레이딩 세션 중 거래소 대시보드의 스크린샷
BigQuery ML에 대해 더 자세히 배워보고 싶으면 Google의 관련 문서를 참조하거나, 더 광범위한 자료를 보고 싶으면 금융 서비스 산업을 위한 우리의 솔루션을 살펴보거나 이 대화식 BigQuery ML 둘러보기 동영상을 시청하시기 바랍니다. 혹시 샌프란시스코에서 열리는 Google Next ‘19에 참석할 기회가 있다면 직접 거래소를 체험해볼 수도 있습니다.