Bryce Journey
United States
Omaha
Nebraska
flag msg tools
designer
Avatar
mbmbmbmbmb
There used to be a tool that would calculate the value of one's collection as entered in BGG. Old links to that tool no longer appear to work. Does anyone know what happened to this handy little tool? Was it removed during the site upgrade or is it hiding somewhere?
 
 Thumb up
 tip
 Hide
  • [+] Dice rolls
Justin Case
United States
Greensboro
North Carolina
flag msg tools
badge
Avatar
mbmbmbmbmb

That hasn't been running for quite a while now, and the fellow took the link(s) down, I'm pretty sure. He still has the collection rarity checker, but the collection value tool is no longer on the page.

 
 Thumb up
 tip
 Hide
  • [+] Dice rolls
Scott Walker
United States
Bolingbroke
Georgia
flag msg tools
Avatar
mbmbmbmbmb
I have hacked -- ok, seriously hacked -- a python script that will print out a user's collection value. Tossed it together in Python 3 under Linux. It's not very long, so I'm going to just paste the code in here, and let folks have fun with it. It uses Crustymonkey's PY-BGG package, which you can get from github here:
https://github.com/crustymonkey/py-bgg
I didn't install it as his instructions said; I placed "libbgg" in a directory underneath where I had my python code, and that seemed to work fine from the command line. For example:

cd /home/mine
mkdir bggvalue
cd bggvalue
------> Now, paste in the code below into a file called "bggvalue.py"
------>Then, copy the "libbgg" direcory from github into this directory

To run, first edit the bggvalue.py file, look for USERNAME, and change that to the BGG user name you want collection value for. I left my username in there as an example.
Then, just type:
python3 bggvalue.py

And sit back and let it talk to BGG a bit. Like I said, it isn't pretty, but it works. It only looks at the average US marketplace values. I may devote some time to prettying it up, but feel free to copy the code below and do what you want with it!


------------------------------ cut here, save as bggvalue.py
import time
from libbgg.apiv1 import BGG as BGG1
from libbgg.apiv2 import BGG as BGG2

connection1 = BGG1()
connection2 = BGG2()

# Change this to the user you want collection information on!
USERNAME = 'LKOsiliconMage'


def CalcValue(glist):
global gameswithoutvalue
global gameswithvalue
global averagegamevalue
global totalgamevalue

for glitem in glist:
gameid = glitem['id']

if (isinstance(glitem['name'], dict)):
gamename = glitem['name']['value']
else:
gamename = glitem['name'][0]['value']

print(gameid, ', ', gamename, ' ==> ', end='')

if ('marketplacelistings' in glitem):
marketplace = glitem['marketplacelistings']
else:
marketplace = None

if (marketplace != None):
n = 0
vsum = 0

mp = marketplace['listing']
# PROBLEM! mp is usually a LIST, but if it has only one entry, it becomes a DICTIONARY of mlitem!!!!!
if (isinstance(mp, dict)):
# Convert dictionary to list one dictionary, for consistency later
mp = []
mp.append(marketplace['listing'])

for mlitem in mp:
mlprice = mlitem['price']

if (mlprice['currency'] == 'USD'):
mldate = mlitem['listdate']['value']
mlvalue = float(mlprice['value'])
n += 1
vsum += mlvalue

if (n > 0):
vavg = vsum / n
gameswithvalue += 1
totalgamevalue += vavg
print('Average market value = ', '{:.2f}'.format(vavg))
else:
gameswithoutvalue += 1
print('No U.S. marketplace info.')
else:
gameswithoutvalue += 1
print('No marketplace info.')

##########################################################################################

# Gather user's game collection that is owned
print('Requesting collection info from BGG. This may take some time.')
gamecollection = connection1.get_collection(USERNAME, own = 1)
print('Collection details retrieved.')
if (gamecollection != None):
gamelist = gamecollection['items']['item'] # isolate game information

print('Total items in collection: ', len(gamelist))

# Gather boardgame ids; games and expansions
boardgameids = []
for glitem in gamelist:
gameid = glitem['objectid']
boardgameids.append(gameid)

# Request boardgame details from bgg
print('Requesting board game details from BGG. This may take some time.')
boardgameinfo = connection2.boardgame(boardgameids, marketplace=1)
boardgames = boardgameinfo['items']['item'] # isolate game information
print('Board game details retrieved. Total board games: ', len(boardgames))

# Request board game expansion details from bgg
print('Requesting board game expansion details from BGG. This may take some time.')
boardgameexpansioninfo = connection2.boardgameexpansion(boardgameids, marketplace=1)
boardgameexpansions = boardgameexpansioninfo['items']['item'] # isolate game information
print('Board game expansion details retrieved. Total board games expansions: ', len(boardgameexpansions))

# Calculate collection value
gameswithoutvalue = 0
gameswithvalue = 0
averagegamevalue = 0;
totalgamevalue = 0;

print('Calculating board game values.')
CalcValue(boardgames)
print('Calculating board game expansion values.')
CalcValue(boardgameexpansions)
print()

# All done! What's it worth?
if (gameswithvalue > 0) :
averagegamevalue = totalgamevalue / gameswithvalue
print('Collection Value...')
print('Number of games: ', len(gamelist))
print('Number of games with a value: ', gameswithvalue)
print('Number of games without a value: ', gameswithoutvalue)
print('Total game value: ', '{:.2f}'.format(totalgamevalue))
print('Average game value: ', '{:.2f}'.format(averagegamevalue))
4 
 Thumb up
3.00
 tip
 Hide
  • [+] Dice rolls
Jay D
United States
Washington
flag msg tools
mbmbmbmbmb
Nice! This is similar to what I do here: https://games.splitstreams.com/?u=LKOsiliconMage
1 
 Thumb up
 tip
 Hide
  • [+] Dice rolls
that Matt
United States
Ann Arbor
Michigan
flag msg tools
I'm a quitter. I come from a long line of quitters. It's amazing I'm here at all.
badge
I can feel bits of my brain falling away like wet cake.
Avatar
mbmbmbmbmb
LKOsiliconMage wrote:
It uses Crustymonkey's PY-BGG package, which you can get from github here:
https://github.com/crustymonkey/py-bgg
I didn't install it as his instructions said; I placed "libbgg" in a directory underneath where I had my python code, and that seemed to work fine from the command line. For example:

cd /home/mine
mkdir bggvalue
cd bggvalue
------> Now, paste in the code below into a file called "bggvalue.py"
------>Then, copy the "libbgg" direcory from github into this directory

To be clear for people who'd rather take the easy library install, that'd be: pip install py-bgg



1 
 Thumb up
 tip
 Hide
  • [+] Dice rolls
that Matt
United States
Ann Arbor
Michigan
flag msg tools
I'm a quitter. I come from a long line of quitters. It's amazing I'm here at all.
badge
I can feel bits of my brain falling away like wet cake.
Avatar
mbmbmbmbmb
LKOsiliconMage wrote:
USERNAME = 'LKOsiliconMage'

So your script works great from Windows command line minus the dreaded

57390 , Catacombs ==> Average market value = 96.33
67239 , Traceback (most recent call last):
File "C:\...\bggvalue.py", line 101, in <module>
CalcValue(boardgames)
File "C:\...\bggvalue.py", line 26, in CalcValue
print(gameid, ', ', gamename, ' ==> ', end='')
File "C:\Program Files (x86)\Python35-32\lib\encodings\cp437.py", line 19, in encode
return codecs.charmap_encode(input,self.errors,encoding_map)[0]
UnicodeEncodeError: 'charmap' codec can't encode character '\u2013' in position 37: character maps to <undefined>


Yes, it's choking on the character '–'

Someone bothering to read this far can probably fix more properly, but I the script runs just fine once I stuff in sys.stdout.encoding thusly:

Line 1 replace
import time
with
import sys # p.s. you didn't actually use time ;)

Line 25 replace
print(gameid, ', ', gamename, ' ==> ', end='')
with
try:
print(gameid, ', ', gamename, ' ==> ', end='')
except UnicodeEncodeError:
print(gameid, ', ', gamename.encode(sys.stdout.encoding, errors='replace'), ' ==> ', end='')


That said, working from listings instead of actually sold prices leads to certain issues. Like the guy selling a Magic the Gathering collection for $100,000 creating a $5,619.39 "market value" for anyone owning any MtG...
1 
 Thumb up
 tip
 Hide
  • [+] Dice rolls
Scott Walker
United States
Bolingbroke
Georgia
flag msg tools
Avatar
mbmbmbmbmb
Good points!
I don't get the Unicode error; I'm gonna blame Windows, as I don't have a Windows box to test with. Cue cliche "works fine on my machine" excuse!

Oops, the time import is from my first shot at this while I figured out the data, which used very inefficient BGG calls, and needed throttling. No longer needed once I combined the BGG calls.

Hmmm, you're saying I need to look at the market listing data better. I didn't realize what I grabbed wasn't completed sales. Nor did I think of the case of a collection within the collection, as with MtG or Heroclix. I was kind of imagining how the old web-based tool might have worked. This is what happens when I throw a script together...

I'll look into the market stuff and see if I can fix the MtG issue.
1 
 Thumb up
 tip
 Hide
  • [+] Dice rolls
Scott Walker
United States
Bolingbroke
Georgia
flag msg tools
Avatar
mbmbmbmbmb
Well, I came across this:
https://boardgamegeek.com/thread/427153/great-new-web-app-ga...
Which still works, so I know it is possible to filter by actual sold items, but I didn't see how after a cursory look through the data. Hopefully I'll have some more time during Christmas break to dig into this and fix it!
 
 Thumb up
 tip
 Hide
  • [+] Dice rolls
Hector L.
United States
Illinois
flag msg tools
Is there a simpler user guide?
 
 Thumb up
 tip
 Hide
  • [+] Dice rolls
Oriol Vilanova

Les Franqueses
Barcelona
msg tools
Avatar
mbmbmbmbmb
Hi! how good of a tool! I've been looking for something like this every now and then...

I post a slightly modified version of your code here. I just implemented a function that allows to take the average over every entry in the marketplace by converting the different currencies to your currency USD, so the prices are more significative.
* The currency exchanges are automatically obtained using google exchange, but I left an alternate function for manual setting, in case that the former one fails.
* Please note that the currency exchange rates are hardcoded, so they need to be updated by hand periodically... These could be easily retrieved automatically, from e.g. www.xe.com.

Also, I modified the print format for better readability.

Notes:
- The code is organized in two files: bggvalue.py and currencyconverter.py
- The dependencies to run this code are: pyhton3, python3-bs4 (or whatever names they get in your GNU linux or ptyhon distro); and libbgg
- To run this code just type:
$ python3 bggvalue.py
in a terminal emulator from the directory path where these files reside

It would be great if we create a github page with this code, so other people interested in this, contributes?

Thanks and good job!

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bggvalue.py ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


import time
from libbgg.apiv1 import BGG as BGG1
from libbgg.apiv2 import BGG as BGG2
import currencyconverter

connection1 = BGG1()
connection2 = BGG2()

# Change this to the user you want collection information on!
USERNAME = 'Your_UserName_Here'

# Change this to your currency: EUR / USD / GBP / AUD / CAD
myCurrency = 'EUR'


exchange = {}

exchange['USD'] = 1
exchange['EUR'] = currencyconverter.exchange_google(1,'eur','usd')
exchange['GBP'] = currencyconverter.exchange_google(1,'gbp','usd')
exchange['AUD'] = currencyconverter.exchange_google(1,'aud','usd')
exchange['CAD'] = currencyconverter.exchange_google(1,'cad','usd')


def toUSD(currency,amount):
return amount*exchange[currency]

def toUSD_manual(currency, amount):
dollar = 0
if currency == 'USD':
dollar = amount*1.0
elif currency == 'EUR':
dollar = amount*1.04
elif currency == 'GBP':
dollar = amount*1.23
elif currency == 'AUD':
dollar = amount*0.72
elif currency == 'CAD':
dollar = amount*0.75
return dollar




def CalcValue(glist):
global gameswithoutvalue
global gameswithvalue
global averagegamevalue
global totalgamevalue

for glitem in glist:
gameid = glitem['id']

if (isinstance(glitem['name'], dict)):
gamename = glitem['name']['value']
else:
gamename = glitem['name'][0]['value']

print("%s\t%30s\t" % (gameid.strip(), (gamename.strip())[:30]), end='' )

if ('marketplacelistings' in glitem):
marketplace = glitem['marketplacelistings']
else:
marketplace = None

if (marketplace != None):
n = 0
vsum = 0

mp = marketplace['listing']
# PROBLEM! mp is usually a LIST, but if it has only one entry, it becomes a DICTIONARY of mlitem!!!!!
if (isinstance(mp, dict)):
# Convert dictionary to list one dictionary, for consistency later
mp = []
mp.append(marketplace['listing'])

for mlitem in mp:
mlprice = mlitem['price']

if (mlprice['currency'] in ['USD','EUR','GBP','AUD','CAD']):
mldate = mlitem['listdate']['value']
mlvalue = toUSD(mlprice['currency'], float(mlprice['value']))
n += 1
vsum += mlvalue/exchange[myCurrency]

if (n > 0):
vavg = vsum / n
if(n>=3):
gameswithvalue += 1
totalgamevalue += vavg
else:
gameswithoutvalue += 1

print('%3d' % n, 'items\t' + myCurrency, '{:.2f}'.format(vavg))
else:
gameswithoutvalue += 1
print('No U.S. marketplace info.')
else:
gameswithoutvalue += 1
print('No info.')

##########################################################################################

# Gather user's game collection that is owned
print('Requesting collection info from BGG. This may take some time.')
gamecollection = connection1.get_collection(USERNAME, own = 1)
print('Collection details retrieved.')
if (gamecollection != None):
gamelist = gamecollection['items']['item'] # isolate game information

print('Total items in collection: ', len(gamelist))

# Gather boardgame ids; games and expansions
boardgameids = []
for glitem in gamelist:
gameid = glitem['objectid']
boardgameids.append(gameid)

# Request boardgame details from bgg
print('Requesting board game details from BGG. This may take some time.')
boardgameinfo = connection2.boardgame(boardgameids, marketplace=1)
boardgames = boardgameinfo['items']['item'] # isolate game information
print('Board game details retrieved. Total board games: ', len(boardgames))

# Request board game expansion details from bgg
print('Requesting board game expansion details from BGG. This may take some time.')
boardgameexpansioninfo = connection2.boardgameexpansion(boardgameids, marketplace=1)
boardgameexpansions = boardgameexpansioninfo['items']['item'] # isolate game information
print('Board game expansion details retrieved. Total board games expansions: ', len(boardgameexpansions))

# Calculate collection value
gameswithoutvalue = 0
gameswithvalue = 0
averagegamevalue = 0;
totalgamevalue = 0;

print('ID', '%30s' % 'Name', '\tMarket\t\tAverage Value')
#print('Calculating board game values.')
CalcValue(boardgames)
#print('Calculating board game expansion values.')
CalcValue(boardgameexpansions)
print()

# All done! What's it worth?
if (gameswithvalue > 0) :
averagegamevalue = totalgamevalue / gameswithvalue
print('Collection Value...')
print('Number of games: ', len(gamelist))
print('Number of games with a value: ', gameswithvalue)
print('Number of games without a value: ', gameswithoutvalue)
print('Total game value: ', '{:.2f}'.format(totalgamevalue))
print('Average game value: ', '{:.2f}'.format(averagegamevalue))


~~~~~~~~~~~~~~~~~~~~ currencyconverter.py ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


import urllib
from bs4 import BeautifulSoup

def exchange_google(a, f, t):
url = "https://www.google.com/finance/converter?a="+str(a)+"&from="+f+"&to="+t
with urllib.request.urlopen(url) as response:
page = response.read()
soup = BeautifulSoup(page, "html.parser")
tag = soup.find(attrs={"class":"bld"})
result = tag.contents[0].split()[0]
return float(result)

3 
 Thumb up
 tip
 Hide
  • [+] Dice rolls
Ron Schachtner
United States
North Carolina
flag msg tools
designer
I like to play games solo or over hangouts.
badge
Nothing to see here, madness.
Avatar
mbmbmbmbmb
thesunshadow wrote:
I post a slightly modified version of your code here.

For everyone else not familiar with Python, this code as posted will not work as is. Python uses white spaces/indents to mark sections of code instead of (){} style conventions used in other programming languages.

TheSunShadow, maybe you want to repost your modified code without the white spaces removed?
 
 Thumb up
 tip
 Hide
  • [+] Dice rolls
D Hall
United States
McDonough
Georgia
flag msg tools
Avatar
mbmbmbmbmb
I'm sorry ... you lost me at crustymonkey ...
1 
 Thumb up
 tip
 Hide
  • [+] Dice rolls
Oriol Vilanova

Les Franqueses
Barcelona
msg tools
Avatar
mbmbmbmbmb
Hi Ron!
Thanks for pointing this out, I just edited the post (not to confuse other people with several posts, with different versions, and so on).
I also introduced some features and improvements
 
 Thumb up
 tip
 Hide
  • [+] Dice rolls
Brian Ciuppa

Georgia
msg tools
[/q]
To be clear for people who'd rather take the easy library install, that'd be: pip install py-bgg



[/q]

Brilliant ... Thanks
 
 Thumb up
 tip
 Hide
  • [+] Dice rolls
Phil Dreyer
United States
Lake Elmo
Minnesota
flag msg tools
mb
I've done a few modifications to this code to get a more accurate pricing for games. I am not a python coder so someone could probably clean up my code a bit.

First, I added a foreign exchange component so that all sales in marketplace are converted to US dollars. This way more sales can be calculated into the equation.

Second, I added the US Consumer price index for each sale to true up a sale of a game in 2009 to a price of a game sold in 2017.

Third, I separated out each type of sale by condition (new, like new, etc.) so that a game in a collection that is like new will have a price that is higher than the average price of a game or one that is only acceptable. This depends upon a collection owner tagging their "Trade Condition" column in their BG collection with one of the following conditions: New, Like New, Very Good, Good, Acceptable, or Unacceptable. If this column is not marked then an average game price is used.

Please note: the calculation of totals (gameswithvalue, gameswithoutvalue) have been removed from the calcvalue function. Also, I commented out the board game expansion calulations from this code as I don't have any and it kept crashing the code. I would be more than happy to put that back in a future version.


import time
from libbgg.apiv1 import BGG as BGG1
from libbgg.apiv2 import BGG as BGG2
import forex_python
from forex_python.converter import CurrencyRates
global game

c = CurrencyRates()
connection1 = BGG1()
connection2 = BGG2()


conditiontype = ['new','likenew','verygood','good','acceptable','unacceptable']
longconditiontype = ['New','Like New','Very Good','Good','Acceptable','Unacceptable']

# Change this to the user you want collection information on!
USERNAME = 'youruser'


def uscpi(year, value):
min_year = int(1980)
if year < 1980: raise ValueError('uscpi: Year supplied was less than 1980.')
if year > 2017: raise ValueError('uscpi: Year supplied was greater than 2017')
# US Consumer Price Index from 1980 Thru 2017 from US Fed Reserve as a multiplier
uscpinum = (
1.135, 1.103, 1.061, 1.032, 1.043, 1.035, 1.019, 1.037, 1.041, 1.048, 1.054, 1.042, 1.030, 1.030, 1.026, 1.028,
1.029, 1.023, 1.016, 1.022, 1.034, 1.028, 1.016, 1.023, 1.027, 1.034, 1.032, 1.029, 1.038, 0.996, 1.016, 1.032,
1.021, 1.015, 1.016, 1.001, 1.013, 1.018)
result = float(value)
for x in uscpinum[year - min_year:len(uscpinum)]:
result *= x
return result

def calcvalue(glist):
exchangerate = c.get_rates('USD')

for glitem in glist:
gameid = glitem['id']
if isinstance(glitem['name'], dict):
gamename = glitem['name']['value']
else:
gamename = glitem['name'][0]['value']

if 'marketplacelistings' in glitem:
marketplace = glitem['marketplacelistings']
else:
marketplace = None

if marketplace is not None:

mp = marketplace['listing']

# PROBLEM! mp is usually a LIST, but if it has only one entry, it becomes a DICTIONARY of mlitem!!!!!
if isinstance(mp, dict):
# Convert dictionary to list one dictionary, for consistency later
mp = []
mp.append(marketplace['listing'])

for mlitem in mp:
mlprice = mlitem['price']
mlcondition = mlitem['condition']['value']
mldate = mlitem['listdate']['value']
date = mldate.split(' ')
year = date[3]
## print(' ',mlitem['price'],' ',mlitem['condition']['value'],' ',mlitem['listdate']['value'])
if mlprice['currency'] == 'USD':
mlvalue = float(mlprice['value'])
else:
mlvalue = float(exchangerate[mlprice['currency']]*float(mlprice['value']))
mlvalue = uscpi(int(year),mlvalue)
game[gameid][mlcondition]['value'] += mlvalue
game[gameid][mlcondition]['count'] += 1
game[gameid]['avgprice']['value'] += mlvalue
game[gameid]['avgprice']['count'] += 1
else:
print('No marketplace info for ' + gamename + '...')

##########################################################################################

# Gather user's game collection that is owned
print('Requesting collection info from BGG. This may take some time.')
gamecollection = connection1.get_collection(USERNAME, own = 1)
print('Collection details retrieved.')
if gamecollection != None:
gamelist = gamecollection['items']['item'] # isolate game information

print('Total items in collection: ', len(gamelist))

# Gather boardgame ids; games and expansions
boardgameids = []
game = {}
for glitem in gamelist:
gameid = glitem['objectid']
## print(glitem['objectid'], glitem['name']['TEXT'])
game[gameid] = {'name': glitem['name']['TEXT'],
'new': {'value': 0.00, 'count': 0},
'likenew': {'value': 0.00, 'count': 0},
'verygood': {'value': 0.00, 'count': 0},
'good': {'value': 0.00, 'count': 0},
'acceptable': {'value': 0.00, 'count': 0},
'unacceptable': {'value': 0.00, 'count': 0},
'avgprice': {'value': 0.00, 'count': 0}}
if not gameid in boardgameids:
boardgameids.append(gameid)

# Request boardgame details from bgg
print('Requesting marketplace details from BGG. This may take some time.')
boardgameinfo = connection2.boardgame(boardgameids, marketplace=1)
boardgames = boardgameinfo['items']['item'] # isolate game information
print('Board game details retrieved. Total board games: ', len(boardgames))

# Request board game expansion details from bgg
#- print('Requesting board game expansion details from BGG. This may take some time.')
#- boardgameexpansioninfo = connection2.boardgameexpansion(boardgameids, marketplace=1)
#- boardgameexpansions = boardgameexpansioninfo['items']['item'] # isolate game information
#- print('Board game expansion details retrieved. Total board games expansions: ', len(boardgameexpansions))

# Calculate collection value
gameswithoutvalue = 0
gameswithvalue = 0
averagegamevalue = 0
totalgamevalue = 0

print('Calculating board game values.')
calcvalue(boardgames)
#- print('Calculating board game expansion values.')
#- calcvalue(boardgameexpansions)

# Determine each game value based upon condition specified or average value
for glitem in gamelist:
gameid = glitem['objectid']
gamename = glitem['name']['TEXT']
gamecondition = glitem['conditiontext']['TEXT']
if gamecondition == '':
gamecondition = 'avgprice'
condition = conditiontype[longconditiontype.index(gamecondition)]
if game[gameid][condition]['count'] != 0:
gamevalue = game[gameid][condition]['value'] / game[gameid][condition]['count']
gameswithvalue += 1
totalgamevalue += gamevalue
elif game[gameid]['avgprice']['count'] != 0:
gamevalue = game[gameid]['avgprice']['value'] / game[gameid]['avgprice']['count']
gameswithvalue += 1
totalgamevalue += gamevalue
else:
gameswithoutvalue += 1
print(gameid, ', ', gamename, ': ', '{:.2f}'.format(gamevalue))

# All done! What's it worth?
print('Collection Value...')
print('Number of games: ', len(gamelist))
print('Number of games with a value: ', gameswithvalue)
print('Number of games without a value: ', gameswithoutvalue)
print('Total game value: ', '{:.2f}'.format(totalgamevalue))
# print('Average game value: ', '{:.2f}'.format(averagegamevalue))

1 
 Thumb up
 tip
 Hide
  • [+] Dice rolls
Front Page | Welcome | Contact | Privacy Policy | Terms of Service | Advertise | Support BGG | Feeds RSS
Geekdo, BoardGameGeek, the Geekdo logo, and the BoardGameGeek logo are trademarks of BoardGameGeek, LLC.