Skip to content

Instantly share code, notes, and snippets.

@jefflunt
Created February 23, 2014 23:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jefflunt/9178947 to your computer and use it in GitHub Desktop.
Save jefflunt/9178947 to your computer and use it in GitHub Desktop.
Basic ruby script to pull active listings and match against a strategy every 300 milliseconds (max)
source "https://rubygems.org"
gem 'curb'
1. Install Ruby 2.1.0 (there are tutorials for this - if you're new, try the
tool called RVM - http://rvm.io
If you're installing Ruby on Windows, god help you. Hopefully you're either
on Mac or Linux. Can't help you with Windows.
2. Put these files all into a single folder
3. Go into that folder
4. Type 'bundle install'
If you get errors about not being able to find 'bundler', then type:
gem install bundler
5. Type 'ruby lender.rb' to run the script
6. Press Ctrl+C to kill the script (or else it will run forever)
Sample output from the script is in the 'Sample output' file
# Script to interact with Prosper's API
require 'json'
require 'time'
require 'curb'
# Init
USER = ENV['PROSPER_USER']
PASS = ENV['PROSPER_PASS']
STRATEGIES = JSON.parse(File.read('./strategies.json'))
@queries = {
account: Curl::Easy.new('https://api.prosper.com/api/Account'),
investments: Curl::Easy.new('https://api.prosper.com/api/Investments'),
active_listings: Curl::Easy.new('https://api.prosper.com/api/Listings')
}
@queries.each do |symbol, query|
query.http_auth_types = :basic
query.username = USER
query.password = PASS
end
##
# Returns a string representation of the number of seconds (with millisecond
# precision) since the specified start time.
def seconds_since(start_time)
"#{(Time.now-start_time).to_s[0..4]}s"
end
##
# Returns a JSON object containing the results of the query lookup, or nil if
# the query failed.
def perform_query(query_symbol)
start_time = Time.now
@queries[query_symbol].perform
puts "Query time: #{seconds_since(start_time)}"
@queries[query_symbol].response_code == 200 ? JSON.parse(@queries[query_symbol].body_str) : nil
end
##
# Returns the number of strategies that match the provided listing JSON string.
# This number maps directly to the number of buy orders to be placed under the
# current users' account.
def strategy_matches(listing_json)
matches = []
STRATEGIES.each do |s|
matches << s if s["active"] && s["match_criterion"].all?{|data_point_name, acceptable_values| acceptable_values.include?(listing_json[data_point_name]) }
end
matches
end
##
# Print the investments JSON object nicely for human consumption.
def print_investments(investments_json)
start_time = Time.now
printf "%-10s | %-6s | %-20s | %8s | %8s\n", "Date", "Amount", "Status", "Listing", "Loan"
printf "----------------------------------------------------------------\n"
investments_json.each do |i|
loan_number = case i["ListingStatusDescription"]
when "Completed"
i["LoanNumber"]
when "Cancelled"
i["ListingStatusDescription"] = "XX"
"XX"
else
"--"
end
printf("%10s | $%5d | %-20s | %8s | %8s\n", i["InvestmentDate"][0..9], i["Amount"], i["ListingStatusDescription"][0..19], i["ListingNumber"], loan_number)
end
puts "Print time: #{seconds_since(start_time)}"
nil
end
##
# Print the active listings JSON object nicely for human consumption.
def print_active_listings(listings_json)
start_time = Time.now
printf "%7s | %4s | %4s | %4s | %4s | %8s | %7s | %6s | %5s | %-5s\n", "BUY", "Pop", "% Fd", "Age", "Term", "Listing", "FICO", "Rating", "Score", "ER"
printf "----------------------------------------------------------------------------------\n"
listings_json.each do |l|
matches = strategy_matches(l)
percent_funded = l["PercentFunded"]
age = Time.now - Time.parse(l["ListingCreationDate"])
# Magic formula that make the popularity approach 500 when the listing is
# <1ms old, but drop to zero by the time it's 24 hours old
popularity = (((-25 * Math::log(age))+284.1685739) * percent_funded).to_i
popularity = 0 if popularity < 0
printf "%7s | %4s | %4d | %3s | %4s | %8s | %7s | %6s | %5s | %5s\%\n", matches.collect{|m| m["id"]}.join(','), popularity.to_s, (percent_funded*100).to_i, (age/86400).to_s[0..3], l["ListingTerm"], l["ListingNumber"], l["FICOScore"], l["ProsperRating"], l["ProsperScore"], (l["EstimatedReturn"]*100).to_s[0..4]
end
puts "Print time: #{seconds_since(start_time)}"
nil
end
begin
print_active_listings(perform_query(:active_listings).sort_by{|l| l["ListingCreationDate"]})
sleep 0.3
end while true
The fields listed here are:
- BUY (if a strategy name is shown in this column, then that strategy matches that listing
- Pop (a rough guess at the popularity of that listing, based on its age and its percentage funded)
- % Fd (Percentage funded)
- Age (age since the listing was created - NOTE: probably not the same as the age since the listing
was made public during feeding time)
- Term (36 or 60 months / 3 or 5 year)
- Listing (the listing ID)
- FICO (the borrow's FICO score range)
- Rating (the Prosper Rating)
- Score (the Prosper Score)
- ER (Estimated Return - pulled from the API - NOT my own calculation)
Query time: 1.477s
BUY | Pop | % Fd | Age | Term | Listing | FICO | Rating | Score | ER
----------------------------------------------------------------------------------
| 0 | 76 | 10.1 | 60 | 1191512 | 820-850 | A | 11 | 6.081%
| 0 | 39 | 6.99 | 36 | 1212813 | 760-779 | A | 11 | 5.568%
| 0 | 33 | 5.08 | 36 | 1161322 | 740-759 | AA | 10 | 4.261%
| 0 | 89 | 5.00 | 36 | 1221276 | 680-699 | A | 8 | 5.879%
| 0 | 88 | 4.96 | 36 | 1162330 | 760-779 | A | 8 | 6.705%
| 0 | 61 | 4.22 | 36 | 1165903 | 700-719 | AA | 10 | 4.553%
| 0 | 38 | 3.86 | 36 | 1169683 | 700-719 | AA | 8 | 4.929%
| 0 | 73 | 3.35 | 36 | 1200347 | 740-759 | A | 11 | 5.342%
| 0 | 49 | 3.03 | 36 | 1201904 | 760-779 | AA | 10 | 4.261%
| 0 | 71 | 2.68 | 60 | 1202927 | 820-850 | A | 10 | 5.879%
| 0 | 90 | 2.42 | 36 | 1170511 | 680-699 | A | 7 | 6.499%
| 0 | 35 | 2.38 | 36 | 1170703 | 740-759 | AA | 10 | 4.261%
| 0 | 70 | 2.34 | 36 | 1226103 | 660-679 | A | 8 | 6.081%
| 0 | 34 | 2.28 | 36 | 1226514 | 720-739 | AA | 8 | 4.929%
| 0 | 94 | 2.23 | 36 | 1227039 | 720-739 | A | 7 | 6.705%
| 0 | 42 | 2.22 | 36 | 1227060 | 720-739 | A | 10 | 5.568%
| 0 | 35 | 2.20 | 36 | 1227270 | 760-779 | AA | 6 | 4.929%
| 0 | 33 | 2.17 | 36 | 1171417 | 760-779 | AA | 10 | 4.806%
| 0 | 28 | 2.09 | 36 | 1228101 | 780-799 | AA | 7 | 4.929%
| 0 | 22 | 2.04 | 36 | 1171837 | 760-779 | AA | 9 | 4.261%
| 0 | 64 | 1.46 | 36 | 1172950 | 800-819 | AA | 6 | 4.806%
| 0 | 19 | 1.40 | 36 | 1173310 | 720-739 | AA | 10 | 4.261%
| 0 | 43 | 1.20 | 36 | 1175035 | 720-739 | A | 7 | 5.342%
| 0 | 40 | 1.19 | 36 | 1175104 | 740-759 | AA | 9 | 4.553%
| 0 | 49 | 1.12 | 36 | 1175701 | 720-739 | AA | 11 | 4.806%
| 0 | 29 | 1.12 | 36 | 1175704 | 720-739 | AA | 8 | 4.929%
| 0 | 80 | 0.95 | 36 | 1176559 | 780-799 | AA | 10 | 4.553%
| 1 | 56 | 0.92 | 36 | 1176625 | 740-759 | A | 9 | 5.568%
| 0 | 22 | 0.90 | 36 | 1176715 | 720-739 | AA | 10 | 4.261%
| 3 | 39 | 0.73 | 36 | 1177198 | 740-759 | AA | 8 | 4.806%
Print time: 0.003s
[
{
"id": "NSR-001",
"name": "Bill Payers",
"active": true,
"match_criterion": {
"ProsperRating": ["C", "E", "HR"],
"FICOScore": ["640-659", "660-679", "680-699"],
"PublicRecordsLast10Years": [0],
"InquiriesLast6Months": [0]
}
}
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment