Last active
January 20, 2016 20:43
-
-
Save arlm/55cc1f7d8014558f00a6 to your computer and use it in GitHub Desktop.
stockfighter.io
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
level = sanity_check :chock_a_block | |
SPREAD = 8 | |
SPREAD_ADJUST = 66 | |
TOTAL_ORDERS = 100000 | |
VERBOSE = TRUE | |
orders_requested = 0 | |
orders ||= [] | |
fills ||= [] | |
_average, _expected = (get_expected_and_average_values level["instanceId"]) | |
initial_quote = get_quote | |
last_price = (initial_quote["bid"].to_i + initial_quote["last"].to_i) / 2 | |
if (_average > 0 && _expected > 0) | |
else | |
begin | |
order = create_order $venue, $stock, last_price + rand(10), rand(500 .. 30000), :buy, :limit | |
first_stock_block = (buy_stock order) | |
orders << (create_local_order first_stock_block) | |
counter = 0 | |
until counter >= 10 do | |
sleep 3 | |
update = get_order_status(first_stock_block["id"], verbose: VERBOSE) | |
update_local_order(update, first_stock_block) if update["totalFilled"] > 0 | |
break if update["totalFilled"] > 0 | |
counter += 1 | |
puts "#{10 - counter} Tries to wait" | |
end | |
end while first_stock_block["totalFilled"] == 0 | |
begin | |
_average, _expected = (get_expected_and_average_values level["instanceId"]) | |
end while _average <= 0 && _expected <= 0 | |
end | |
EXPECTED_PRICE = _expected | |
average_price = _average | |
until orders_requested == TOTAL_ORDERS do | |
puts "Orders to be placed: #{TOTAL_ORDERS - orders_requested}" | |
puts "Average $#{average_price / 100.0}" | |
order_qty = rand(500 .. 30000) | |
order_qty = TOTAL_ORDERS - orders_requested if (orders_requested + order_qty) > TOTAL_ORDERS | |
quote = get_quote | |
avg_value = (quote["bid"].to_i + quote["last"].to_i) / 2 | |
value = (last_price + avg_value) / 2 | |
puts "VALUE #{value}" | |
if value > EXPECTED_PRICE | |
value -= (value - EXPECTED_PRICE) / SPREAD + rand(10) | |
value -= (value - EXPECTED_PRICE) / SPREAD + rand(10) if value >= average_price | |
else | |
value += (value - EXPECTED_PRICE) / SPREAD + rand(10) | |
value += (value - EXPECTED_PRICE) / SPREAD + rand(10) if value <= average_price | |
end | |
puts "ADJUSTED VALUE #{value}" | |
order_value = value | |
last_price = value | |
# spread = order_value - value | |
# if spread.abs >= (order_value * 0.2) | |
# order_value -= spread * rand(SPREAD_ADJUST) / 100 if order_value > 0 | |
# order_value += spread * rand(SPREAD_ADJUST) / 100 if order_value < 0 | |
# end | |
order_value = value if order_value <= 0 | |
puts "Trying to order #{order_qty} shares by $#{order_value / 100.0}" | |
order = create_order $venue, $stock, order_value, order_qty, :buy, :limit | |
result = buy_stock order | |
result["fills"].each do |fill| | |
fills << (create_local_fill fill) | |
end | |
avg = calculate_average fills | |
average_price = avg unless avg == 0 | |
orders.each do |order| | |
update = get_order_status(order["id"], verbose: false) | |
update_local_order(update, order) | |
puts " order ##{order["id"]} CLOSED" unless order["open"] | |
puts " order ##{order["id"]} OPEN #{order["qty"]} #{(update["qty"].to_f / update["originalQty"].to_f * 100).to_i}%" if order["open"] | |
end if VERBOSE | |
orders << (create_local_order result) | |
orders_requested += order_qty | |
end | |
orders.each do |order| | |
update = get_order_status order["id"] | |
update_local_order(update, order) | |
order["shouldnt_close"] = update["qty"].to_i < update["originalQty"].to_i && !!update["open"] | |
puts "order ##{order["id"]} CLOSED" unless order["open"] | |
sleep 10 | |
if order["open"] | |
puts "order ##{order["id"]} OPEN #{order["qty"]}" | |
cancel_order order["id"] unless order["shouldnt_close"] | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
API_ENDPOINT = 'https://api.stockfighter.io/ob/api' | |
GM_ENDPOINT = 'https://api.stockfighter.io/gm' | |
$venue = "TESTEX" | |
$stock = "FOOBAR" | |
$account = "EXB123456" | |
ACCESS_TOKEN = 'dfc12b59d1d495cb0b68a0951b14ec21557f253a' | |
API_KEY = ACCESS_TOKEN |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sanity_check :first_steps | |
order = create_order $venue, $stock, 0, 100, :buy, :market | |
buy_stock order |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'json' | |
require 'uri' | |
require 'net/http' | |
require 'net/https' | |
def GET (action, cookie = "", endpoint = API_ENDPOINT) | |
uri = URI.parse("#{endpoint}/#{action}") | |
response = nil | |
cookie = "api_key=#{API_KEY}" unless cookie != "" | |
Net::HTTP.start(uri.host, uri.port, use_ssl: true){|http| | |
request = Net::HTTP::Get.new(uri, initheader = {'Cookie' => cookie, | |
'X-Starfighter-Authorization' => ACCESS_TOKEN}) | |
response = http.request(request) | |
} | |
return response | |
end | |
def DELETE (action, cookie = "", endpoint = API_ENDPOINT) | |
uri = URI.parse("#{endpoint}/#{action}") | |
response = nil | |
cookie = "api_key=#{API_KEY}" unless cookie != "" | |
Net::HTTP.start(uri.host, uri.port, use_ssl: true){|http| | |
request = Net::HTTP::Delete.new(uri, initheader = {'Cookie' => cookie, | |
'X-Starfighter-Authorization' => ACCESS_TOKEN}) | |
response = http.request(request) | |
} | |
return response | |
end | |
def POST (action, object, cookie = "", endpoint = API_ENDPOINT) | |
uri = URI.parse("#{endpoint}/#{action}"); | |
response = nil | |
cookie = "api_key=#{API_KEY}" unless cookie != "" | |
Net::HTTP.start(uri.host, uri.port, use_ssl: true){|http| | |
request = Net::HTTP::Post.new(uri, initheader = { | |
'Cookie' => cookie, | |
'X-Starfighter-Authorization' => ACCESS_TOKEN}) | |
request.body = JSON.dump(object) | |
response = http.request(request) | |
} | |
return response | |
end | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
load "constants.rb" | |
load "functions.rb" | |
load "util.rb" | |
load "rest_api.rb" | |
load "websocket_api.rb" | |
load "gm_api.rb" | |
if ARGV.length > 0 | |
case ARGV.first.chomp | |
when 'start' | |
puts start_level ARGV[1].chomp | |
when 'reset' | |
puts reset_level ARGV[1].chomp | |
when 'resume' | |
puts resume_level ARGV[1].chomp | |
when 'status' | |
puts get_level_status ARGV[1].chomp | |
when 'levels' | |
get_levels | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'json' | |
require 'uri' | |
require 'net/http' | |
require 'net/https' | |
def get_levels (verbose = true) | |
response = GET("levels", "", GM_ENDPOINT) | |
ok = JSON.parse(response.body)["ok"] rescue false | |
raise "The universe has collapsed!" unless ok | |
puts response.body | |
return JSON.parse(response.body) | |
end | |
def get_level_status (instanceId, verbose = true) | |
raise "No level instance to get info about" if !instanceId | |
response = GET("instances/#{instanceId}", "", GM_ENDPOINT) | |
ok = JSON.parse(response.body)["ok"] rescue false | |
raise "Could not get level info!" unless ok | |
level = JSON.parse(response.body) rescue nil | |
if verbose | |
puts "OPEN for #{level["details"]["endOfTheWorldDay"]} days [#{level["details"]["endOfTheWorldDay"].to_i - level["details"]["tradingDay"].to_i} days left]" if (level["state"].upcase <=> "OPEN") == 0 | |
end | |
return level | |
end | |
def reset_level (instanceId, verbose = true) | |
raise "No level instance to reset" if !instanceId | |
response = POST("instances/#{instanceId}/restart", {}, "", GM_ENDPOINT) | |
ok = JSON.parse(response.body)["ok"] rescue false | |
raise "Could not restart level!" unless ok | |
return JSON.parse(response.body) | |
end | |
def stop_level (instanceId, verbose = true) | |
raise "No level instance to stop" if !instanceId | |
response = POST("instances/#{instanceId}/stop", {}, "", GM_ENDPOINT) | |
ok = JSON.parse(response.body)["ok"] rescue false | |
raise "Could not stop level!" unless ok | |
return JSON.parse(response.body) | |
end | |
def resume_level (instanceId, verbose = true) | |
raise "No level instance to resume" if !instanceId | |
response = POST("instances/#{instanceId}/resume", {}, "", GM_ENDPOINT) | |
ok = JSON.parse(response.body)["ok"] rescue false | |
raise "Could not resume level!" unless ok | |
return JSON.parse(response.body) | |
end | |
def start_level (level, verbose = true) | |
raise "No level to start" if !level | |
response = POST("levels/#{level}", {}, "", GM_ENDPOINT) | |
ok = JSON.parse(response.body)["ok"] rescue false | |
unless ok | |
error = JSON.parse(response.body)["error"] rescue "" | |
puts "Error: #{error}" | |
raise "Level not present on this universe!" | |
end | |
level = JSON.parse(response.body) rescue nil | |
if verbose | |
puts "Instance #{level["instanceId"]}" | |
puts "#{level["secondsPerTradingDay"]} seconds per trading day" | |
puts "Tickers: #{level["tickers"]}" | |
puts "Venues: #{level["venues"]}" | |
puts "Balances: #{level["balances"]}" | |
puts "Account: #{level["account"]}" | |
end | |
if ENV["VENUE"] == nil || (ENV["VENUE"].upcase <=> "TEST") != 0 | |
$venue = level["venues"][0] | |
$stock = level["tickers"][0] | |
$account = level["account"] | |
end | |
return level | |
end | |
def judge_level (instanceId, account = ACCOUNT, link, summary) | |
raise "No level instance to judge" if !instanceId | |
raise "No link to judge" if !link | |
raise "No summary to judge" if !summary | |
data = { | |
"account" => account, | |
"explanation_link" => link, | |
"executive_summary" => summary | |
} | |
response = POST("instances/#{instanceId}/judge", data, "", GM_ENDPOINT) | |
ok = JSON.parse(response.body)["ok"] rescue false | |
unless ok | |
error = JSON.parse(response.body)["error"] rescue "" | |
puts "Error: #{error}" | |
raise "Judge nixed!" | |
end | |
judgment = JSON.parse(response.body) rescue nil | |
if verbose | |
puts judgment | |
end | |
return judgment | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
load "constants.rb" | |
load "functions.rb" | |
load "util.rb" | |
load "rest_api.rb" | |
load "websocket_api.rb" | |
load "gm_api.rb" | |
if ARGV.length > 0 | |
case ARGV.first.chomp | |
when 'first_steps' | |
load "first_steps.rb" | |
when 'chock_a_block' | |
load "chock_a_block.rb" | |
when 'sanity_check' | |
raise "level not specified" unless ARGV.length == 2 | |
sanity_check ARGV[1] | |
when 'order' | |
raise "order not specified" unless ARGV.length == 6 | |
sanity_check ARGV[1] | |
order = create_order $venue, $stock, ARGV[4].to_i, ARGV[3].to_i, ARGV[2], ARGV[5] | |
buy_stock order | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'json' | |
require 'date' | |
require 'time' | |
def check_system_heartbeat | |
response = GET "heartbeat" | |
ok = JSON.parse(response.body)["ok"] rescue false | |
raise "Oh no the world is on fire!" unless ok | |
end | |
def check_venue_heartbeat (venue = $venue) | |
response = GET "venues/#{venue}/heartbeat" | |
ok = JSON.parse(response.body)["ok"] rescue false | |
raise "Oh no the venue is on fire!" unless ok | |
end | |
def get_venue_stocks (venue = $venue, verbose: true) | |
response = GET "venues/#{venue}/stocks" | |
ok = JSON.parse(response.body)["ok"] rescue false | |
raise "Oh no tickers on the venue!" unless ok | |
symbols = JSON.parse(response.body)["symbols"] rescue nil | |
if verbose | |
symbols.each do |stock| | |
puts stock["name"] + " " + stock["symbol"] | |
end | |
end | |
return symbols | |
end | |
def get_quote (venue = $venue, stock = $stock, verbose: true) | |
response = GET "venues/#{venue}/stocks/#{stock}/quote" | |
ok = JSON.parse(response.body)["ok"] rescue false | |
raise "Oh no quote for the ticker on this venue!" unless ok | |
quote = JSON.parse(response.body) rescue nil | |
if verbose | |
puts "#{quote["symbol"]} => $#{(quote["bid"].to_f / 100).to_s} (last: #{quote["lastSize"].to_s} by $#{(quote["last"].to_f / 100).to_s} on #{ DateTime.parse(quote["lastTrade"]).strftime("%X")}) (ask: #{quote["askSize"].to_s} by $#{(quote["ask"].to_f / 100).to_s}) [#{DateTime.parse(quote["quoteTime"]).strftime("%F %X")}]" | |
end | |
return quote | |
end | |
def buy_stock (order, verbose: true) | |
raise "No orders to submit" if !order | |
response = POST("venues/#{$venue}/stocks/#{$stock}/orders", order) | |
ok = JSON.parse(response.body)["ok"] rescue false | |
unless ok | |
puts order.to_json | |
error = JSON.parse(response.body)["error"] rescue "" | |
puts "Error: #{error}" | |
raise "Oh no we could not place our orders!" | |
end | |
result = JSON.parse(response.body) rescue nil | |
if verbose | |
puts "#{result["symbol"]} on #{result["venue"]} => #{result["direction"]} [#{result["orderType"]}] #{result["originalQty"]} shares at $#{(result["price"].to_f / 100).to_s} [ID ##{result["id"]}] on #{ DateTime.parse(result["ts"]).strftime("%F %X")}" | |
puts " #{result["fills"].length} fills #{(result["originalQty"].to_f/result["totalFilled"].to_f * 100).to_s}% [#{result["totalFilled"]}/#{result["originalQty"]}]" unless result["fills"].length == 0 or result["totalFilled"].to_i == 0 | |
puts " no fills so far" if result["fills"].length == 0 or result["totalFilled"].to_i == 0 | |
end | |
return result | |
end | |
def get_order_status (order_id, venue = $venue, stock = $stock, verbose: true) | |
raise "No order to get status from!" if !order_id | |
response = GET "venues/#{venue}/stocks/#{stock}/orders/#{order_id}" | |
ok = JSON.parse(response.body)["ok"] rescue false | |
raise "Oh no order with this id for this stock on this venue!" unless ok | |
order = JSON.parse(response.body) rescue nil | |
if verbose | |
puts "#{order["symbol"]} => $#{(order["price"].to_f / 100).to_s} by #{order["originalQty"].to_s} on #{ DateTime.parse(order["ts"]).strftime("%F %X")} [#{order["orderType"]} on #{order["account"]}]" | |
order["fills"].each do |fill| | |
puts "$#{(fill["price"].to_f / 100).to_s} by #{fill["qty"].to_s} on #{ DateTime.parse(fill["ts"]).strftime("%F %X")}" | |
end unless order["qty"].to_i == 0 | |
puts " no fills so far" if order["fills"].length == 0 or order["totalFilled"].to_i == 0 | |
end | |
return order | |
end | |
def cancel_order (order_id, venue = $venue, stock = $stock, verbose: true) | |
raise "No order to cancel!" if !order_id | |
response = DELETE "venues/#{venue}/stocks/#{stock}/orders/#{order_id}" | |
ok = JSON.parse(response.body)["ok"] rescue false | |
raise "Oh order to cancel for the ticker on this venue!" unless ok | |
order = JSON.parse(response.body) rescue nil | |
if verbose | |
puts "CANCELLED #{order["symbol"]} => $#{(order["price"].to_f / 100).to_s} by #{order["qty"].to_s} (#{order["totalFilled"]} filled) [#{DateTime.parse(order["ts"]).strftime("%F %X")}]" | |
end | |
return order | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def create_order (venue = $venue, stock = $stock, price = 0, qty = 0, direction = "buy", orderType = "market") | |
return { | |
"account" => $account, | |
"venue" => $venue, | |
"stock" => $stock, | |
"price" => price, | |
"qty" => qty, | |
"direction" => direction, | |
"orderType" => orderType | |
} | |
end | |
def create_local_order (result) | |
return { | |
"id" => result["id"].to_i, | |
"qty" => result["qty"].to_i, | |
"price" => result["price"].to_i, | |
"ts" => result["ts"], | |
"open" => !!result["open"] | |
} | |
end | |
def update_local_order (result, order) | |
raise "trying to update different orders" if order["id"] != result["id"].to_i | |
order["qty"] = result["qty"].to_i | |
order["price"] = result["price"].to_i | |
order["ts"] = result["ts"] | |
order["open"] = !!result["open"] | |
order["totalFilled"] = result["totalFilled"] | |
end | |
def create_local_fill (fill) | |
return { | |
"price" => fill["price"], | |
"qty" => fill["qty"], | |
"ts" => fill["ts"] | |
} | |
end | |
def calculate_average (fills) | |
sum = 0 | |
qty = 0 | |
fills.each do |fill| | |
sum += fill["qty"] * fill["price"] | |
qty += fill["qty"] | |
end | |
return (sum/qty).to_i if qty > 0 | |
return 0 | |
end | |
def get_expected_and_average_values(instanceId) | |
status = get_level_status instanceId | |
# http://rubular.com/r/iqURIRQ1Ud | |
flash = status["flash"] | |
if flash | |
_average, _expected = flash["info"].match(/average cost of \$([\d.]*)\..*target price is \$([\d\.]*)\./i).captures | |
return (_expected.to_f * 100).to_i, (_average.to_f * 100).to_i | |
else | |
return 0, 0 | |
end | |
end | |
def sanity_check(level) | |
level_info = start_level level | |
check_system_heartbeat | |
check_venue_heartbeat | |
get_venue_stocks | |
get_quote | |
return level_info | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment