Skip to content

Instantly share code, notes, and snippets.

@arlm
Last active January 20, 2016 20:43
Show Gist options
  • Save arlm/55cc1f7d8014558f00a6 to your computer and use it in GitHub Desktop.
Save arlm/55cc1f7d8014558f00a6 to your computer and use it in GitHub Desktop.
stockfighter.io
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
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
sanity_check :first_steps
order = create_order $venue, $stock, 0, 100, :buy, :market
buy_stock order
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
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
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
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
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
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment